From 88bf1451e03a71af5b509d67810e29fd2a5eca6e Mon Sep 17 00:00:00 2001 From: foudfou Date: Sat, 17 Dec 2011 04:15:02 +0100 Subject: [PATCH] * extract FiretrayWindow.jsm out of FiretrayStatusIcon.jsm * new windows get registered (onLoad) * basic show/hide fonctionality WORK IN PROGRESS --- src/chrome/content/overlay.js | 1 + src/modules/FiretrayHandler.jsm | 47 ++--- src/modules/gdk.jsm | 1 + src/modules/gtk2/FiretrayStatusIcon.jsm | 212 ++-------------------- src/modules/gtk2/FiretrayWindow.jsm | 228 ++++++++++++++++++++++++ src/modules/x11.jsm | 2 + 6 files changed, 270 insertions(+), 221 deletions(-) create mode 100644 src/modules/gtk2/FiretrayWindow.jsm diff --git a/src/chrome/content/overlay.js b/src/chrome/content/overlay.js index 929e8e1..8210a58 100644 --- a/src/chrome/content/overlay.js +++ b/src/chrome/content/overlay.js @@ -32,6 +32,7 @@ firetray.Main = { } let init = firetray.Handler.initialized || firetray.Handler.init(); + firetray.Handler.registerWindow(window); // update unread messages count if (firetray.Handler.inMailApp && firetray.Messaging.initialized) diff --git a/src/modules/FiretrayHandler.jsm b/src/modules/FiretrayHandler.jsm index 5d19650..ad6b705 100644 --- a/src/modules/FiretrayHandler.jsm +++ b/src/modules/FiretrayHandler.jsm @@ -35,11 +35,9 @@ firetray.Handler = { FILENAME_NEWMAIL: null, runtimeOS: null, inMailApp: false, + windows: [], - _windowsHidden: false, - _handledDOMWindows: [], - - init: function() { // creates icon + init: function() { // does creates icon this.appName = Services.appinfo.name.toLowerCase(); this.FILENAME_DEFAULT = firetray.Utils.chromeToPath( "chrome://firetray/skin/" + this.appName + this.FILENAME_SUFFIX); @@ -48,8 +46,10 @@ firetray.Handler = { this.FILENAME_NEWMAIL = firetray.Utils.chromeToPath( "chrome://firetray/skin/message-mail-new.png"); +/* GTK TEST // init all handled windows this._updateHandledDOMWindows(); +*/ // OS/platform checks this.runtimeABI = Services.appinfo.XPCOMABI; @@ -61,6 +61,8 @@ firetray.Handler = { case "Linux": Cu.import("resource://firetray/gtk2/FiretrayStatusIcon.jsm"); LOG('FiretrayStatusIcon imported'); + Cu.import("resource://firetray/gtk2/FiretrayWindow.jsm"); + LOG('FiretrayWindow imported'); // instanciate tray icon firetray.StatusIcon.init(); @@ -116,6 +118,9 @@ firetray.Handler = { setText: function(text, color) {}, setTooltip: function(localizedMessage) {}, setTooltipDefault: function() {}, + showHideToTray: function() {}, + registerWindow: function(win) {}, + unregisterWindow: function(win) {}, _getBaseOrXULWindowFromDOMWindow: function(win, winType) { let winInterface, winOut; @@ -143,24 +148,26 @@ firetray.Handler = { return winOut; }, - /* - * DAMN IT ! getZOrderDOMWindowEnumerator doesn't work on Linux :-( - * https://bugzilla.mozilla.org/show_bug.cgi?id=156333, and all windows - * seem to have the same zlevel ("normalZ") which is different from the - * z-order. There seems to be no means to get/set the z-order at this - * time... - */ - _updateHandledDOMWindows: function() { - LOG("_updateHandledDOMWindows"); - this._handledDOMWindows = []; - var windowsEnumerator = Services.wm.getEnumerator(null); // returns a nsIDOMWindow - while (windowsEnumerator.hasMoreElements()) { - this._handledDOMWindows[this._handledDOMWindows.length] = - windowsEnumerator.getNext(); - } - }, /* GTK TEST */ + + // /* + // * DAMN IT ! getZOrderDOMWindowEnumerator doesn't work on Linux :-( + // * https://bugzilla.mozilla.org/show_bug.cgi?id=156333, and all windows + // * seem to have the same zlevel ("normalZ") which is different from the + // * z-order. There seems to be no means to get/set the z-order at this + // * time... + // */ + // _updateHandledDOMWindows: function() { + // LOG("_updateHandledDOMWindows"); + // this._handledDOMWindows = []; + // var windowsEnumerator = Services.wm.getEnumerator(null); // returns a nsIDOMWindow + // while (windowsEnumerator.hasMoreElements()) { + // this._handledDOMWindows[this._handledDOMWindows.length] = + // windowsEnumerator.getNext(); + // } + // }, + // showHideToTray: function(a1) { // unused param // LOG("showHideToTray"); diff --git a/src/modules/gdk.jsm b/src/modules/gdk.jsm index 2298bc1..4726bc9 100644 --- a/src/modules/gdk.jsm +++ b/src/modules/gdk.jsm @@ -129,6 +129,7 @@ function gdk_defines(lib) { ctypes.default_abi, this.GdkFilterReturn, [this.GdkXEvent.ptr, this.GdkEvent.ptr, gobject.gpointer]).ptr; + lib.lazy_bind("gdk_x11_drawable_get_xid", x11.XID, this.GdkDrawable.ptr); lib.lazy_bind("gdk_window_new", this.GdkWindow.ptr, this.GdkWindow.ptr, this.GdkWindowAttributes.ptr, gobject.gint); lib.lazy_bind("gdk_window_destroy", ctypes.void_t, this.GdkWindow.ptr); lib.lazy_bind("gdk_x11_window_set_user_time", ctypes.void_t, this.GdkWindow.ptr, gobject.guint32); diff --git a/src/modules/gtk2/FiretrayStatusIcon.jsm b/src/modules/gtk2/FiretrayStatusIcon.jsm index e5bb515..d6ae933 100644 --- a/src/modules/gtk2/FiretrayStatusIcon.jsm +++ b/src/modules/gtk2/FiretrayStatusIcon.jsm @@ -17,36 +17,17 @@ Cu.import("resource://firetray/libc.jsm"); Cu.import("resource://firetray/pango.jsm"); Cu.import("resource://firetray/commons.js"); -const Services2 = {}; -XPCOMUtils.defineLazyServiceGetter( - Services2, - "uuid", - "@mozilla.org/uuid-generator;1", - "nsIUUIDGenerator" -); - if ("undefined" == typeof(firetray.Handler)) - ERROR("FiretrayIcon*.jsm MUST be imported from/after FiretrayHandler !"); + ERROR("This module MUST be imported from/after FiretrayHandler !"); -// pointers to JS functions. should *not* be eaten by GC ("Running global -// cleanup code from study base classes" ?) +// pointers to JS functions. MUST LIVE DURING ALL THE EXECUTION var firetray_iconActivateCb; var firetray_popupMenuCb; var firetray_menuItemQuitActivateCb; -var firetray_findGtkWindowByTitleCb; -var firetray_windowDeleteCb; -var firetray_windowStateCb; - -/** - * custum type used to pass data in to and out of firetray_findGtkWindowByTitleCb - */ -var _find_data_t = ctypes.StructType("_find_data_t", [ - { inTitle: ctypes.char.ptr }, - { outWindow: gtk.GtkWindow.ptr } -]); firetray.StatusIcon = { + initialized: false, trayIcon: null, menu: null, MIN_FONT_SIZE: 4, @@ -66,60 +47,23 @@ firetray.StatusIcon = { firetray.Handler.setTooltipDefault(); - // attach popupMenu to trayIcon - try { - - /* GTK TEST. initWindow should be done somewhere else - (Firetray.WindowLinux ?) */ - let win = Services.wm.getMostRecentWindow(null); - /* NOTE: it should not be necessary to cast gtkWin to a GtkWidget, nor - gtk_widget_add_events(gtkWin, gdk.GDK_ALL_EVENTS_MASK); */ - let gtkWin = this.getGtkWindowHandle(win); - LOG("gtkWin="+gtkWin); - let gdkWin = gtk.gtk_widget_get_window( - ctypes.cast(gtkWin, gtk.GtkWidget.ptr)); - LOG("gdkWin="+gdkWin); - - firetray_iconActivateCb = gtk.GCallbackStatusIconActivate_t(firetray.StatusIcon.showHideToTray); - // gobject.g_signal_connect(this.trayIcon, "activate", firetray_iconActivateCb, null); - gobject.g_signal_connect(this.trayIcon, "activate", firetray_iconActivateCb, gdkWin); // TEST - - /* delete_event_cb (in gtk2/nsWindow.cpp) prevents us from catching - "delete-event" */ - - let deleteEventId = gobject.g_signal_lookup("delete-event", gtk.gtk_window_get_type()); - LOG("deleteEventId="+deleteEventId); - let deleteEventHandler = gobject.g_signal_handler_find(gtkWin, gobject.G_SIGNAL_MATCH_ID, deleteEventId, 0, null, null, null); - LOG("deleteEventHandler="+deleteEventHandler); - gobject.g_signal_handler_block(gtkWin, deleteEventHandler); // not _disconnect - - firetray_windowDeleteCb = gtk.GCallbackGenericEvent_t(firetray.StatusIcon.windowDelete); - // let res = gobject.g_signal_connect(gtkWin, "delete_event", firetray_windowDeleteCb, null); - let res = gobject.g_signal_connect(gtkWin, "delete_event", firetray_windowDeleteCb, null); - LOG("g_connect delete-event="+res); - - - /* we'll catch minimize events with Gtk: - http://stackoverflow.com/questions/8018328/what-is-the-gtk-event-called-when-a-window-minimizes */ - firetray_windowStateCb = gtk.GCallbackGenericEvent_t(firetray.StatusIcon.windowState); - res = gobject.g_signal_connect(gtkWin, "window-state-event", firetray_windowStateCb, null); - LOG("g_connect window-state-event="+res); - - } catch (x) { - ERROR(x); - return false; - } + LOG("showHideToTray: "+firetray.Handler.hasOwnProperty("showHideToTray")); + firetray_iconActivateCb = gtk.GCallbackStatusIconActivate_t(firetray.Handler.showHideToTray); + let res = gobject.g_signal_connect(firetray.StatusIcon.trayIcon, "activate", firetray_iconActivateCb, null); + LOG("g_connect activate="+res); + this.initialized = true; return true; }, shutdown: function() { cairo.close(); - // glib.close(); gobject.close(); gdk.close(); gtk.close(); pango.close(); + + this.initialized = false; }, _buildPopupMenu: function() { @@ -146,8 +90,7 @@ firetray.StatusIcon = { definition) because we need the args passed to it ! As a consequence, we need to abandon 'this' in popupMenu() */ let that = this; - firetray_popupMenuCb = - gtk.GCallbackMenuPopup_t(that.popupMenu); + firetray_popupMenuCb = gtk.GCallbackMenuPopup_t(that.popupMenu); gobject.g_signal_connect(this.trayIcon, "popup-menu", firetray_popupMenuCb, this.menu); }, @@ -165,135 +108,6 @@ firetray.StatusIcon = { } catch (x) { LOG(x); } - - }, - - /** - * 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. - * - * @author Nils Maier (stolen from MiniTrayR) - * @param window nsIDOMWindow from Services.wm - * @return a gtk.GtkWindow.ptr - */ - getGtkWindowHandle: function(window) { - let baseWindow = window - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIBaseWindow); - - // Tag the base window - let oldTitle = baseWindow.title; - baseWindow.title = Services2.uuid.generateUUID().toString(); - - try { - // Search the window by the *temporary* title - let widgets = gtk.gtk_window_list_toplevels(); - let that = this; - firetray_findGtkWindowByTitleCb = gobject.GFunc_t(that._findGtkWindowByTitle); - var userData = new _find_data_t( - ctypes.char.array()(baseWindow.title), - null - ).address(); - LOG("userData="+userData); - gobject.g_list_foreach(widgets, firetray_findGtkWindowByTitleCb, userData); - gobject.g_list_free(widgets); - - if (userData.contents.outWindow.isNull()) { - throw new Error("Window not found!"); - } - LOG("found window: "+userData.contents.outWindow); - } catch (x) { - ERROR(x); - } finally { - // Restore - baseWindow.title = oldTitle; - } - - return userData.contents.outWindow; - }, - - /** - * compares a GtkWindow's title with a string passed in userData - * @param gtkWidget: GtkWidget from gtk_window_list_toplevels() - * @param userData: _find_data_t - */ - _findGtkWindowByTitle: function(gtkWidget, userData) { - LOG("GTK Window: "+gtkWidget+", "+userData); - - let data = ctypes.cast(userData, _find_data_t.ptr); - let inTitle = data.contents.inTitle; - LOG("inTitle="+inTitle.readString()); - - let gtkWin = ctypes.cast(gtkWidget, gtk.GtkWindow.ptr); - let winTitle = gtk.gtk_window_get_title(gtkWin); - - try { - if (!winTitle.isNull()) { - LOG(inTitle+" = "+winTitle); - if (libc.strcmp(inTitle, winTitle) == 0) - data.contents.outWindow = gtkWin; - } - } catch (x) { - ERROR(x); - } - }, - - // FIXME: it may not be worth wrapping gtk_widget_get_window... - getGdkWindowFromGtkWindow: function(gtkWin) { - try { - let gtkWid = ctypes.cast(gtkWin, gtk.GtkWidget.ptr); - var gdkWin = gtk.gtk_widget_get_window(gtkWid); - } catch (x) { - ERROR(x); - } - return gdkWin; - }, - - getGdkWindowHandle: function(win) { - try { - let gtkWin = firetray.StatusIcon.getGtkWindowHandle(win); - LOG("FOUND: "+gtk.gtk_window_get_title(gtkWin).readString()); - let gdkWin = this.getGdkWindowFromGtkWindow(gtkWin); - if (!gdkWin.isNull()) { - LOG("has window"); - return gdkWin; - } - } catch (x) { - ERROR(x); - } - return null; - }, - - showHideToTray: function(gtkStatusIcon, userData){ - LOG("showHideToTray: "+userData); - try { - let gdkWin = ctypes.cast(userData, gdk.GdkWindow.ptr); - gdk.gdk_window_show(gdkWin); - } catch (x) { - ERROR(x); - } - - let stopPropagation = true; - return stopPropagation; - }, - - windowDelete: function(gtkWidget, gdkEv, userData){ - LOG("gtk_widget_hide: "+gtkWidget+", "+gdkEv+", "+userData); - try{ - let gdkWin = firetray.StatusIcon.getGdkWindowFromGtkWindow(gtkWidget); - gdk.gdk_window_hide(gdkWin); - } catch (x) { - ERROR(x); - } - let stopPropagation = true; - return stopPropagation; - }, - - windowState: function(gtkWidget, gdkEv, userData){ - LOG("window-state-event"); - let stopPropagation = true; - return stopPropagation; } }; // firetray.StatusIcon @@ -445,7 +259,3 @@ firetray.Handler.setText = function(text, color) { // TODO: split into smaller f return true; }; - -firetray.Handler.showHideToTray = function() { - // How to do that ?? is called in overlay.xul -}; diff --git a/src/modules/gtk2/FiretrayWindow.jsm b/src/modules/gtk2/FiretrayWindow.jsm new file mode 100644 index 0000000..9a72d6b --- /dev/null +++ b/src/modules/gtk2/FiretrayWindow.jsm @@ -0,0 +1,228 @@ +/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* GdkWindow and GtkWindow are totally different things. A GtkWindow is a + "standalone" window. A GdkWindow is just a region on the screen that can + capture events and has certain attributes (such as a cursor, and a coordinate + system). Basically a GdkWindow is an X window, in the Xlib sense, and + GtkWindow is a widget used for a particular UI effect. + (http://mail.gnome.org/archives/gtk-app-devel-list/1999-January/msg00138.html) */ + +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/gobject.jsm"); +Cu.import("resource://firetray/gdk.jsm"); +Cu.import("resource://firetray/gtk.jsm"); +Cu.import("resource://firetray/libc.jsm"); +Cu.import("resource://firetray/commons.js"); + +const Services2 = {}; +XPCOMUtils.defineLazyServiceGetter( + Services2, + "uuid", + "@mozilla.org/uuid-generator;1", + "nsIUUIDGenerator" +); + +if ("undefined" == typeof(firetray.Handler)) + ERROR("This module MUST be imported from/after FiretrayHandler !"); + +/** + * custum type used to pass data in to and out of findGtkWindowByTitleCb + */ +var _find_data_t = ctypes.StructType("_find_data_t", [ + { inTitle: ctypes.char.ptr }, + { outWindow: gtk.GtkWindow.ptr } +]); + + +firetray.Handler.registerWindow = function(win) { + let that = this; + + /* GTK TEST. */ + try { + + let gtkWin = firetray.Window.getGtkWindowHandle(win); + LOG("gtkWin="+gtkWin); + let gdkWin = gtk.gtk_widget_get_window(ctypes.cast(gtkWin, gtk.GtkWidget.ptr)); + LOG("gdkWin="+gdkWin); + /* NOTE: it should not be necessary to gtk_widget_add_events(gtkWin, + gdk.GDK_ALL_EVENTS_MASK); */ + + // register + let xid = gdk.gdk_x11_drawable_get_xid(ctypes.cast(gdkWin, gdk.GdkDrawable.ptr)); + LOG("XID="+xid); + this.windows[xid] = {}; // windows.hasOwnProperty(xid) is true, remove with: delete windows[xid] + this.windows[xid].gtkWin = gtkWin; + this.windows[xid].gdkWin = gdkWin; + + /* delete_event_cb (in gtk2/nsWindow.cpp) prevents us from catching + "delete-event" */ + let deleteEventId = gobject.g_signal_lookup("delete-event", gtk.gtk_window_get_type()); + LOG("deleteEventId="+deleteEventId); + let mozDeleteEventCb = gobject.g_signal_handler_find(gtkWin, gobject.G_SIGNAL_MATCH_ID, deleteEventId, 0, null, null, null); + LOG("mozDeleteEventCb="+mozDeleteEventCb); + gobject.g_signal_handler_block(gtkWin, mozDeleteEventCb); // not _disconnect ! + this.windows[xid].mozDeleteEventCb = mozDeleteEventCb; // FIXME: cb should be unblocked + + this.windows[xid].windowDeleteCb = gtk.GCallbackGenericEvent_t(firetray.Window.windowDelete); + res = gobject.g_signal_connect(gtkWin, "delete-event", that.windows[xid].windowDeleteCb, null); + LOG("g_connect delete-event="+res); + + /* we'll catch minimize events with Gtk: + http://stackoverflow.com/questions/8018328/what-is-the-gtk-event-called-when-a-window-minimizes */ + this.windows[xid].windowStateCb = gtk.GCallbackGenericEvent_t(firetray.Window.windowState); + res = gobject.g_signal_connect(gtkWin, "window-state-event", this.windows[xid].windowStateCb, null); + LOG("g_connect window-state-event="+res); + + } catch (x) { + ERROR(x); + return false; + } + + return true; +}; + +firetray.Handler.unregisterWindow = function(win) {}; + +firetray.Handler.showHideToTray = function(gtkStatusIcon, userData) { + LOG("showHideToTray: "+userData); + + for (let xid in firetray.Handler.windows) { + LOG(xid); + try { + gdk.gdk_window_show(firetray.Handler.windows[xid].gdkWin); + } catch (x) { + ERROR(x); + } + } + + let stopPropagation = true; + return stopPropagation; +}; + + +firetray.Window = { + + /** + * 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. + * + * @author Nils Maier (stolen from MiniTrayR) + * @param window nsIDOMWindow from Services.wm + * @return a gtk.GtkWindow.ptr + */ + getGtkWindowHandle: function(window) { + let baseWindow = window + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIBaseWindow); + + // Tag the base window + let oldTitle = baseWindow.title; + baseWindow.title = Services2.uuid.generateUUID().toString(); + + try { + // Search the window by the *temporary* title + let widgets = gtk.gtk_window_list_toplevels(); + let that = this; + let findGtkWindowByTitleCb = gobject.GFunc_t(that._findGtkWindowByTitle); + var userData = new _find_data_t( + ctypes.char.array()(baseWindow.title), + null + ).address(); + LOG("userData="+userData); + gobject.g_list_foreach(widgets, findGtkWindowByTitleCb, userData); + gobject.g_list_free(widgets); + + if (userData.contents.outWindow.isNull()) { + throw new Error("Window not found!"); + } + LOG("found window: "+userData.contents.outWindow); + } catch (x) { + ERROR(x); + } finally { + // Restore + baseWindow.title = oldTitle; + } + + return userData.contents.outWindow; + }, + + /** + * compares a GtkWindow's title with a string passed in userData + * @param gtkWidget: GtkWidget from gtk_window_list_toplevels() + * @param userData: _find_data_t + */ + _findGtkWindowByTitle: function(gtkWidget, userData) { + LOG("GTK Window: "+gtkWidget+", "+userData); + + let data = ctypes.cast(userData, _find_data_t.ptr); + let inTitle = data.contents.inTitle; + LOG("inTitle="+inTitle.readString()); + + let gtkWin = ctypes.cast(gtkWidget, gtk.GtkWindow.ptr); + let winTitle = gtk.gtk_window_get_title(gtkWin); + + try { + if (!winTitle.isNull()) { + LOG(inTitle+" = "+winTitle); + if (libc.strcmp(inTitle, winTitle) == 0) + data.contents.outWindow = gtkWin; + } + } catch (x) { + ERROR(x); + } + }, + + // FIXME: it may not be worth wrapping gtk_widget_get_window... + getGdkWindowFromGtkWindow: function(gtkWin) { + try { + let gtkWid = ctypes.cast(gtkWin, gtk.GtkWidget.ptr); + var gdkWin = gtk.gtk_widget_get_window(gtkWid); + } catch (x) { + ERROR(x); + } + return gdkWin; + }, + + getGdkWindowHandle: function(win) { + try { + let gtkWin = firetray.Window.getGtkWindowHandle(win); + LOG("FOUND: "+gtk.gtk_window_get_title(gtkWin).readString()); + let gdkWin = this.getGdkWindowFromGtkWindow(gtkWin); + if (!gdkWin.isNull()) { + LOG("has window"); + return gdkWin; + } + } catch (x) { + ERROR(x); + } + return null; + }, + + windowDelete: function(gtkWidget, gdkEv, userData){ + LOG("gtk_widget_hide: "+gtkWidget+", "+gdkEv+", "+userData); + try{ + let gdkWin = firetray.Window.getGdkWindowFromGtkWindow(gtkWidget); + gdk.gdk_window_hide(gdkWin); + } catch (x) { + ERROR(x); + } + let stopPropagation = true; + return stopPropagation; + }, + + windowState: function(gtkWidget, gdkEv, userData){ + LOG("window-state-event"); + let stopPropagation = true; + return stopPropagation; + } + +}; // firetray.Window diff --git a/src/modules/x11.jsm b/src/modules/x11.jsm index 4b26669..a21df69 100644 --- a/src/modules/x11.jsm +++ b/src/modules/x11.jsm @@ -45,11 +45,13 @@ function x11_defines(lib) { this.Atom = ctypes.unsigned_long; this.Window = ctypes.unsigned_long; this.Time = ctypes.unsigned_long; + this.XID = ctypes.unsigned_long; } else { this.CARD32 = ctypes.unsigned_long; this.Atom = this.CARD32; this.Window = this.CARD32; this.Time = this.CARD32; + this.XID = this.CARD32; } // X.h