diff --git a/TODO b/TODO index d7beca0..d2e5246 100644 --- a/TODO +++ b/TODO @@ -11,8 +11,6 @@ Not easily feasible since newmailalerts are hard-coded http://mxr.mozilla.org/comm-central/find?string=content/newmailalert -* make multi-platform. At least have js-ctypes library call dependant on OS detection. (best would be to have the OS-dependant modules loaded at startup) - * convert to a https://developer.mozilla.org/en/Extensions/Bootstrapped_extensions see ../restartless-restart-ffext/ and diff --git a/src/chrome/skin/newmail.png b/src/chrome/skin/blank-icon.png similarity index 100% rename from src/chrome/skin/newmail.png rename to src/chrome/skin/blank-icon.png diff --git a/src/modules/FiretrayHandler.jsm b/src/modules/FiretrayHandler.jsm index 6c7954b..6b2ae51 100644 --- a/src/modules/FiretrayHandler.jsm +++ b/src/modules/FiretrayHandler.jsm @@ -20,7 +20,7 @@ if ("undefined" == typeof(firetray)) { }; /** - * Singleton object for tray icon management + * Singleton object and abstraction for tray icon management. */ // NOTE: modules work outside of the window scope. Unlike scripts in the // chrome, modules don't have access to objects such as window, document, or @@ -28,11 +28,92 @@ if ("undefined" == typeof(firetray)) { // (https://developer.mozilla.org/en/XUL_School/JavaScript_Object_Management) firetray.Handler = { initialized: false, + appName: null, + FILENAME_DEFAULT: null, + FILENAME_SUFFIX: "32.png", + FILENAME_BLANK: null, + FILENAME_NEWMAIL: null, + runtimeOS: null, inMailApp: false, _windowsHidden: false, _handledDOMWindows: [], + init: function() { // creates icon + this.appName = Services.appinfo.name.toLowerCase(); + this.FILENAME_DEFAULT = firetray.Utils.chromeToPath( + "chrome://firetray/skin/" + this.appName + this.FILENAME_SUFFIX); + this.FILENAME_BLANK = firetray.Utils.chromeToPath( + "chrome://firetray/skin/blank-icon.png"); + this.FILENAME_NEWMAIL = firetray.Utils.chromeToPath( + "chrome/skin/message-mail-new.png"); + + // init all handled windows + this._updateHandledDOMWindows(); + + // OS/platform checks + this.runtimeOS = Services.appinfo.OS; // "WINNT", "Linux", "Darwin" + // version checked during install, so we shouldn't need to care + let xulVer = Services.appinfo.platformVersion; // Services.vc.compare(xulVer,"2.0a")>=0 + LOG("OS=" + this.runtimeOS + ", XULrunner=" + xulVer); + switch (this.runtimeOS) { + case "Linux": + Cu.import("resource://firetray/FiretrayIconLinux.jsm"); + LOG('FiretrayIconLinux imported'); + + // instanciate tray icon + firetray.IconLinux.init(); + LOG('IconLinux initialized'); + + break; + default: + ERROR("FIRETRAY: only Linux platform supported at this time. Firetray not loaded"); + return false; + } + + // check if in mail app + var mozAppId = Services.appinfo.ID; + if (mozAppId === THUNDERBIRD_ID || mozAppId === SEAMONKEY_ID) { + this.inMailApp = true; + try { + Cu.import("resource://firetray/FiretrayMessaging.jsm"); + let prefMailNotification = firetray.Utils.prefService.getIntPref("mail_notification"); + if (prefMailNotification !== NOTIFICATION_DISABLED) + firetray.Messaging.enable(); + } catch (x) { + ERROR(x); + return false; + } + } + LOG('inMailApp: '+this.inMailApp); + + this.initialized = true; + return true; + }, + + shutdown: function() { + if (this.inMailApp) + firetray.Messaging.disable(); + + switch (this.runtimeOS) { + case "Linux": + firetray.IconLinux.shutdown(); + break; + default: + ERROR("runtimeOS unknown or undefined."); + return false; + } + + return true; + }, + + // these get overridden in OS-specific Icon handlers + setImage: function(filename) {}, + setImageDefault: function() {}, + setText: function(text, color) {}, + setTooltip: function(localizedMessage) {}, + setTooltipDefault: function() {}, + _getBaseOrXULWindowFromDOMWindow: function(win, winType) { let winInterface, winOut; try { // thx Neil Deakin !! @@ -147,54 +228,6 @@ firetray.Handler = { ERROR(x); return; } - }, - - init: function() { // creates icon - - // platform checks - let runtimeOS = Services.appinfo.OS; // "WINNT", "Linux", "Darwin" - // version checked during install, so we shouldn't need to care - let xulVer = Services.appinfo.platformVersion; // Services.vc.compare(xulVer,"2.0a")>=0 - LOG("OS=" + runtimeOS + ", XULrunner=" + xulVer); - if (runtimeOS != "Linux") { - ERROR("FIRETRAY: only Linux platform supported at this time. Firetray not loaded"); - return false; - } - Cu.import("resource://firetray/FiretrayIconLinux.jsm"); - LOG('FiretrayIconLinux imported'); - - // init all handled windows - this._updateHandledDOMWindows(); - - // instanciate tray icon - firetray.IconLinux.init(); - LOG('IconLinux initialized'); - - // check if in mail app - var mozAppId = Services.appinfo.ID; - if (mozAppId === THUNDERBIRD_ID || mozAppId === SEAMONKEY_ID) { - this.inMailApp = true; - try { - Cu.import("resource://firetray/FiretrayMessaging.jsm"); - let prefMailNotification = firetray.Utils.prefService.getIntPref("mail_notification"); - if (prefMailNotification !== NOTIFICATION_DISABLED) - firetray.Messaging.enable(); - } catch (x) { - ERROR(x); - return false; - } - } - LOG('inMailApp: '+this.inMailApp); - - this.initialized = true; - return true; - }, - - shutdown: function() { // NOT USED YET - if (this.inMailApp) - firetray.Messaging.disable(); - - firetray.IconLinux.shutdown(); } }; // firetray.Handler diff --git a/src/modules/FiretrayIconLinux.jsm b/src/modules/FiretrayIconLinux.jsm index 3f0581b..38c77a3 100644 --- a/src/modules/FiretrayIconLinux.jsm +++ b/src/modules/FiretrayIconLinux.jsm @@ -47,31 +47,22 @@ var _find_data_t = ctypes.StructType("_find_data_t", [ firetray.IconLinux = { tryIcon: null, menu: null, - appName: null, - FILENAME_DEFAULT: null, - FILENAME_SUFFIX: "32.png", - FILENAME_NEWMAIL: "newmail.png", MIN_FONT_SIZE: 4, init: function() { try { // init tray icon, some variables this.trayIcon = gtk.gtk_status_icon_new(); - this.appName = Services.appinfo.name.toLowerCase(); - this.FILENAME_DEFAULT = firetray.Utils.chromeToPath( - "chrome://firetray/skin/" + this.appName + this.FILENAME_SUFFIX); - this.FILENAME_NEWMAIL = firetray.Utils.chromeToPath( - "chrome://firetray/skin/newmail.png"); } catch (x) { ERROR(x); return false; } - this.setImageDefault(); + firetray.Handler.setImageDefault(); this._buildPopupMenu(); - this.setTooltipDefault(); + firetray.Handler.setTooltipDefault(); // attach popupMenu to trayIcon try { @@ -144,154 +135,6 @@ firetray.IconLinux = { }, - setImage: function(filename) { - if (!this.trayIcon) - return false; - LOG(filename); - - try { - gtk.gtk_status_icon_set_from_file(this.trayIcon, - filename); - } catch (x) { - ERROR(x); - return false; - } - return true; - }, - - setImageDefault: function() { - if (!this.FILENAME_DEFAULT) - throw "Default application icon filename not set"; - this.setImage(this.FILENAME_DEFAULT); - }, - - // GTK bug: Gdk-CRITICAL **: IA__gdk_window_get_root_coords: assertion `GDK_IS_WINDOW (window)' failed - setTooltip: function(toolTipStr) { - if (!this.trayIcon) - return false; - - try { - gtk.gtk_status_icon_set_tooltip_text(this.trayIcon, - toolTipStr); - } catch (x) { - ERROR(x); - return false; - } - return true; - }, - - setTooltipDefault: function() { - if (!this.appName) - throw "application name not initialized"; - this.setTooltip(this.appName); - }, - - setText: function(text, color) { // TODO: split into smaller functions; - LOG("setText"); - if (typeof(text) != "string") - throw new TypeError(); - - try { - // build background from image - let specialIcon = gdk.gdk_pixbuf_new_from_file(this.FILENAME_NEWMAIL, null); // GError **error); - let dest = gdk.gdk_pixbuf_copy(specialIcon); - let w = gdk.gdk_pixbuf_get_width(specialIcon); - let h = gdk.gdk_pixbuf_get_height(specialIcon); - - // prepare colors/alpha - let colorMap = gdk.gdk_screen_get_system_colormap(gdk.gdk_screen_get_default()); - let visual = gdk.gdk_colormap_get_visual(colorMap); - let visualDepth = visual.contents.depth; - LOG("colorMap="+colorMap+" visual="+visual+" visualDepth="+visualDepth); - let fore = new gdk.GdkColor; - fore.pixel = fore.red = fore.green = fore.blue = 0; - let alpha = new gdk.GdkColor; - alpha.pixel = alpha.red = alpha.green = alpha.blue = 0xFFFF; - if (!fore || !alpha) - WARN("Undefined GdkColor fore or alpha"); - gdk.gdk_color_parse(color, fore.address()); - if(fore.red == alpha.red && fore.green == alpha.green && fore.blue == alpha.blue) { - alpha.red=0; // make sure alpha is different from fore - } - gdk.gdk_colormap_alloc_color(colorMap, fore.address(), true, true); - gdk.gdk_colormap_alloc_color(colorMap, alpha.address(), true, true); - - // build pixmap with rectangle - let pm = gdk.gdk_pixmap_new(null, w, h, visualDepth); - let pmDrawable = ctypes.cast(pm, gdk.GdkDrawable.ptr); - let cr = gdk.gdk_cairo_create(pmDrawable); - gdk.gdk_cairo_set_source_color(cr, alpha.address()); - cairo.cairo_rectangle(cr, 0, 0, w, h); - cairo.cairo_set_source_rgb(cr, 1, 1, 1); - cairo.cairo_fill(cr); - - // build text - let scratch = gtk.gtk_window_new(gtk.GTK_WINDOW_TOPLEVEL); - let layout = gtk.gtk_widget_create_pango_layout(scratch, null); - gtk.gtk_widget_destroy(scratch); - let fnt = pango.pango_font_description_from_string("Sans 18"); - pango.pango_font_description_set_weight(fnt,pango.PANGO_WEIGHT_SEMIBOLD); - pango.pango_layout_set_spacing(layout,0); - pango.pango_layout_set_font_description(layout, fnt); - LOG("layout="+layout); - LOG("text="+text); - pango.pango_layout_set_text(layout, text,-1); - let tw = new ctypes.int; - let th = new ctypes.int; - let sz; - let border = 4; - pango.pango_layout_get_pixel_size(layout, tw.address(), th.address()); - LOG("tw="+tw.value+" th="+th.value); - // fit text to the icon by decreasing font size - while ( tw.value > (w - border) || th.value > (h - border) ) { - sz = pango.pango_font_description_get_size(fnt); - if(sz < this.MIN_FONT_SIZE) { - sz = this.MIN_FONT_SIZE; - break; - } - sz -= pango.PANGO_SCALE; - pango.pango_font_description_set_size(fnt,sz); - pango.pango_layout_set_font_description(layout, fnt); - pango.pango_layout_get_pixel_size(layout, tw.address(), th.address()); - } - LOG("tw="+tw.value+" th="+th.value); - pango.pango_font_description_free(fnt); - // center text - let px = (w-tw.value)/2; - let py = (h-th.value)/2; - - // draw text on pixmap - gdk.gdk_cairo_set_source_color(cr, fore.address()); - cairo.cairo_move_to(cr, px, py); - pangocairo.pango_cairo_show_layout(cr, layout); - cairo.cairo_destroy(cr); - gobject.g_object_unref(layout); - - let buf = gdk.gdk_pixbuf_get_from_drawable(null, pmDrawable, null, 0, 0, 0, 0, w, h); - gobject.g_object_unref(pm); - LOG("alpha="+alpha); - let alphaRed = gobject.guint16(alpha.red); - let alphaRed_guchar = ctypes.cast(alphaRed, gobject.guchar); - let alphaGreen = gobject.guint16(alpha.green); - let alphaGreen_guchar = ctypes.cast(alphaGreen, gobject.guchar); - let alphaBlue = gobject.guint16(alpha.blue); - let alphaBlue_guchar = ctypes.cast(alphaBlue, gobject.guchar); - let bufAlpha = gdk.gdk_pixbuf_add_alpha(buf, true, alphaRed_guchar, alphaGreen_guchar, alphaBlue_guchar); - gobject.g_object_unref(buf); - - // merge the rendered text on top - gdk.gdk_pixbuf_composite(bufAlpha,dest,0,0,w,h,0,0,1,1,gdk.GDK_INTERP_NEAREST,255); - gobject.g_object_unref(bufAlpha); - - gtk.gtk_status_icon_set_from_pixbuf(this.trayIcon, dest); - } catch (x) { - ERROR(x); - return false; - } - - return true; - }, - /** * Iterate over all Gtk toplevel windows to find a window. We rely on * Service.wm to watch windows correctly: we should find only one window. @@ -394,3 +237,152 @@ firetray.IconLinux = { } }; // firetray.IconLinux + + +firetray.Handler.setImage = function(filename) { + if (!firetray.IconLinux.trayIcon) + return false; + LOG(filename); + + try { + gtk.gtk_status_icon_set_from_file(firetray.IconLinux.trayIcon, + filename); + } catch (x) { + ERROR(x); + return false; + } + return true; +}; + +firetray.Handler.setImageDefault = function() { + if (!this.FILENAME_DEFAULT) + throw "Default application icon filename not set"; + this.setImage(this.FILENAME_DEFAULT); +}; + +// GTK bug: Gdk-CRITICAL **: IA__gdk_window_get_root_coords: assertion `GDK_IS_WINDOW (window)' failed +firetray.Handler.setTooltip = function(toolTipStr) { + if (!firetray.IconLinux.trayIcon) + return false; + + try { + gtk.gtk_status_icon_set_tooltip_text(firetray.IconLinux.trayIcon, + toolTipStr); + } catch (x) { + ERROR(x); + return false; + } + return true; +}; + +firetray.Handler.setTooltipDefault = function() { + if (!this.appName) + throw "application name not initialized"; + this.setTooltip(this.appName); +}; + +firetray.Handler.setText = function(text, color) { // TODO: split into smaller functions; + LOG("setText"); + if (typeof(text) != "string") + throw new TypeError(); + + try { + // build background from image + let specialIcon = gdk.gdk_pixbuf_new_from_file(this.FILENAME_BLANK, null); // GError **error); + let dest = gdk.gdk_pixbuf_copy(specialIcon); + let w = gdk.gdk_pixbuf_get_width(specialIcon); + let h = gdk.gdk_pixbuf_get_height(specialIcon); + + // prepare colors/alpha + let colorMap = gdk.gdk_screen_get_system_colormap(gdk.gdk_screen_get_default()); + let visual = gdk.gdk_colormap_get_visual(colorMap); + let visualDepth = visual.contents.depth; + LOG("colorMap="+colorMap+" visual="+visual+" visualDepth="+visualDepth); + let fore = new gdk.GdkColor; + fore.pixel = fore.red = fore.green = fore.blue = 0; + let alpha = new gdk.GdkColor; + alpha.pixel = alpha.red = alpha.green = alpha.blue = 0xFFFF; + if (!fore || !alpha) + WARN("Undefined GdkColor fore or alpha"); + gdk.gdk_color_parse(color, fore.address()); + if(fore.red == alpha.red && fore.green == alpha.green && fore.blue == alpha.blue) { + alpha.red=0; // make sure alpha is different from fore + } + gdk.gdk_colormap_alloc_color(colorMap, fore.address(), true, true); + gdk.gdk_colormap_alloc_color(colorMap, alpha.address(), true, true); + + // build pixmap with rectangle + let pm = gdk.gdk_pixmap_new(null, w, h, visualDepth); + let pmDrawable = ctypes.cast(pm, gdk.GdkDrawable.ptr); + let cr = gdk.gdk_cairo_create(pmDrawable); + gdk.gdk_cairo_set_source_color(cr, alpha.address()); + cairo.cairo_rectangle(cr, 0, 0, w, h); + cairo.cairo_set_source_rgb(cr, 1, 1, 1); + cairo.cairo_fill(cr); + + // build text + let scratch = gtk.gtk_window_new(gtk.GTK_WINDOW_TOPLEVEL); + let layout = gtk.gtk_widget_create_pango_layout(scratch, null); + gtk.gtk_widget_destroy(scratch); + let fnt = pango.pango_font_description_from_string("Sans 18"); + pango.pango_font_description_set_weight(fnt,pango.PANGO_WEIGHT_SEMIBOLD); + pango.pango_layout_set_spacing(layout,0); + pango.pango_layout_set_font_description(layout, fnt); + LOG("layout="+layout); + LOG("text="+text); + pango.pango_layout_set_text(layout, text,-1); + let tw = new ctypes.int; + let th = new ctypes.int; + let sz; + let border = 4; + pango.pango_layout_get_pixel_size(layout, tw.address(), th.address()); + LOG("tw="+tw.value+" th="+th.value); + // fit text to the icon by decreasing font size + while ( tw.value > (w - border) || th.value > (h - border) ) { + sz = pango.pango_font_description_get_size(fnt); + if(sz < firetray.IconLinux.MIN_FONT_SIZE) { + sz = firetray.IconLinux.MIN_FONT_SIZE; + break; + } + sz -= pango.PANGO_SCALE; + pango.pango_font_description_set_size(fnt,sz); + pango.pango_layout_set_font_description(layout, fnt); + pango.pango_layout_get_pixel_size(layout, tw.address(), th.address()); + } + LOG("tw="+tw.value+" th="+th.value); + pango.pango_font_description_free(fnt); + // center text + let px = (w-tw.value)/2; + let py = (h-th.value)/2; + + // draw text on pixmap + gdk.gdk_cairo_set_source_color(cr, fore.address()); + cairo.cairo_move_to(cr, px, py); + pangocairo.pango_cairo_show_layout(cr, layout); + cairo.cairo_destroy(cr); + gobject.g_object_unref(layout); + + let buf = gdk.gdk_pixbuf_get_from_drawable(null, pmDrawable, null, 0, 0, 0, 0, w, h); + gobject.g_object_unref(pm); + LOG("alpha="+alpha); + let alphaRed = gobject.guint16(alpha.red); + let alphaRed_guchar = ctypes.cast(alphaRed, gobject.guchar); + let alphaGreen = gobject.guint16(alpha.green); + let alphaGreen_guchar = ctypes.cast(alphaGreen, gobject.guchar); + let alphaBlue = gobject.guint16(alpha.blue); + let alphaBlue_guchar = ctypes.cast(alphaBlue, gobject.guchar); + let bufAlpha = gdk.gdk_pixbuf_add_alpha(buf, true, alphaRed_guchar, alphaGreen_guchar, alphaBlue_guchar); + gobject.g_object_unref(buf); + + // merge the rendered text on top + gdk.gdk_pixbuf_composite(bufAlpha,dest,0,0,w,h,0,0,1,1,gdk.GDK_INTERP_NEAREST,255); + gobject.g_object_unref(bufAlpha); + + gtk.gtk_status_icon_set_from_pixbuf(firetray.IconLinux.trayIcon, dest); + } catch (x) { + ERROR(x); + return false; + } + + return true; +}; diff --git a/src/modules/FiretrayMessaging.jsm b/src/modules/FiretrayMessaging.jsm index 0881734..5c1037b 100644 --- a/src/modules/FiretrayMessaging.jsm +++ b/src/modules/FiretrayMessaging.jsm @@ -8,7 +8,7 @@ const Cu = Components.utils; Cu.import("resource:///modules/mailServices.js"); Cu.import("resource://gre/modules/PluralForm.jsm"); -Cu.import("resource://firetray/FiretrayIconLinux.jsm"); +// Cu.import("resource://firetray/FiretrayHandler.jsm"); Cu.import("resource://firetray/commons.js"); const FLDR_UNINTERESTING = @@ -52,8 +52,8 @@ firetray.Messaging = { if (!this.enabled) return; - MailServices.mailSession.RemoveFolderListener(this); - firetray.IconLinux.setImageDefault(); + MailServices.mailSession.RemoveFolderListener(this.mailSessionListener); + firetray.Handler.setImageDefault(); this.enabled = false; }, @@ -110,16 +110,16 @@ firetray.Messaging = { // update icon if (this._unreadMsgCount == 0) { - firetray.IconLinux.setImageDefault(); - firetray.IconLinux.setTooltipDefault(); + firetray.Handler.setImageDefault(); + firetray.Handler.setTooltipDefault(); } else if (this._unreadMsgCount > 0) { let prefIconTextColor = firetray.Utils.prefService.getCharPref("icon_text_color"); - firetray.IconLinux.setText(this._unreadMsgCount.toString(), prefIconTextColor); + firetray.Handler.setText(this._unreadMsgCount.toString(), prefIconTextColor); let localizedMessage = PluralForm.get( this._unreadMsgCount, firetray.Utils.strings.GetStringFromName("tooltip.unread_messages")) .replace("#1", this._unreadMsgCount);; - firetray.IconLinux.setTooltip(localizedMessage); + firetray.Handler.setTooltip(localizedMessage); } else { throw "negative message count"; // should never happen }