FireTray/src/modules/linux/FiretrayChatStatusIcon.jsm

302 lines
9.8 KiB
JavaScript

/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
var EXPORTED_SYMBOLS = [ "firetray" ];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ctypes.jsm");
Cu.import("resource://firetray/ctypes/ctypesMap.jsm");
Cu.import("resource://firetray/ctypes/linux/gdk.jsm");
Cu.import("resource://firetray/ctypes/linux/gio.jsm");
Cu.import("resource://firetray/ctypes/linux/gobject.jsm");
Cu.import("resource://firetray/ctypes/linux/gtk.jsm");
Cu.import("resource://firetray/linux/FiretrayWindow.jsm");
Cu.import("resource://firetray/commons.js");
firetray.Handler.subscribeLibsForClosing([gdk, gio, gobject, gtk]);
if ("undefined" == typeof(firetray.Handler))
log.error("This module MUST be imported from/after FiretrayHandler !");
let log = firetray.Logging.getLogger("firetray.ChatStatusIcon");
const ALPHA_STEP = 5;
const ALPHA_STEP_SLEEP_MILLISECONDS = 10;
const FADE_OVER_SLEEP_MILLISECONDS = 500;
const BLINK_TOGGLE_PERIOD_MILLISECONDS = 500;
firetray.ChatStatusIcon = {
GTK_THEME_ICON_PATH: null,
initialized: false,
trayIcon: null,
appId: (function(){return Services.appinfo.ID;})(),
themedIcons: (function(){let o = {};
o[FIRETRAY_IM_STATUS_AVAILABLE] = null;
o[FIRETRAY_IM_STATUS_AWAY] = null;
o[FIRETRAY_IM_STATUS_BUSY] = null;
o[FIRETRAY_IM_STATUS_OFFLINE] = null;
return o;
})(),
themedIconNameCurrent: null,
timers: {'blink': null, 'fade-step': null, 'fade-loop': null},
events: {},
generators: {},
pixBuffer: {},
get isBlinking () {return (firetray.Chat.convsToAcknowledge.length() > 0);},
init: function() {
if (!firetray.Handler.appHasChat) throw "ChatStatusIcon for chat app only";
firetray.GtkIcons.init();
this.trayIcon = gtk.gtk_status_icon_new();
this.loadThemedIcons();
this.setIconImage(this.themedIconNameCurrent || FIRETRAY_IM_STATUS_OFFLINE); // updated in Chat anyway
this.setIconTooltipDefault();
this.initTimers();
this.initialized = true;
return true;
},
shutdown: function() {
this.destroyTimers();
this.destroyIcons();
this.initialized = false;
},
loadThemedIcons: function() {
for (let name in this.themedIcons)
this.themedIcons[name] = gio.g_themed_icon_new(name);
},
destroyIcons: function() {
for (let name in this.themedIcons) {
let gicon = this.themedIcons[name];
gicon = gobject.g_object_unref(gicon);
}
gobject.g_object_unref(this.trayIcon);
},
setIconImageFromGIcon: function(gicon) {
if (!firetray.ChatStatusIcon.trayIcon || !gicon)
log.error("Icon missing");
gtk.gtk_status_icon_set_from_gicon(firetray.ChatStatusIcon.trayIcon, gicon);
},
setIconImage: function(name) {
this.themedIconNameCurrent = name;
let blinkStyle = firetray.Utils.prefService.getIntPref("chat_icon_blink_style");
if (blinkStyle === FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE &&
this.isBlinking) {
this.events['icon-changed'] = true;
return;
}
this.setIconImageFromGIcon(this.themedIcons[name]);
},
setIconVoid: function() {
gtk.gtk_status_icon_set_from_pixbuf(this.trayIcon, null);
},
initTimers: function() {
for (let tname in this.timers)
this.timers[tname] = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
},
destroyTimers: function() {
for (let tname in this.timers) {
this.timers[tname].cancel();
this.timers[tname] = null;
}
this.events = {};
},
buildPixBuf: function() {
let icon_theme = gtk.gtk_icon_theme_get_for_screen(gdk.gdk_screen_get_default());
// get pixbuf
let arry = gobject.gchar.ptr.array()(2);
arry[0] = gobject.gchar.array()(firetray.ChatStatusIcon.themedIconNameCurrent);
arry[1] = null;
log.debug("icon name="+firetray.ChatStatusIcon.themedIconNameCurrent+", theme="+icon_theme+", arry="+arry);
let icon_info = gtk.gtk_icon_theme_choose_icon(icon_theme, arry, 22, gtk.GTK_ICON_LOOKUP_FORCE_SIZE);
// create pixbuf
let pixbuf = gdk.gdk_pixbuf_copy(gtk.gtk_icon_info_load_icon(icon_info, null));
gtk.gtk_icon_info_free(icon_info); // gobject.g_object_unref(icon_info) in 3.8
// checks
if (gdk.gdk_pixbuf_get_colorspace(pixbuf) != gdk.GDK_COLORSPACE_RGB)
log.error("wrong colorspace for pixbuf");
if (gdk.gdk_pixbuf_get_bits_per_sample(pixbuf) != 8)
log.error("wrong bits_per_sample for pixbuf");
if (!gdk.gdk_pixbuf_get_has_alpha(pixbuf))
log.error("pixbuf doesn't have alpha");
let n_channels = gdk.gdk_pixbuf_get_n_channels(pixbuf);
if (n_channels != 4)
log.error("wrong nb of channels for pixbuf");
// init transform
let width = gdk.gdk_pixbuf_get_width(pixbuf);
let height = gdk.gdk_pixbuf_get_height(pixbuf);
log.debug("width="+width+", height="+height);
let length = width*height*n_channels;
let pixels = ctypes.cast(gdk.gdk_pixbuf_get_pixels(pixbuf),
gobject.guchar.array(length).ptr);
log.debug("pixels="+pixels);
// backup alpha for later fade-in
let buffer = new ArrayBuffer(width*height);
let alpha_bak = new Uint8Array(buffer);
for (let i=3; i<length; i+=n_channels)
alpha_bak[(i-3)/n_channels] = pixels.contents[i];
log.debug("pixbuf created");
this.pixBuffer = {
pixbuf: pixbuf, // TO BE UNREFED WITH to g_object_unref() !!
width: width,
height: height,
length: length,
n_channels: n_channels,
pixels: pixels,
buffer: buffer,
alpha_bak: alpha_bak
};
},
dropPixBuf: function() {
gobject.g_object_unref(this.pixBuffer.pixbuf);
log.debug("pixbuf unref'd");
this.pixBuffer = {};
},
fadeGenerator: function() {
let pixbuf = firetray.ChatStatusIcon.pixBuffer;
for (let a=255; a>0; a-=ALPHA_STEP) {
for(let i=3; i<pixbuf.length; i+=pixbuf.n_channels)
if (pixbuf.pixels.contents[i]-ALPHA_STEP>0)
pixbuf.pixels.contents[i] -= ALPHA_STEP;
gtk.gtk_status_icon_set_from_pixbuf(firetray.ChatStatusIcon.trayIcon, pixbuf.pixbuf);
yield true;
}
for (let a=255; a>0; a-=ALPHA_STEP) {
for(let i=3; i<pixbuf.length; i+=pixbuf.n_channels)
if (pixbuf.pixels.contents[i]+ALPHA_STEP<=pixbuf.alpha_bak[(i-3)/pixbuf.n_channels]) {
pixbuf.pixels.contents[i] += ALPHA_STEP;
}
gtk.gtk_status_icon_set_from_pixbuf(firetray.ChatStatusIcon.trayIcon, pixbuf.pixbuf);
yield true;
}
},
fadeStep: function() {
try {
if (firetray.ChatStatusIcon.generators['fade'].next())
firetray.ChatStatusIcon.timers['fade-step'].initWithCallback(
{ notify: firetray.ChatStatusIcon.fadeStep },
ALPHA_STEP_SLEEP_MILLISECONDS, Ci.nsITimer.TYPE_ONE_SHOT);
} catch (e if e instanceof StopIteration) {
if (firetray.ChatStatusIcon.events['stop-fade']) {
log.debug("stop-fade");
delete firetray.ChatStatusIcon.events['stop-fade'];
delete firetray.ChatStatusIcon.generators['fade'];
firetray.ChatStatusIcon.setIconImage(firetray.ChatStatusIcon.themedIconNameCurrent);
firetray.ChatStatusIcon.dropPixBuf();
return;
}
if (firetray.ChatStatusIcon.events['icon-changed']) {
delete firetray.ChatStatusIcon.events['icon-changed'];
firetray.ChatStatusIcon.dropPixBuf();
firetray.ChatStatusIcon.buildPixBuf();
firetray.ChatStatusIcon.timers['fade-loop'].initWithCallback(
{ notify: firetray.ChatStatusIcon.fadeLoop },
FADE_OVER_SLEEP_MILLISECONDS, Ci.nsITimer.TYPE_ONE_SHOT);
} else {
firetray.ChatStatusIcon.timers['fade-loop'].initWithCallback(
{ notify: firetray.ChatStatusIcon.fadeLoop },
FADE_OVER_SLEEP_MILLISECONDS, Ci.nsITimer.TYPE_ONE_SHOT);
}
};
},
fadeLoop: function() {
firetray.ChatStatusIcon.generators['fade'] = firetray.ChatStatusIcon.fadeGenerator();
firetray.ChatStatusIcon.fadeStep();
},
startFading: function() {
log.debug("startFading");
this.buildPixBuf();
this.fadeLoop();
},
stopFading: function() {
log.debug("stopFading");
this.events['stop-fade'] = true;
},
startBlinking: function() { // gtk_status_icon_set_blinking() deprecated
this.on = true;
firetray.ChatStatusIcon.timers['blink'].initWithCallback({
notify: function() {
if (firetray.ChatStatusIcon.on)
firetray.ChatStatusIcon.setIconVoid();
else
firetray.ChatStatusIcon.setIconImage(firetray.ChatStatusIcon.themedIconNameCurrent);
firetray.ChatStatusIcon.on = !firetray.ChatStatusIcon.on;
}
}, BLINK_TOGGLE_PERIOD_MILLISECONDS, Ci.nsITimer.TYPE_REPEATING_SLACK);
},
stopBlinking: function() {
log.debug("stopBlinking");
this.timers['blink'].cancel();
this.setIconImage(firetray.ChatStatusIcon.themedIconNameCurrent);
this.on = false;
},
toggleBlinkStyle: function(blinkStyle) {
switch (blinkStyle) {
case FIRETRAY_CHAT_ICON_BLINK_STYLE_NORMAL:
this.stopFading();
this.startBlinking();
break;
case FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE:
this.stopBlinking();
this.startFading();
break;
default:
throw new Error("Undefined chat icon blink style.");
}
},
setUrgency: function(xid, urgent) {
gtk.gtk_window_set_urgency_hint(firetray.Handler.gtkWindows.get(xid), urgent);
},
setIconTooltip: function(txt) {
if (!this.trayIcon) return false;
gtk.gtk_status_icon_set_tooltip_text(this.trayIcon, txt);
return true;
},
setIconTooltipDefault: function() {
this.setIconTooltip(firetray.Handler.appName+" Chat");
}
// TODO: onclick/activate -> chatHandler.showCurrentConversation()
}; // firetray.ChatStatusIcon