diff --git a/src/chrome/content/overlay.js b/src/chrome/content/overlay.js index 88d4c3f..28aecf0 100644 --- a/src/chrome/content/overlay.js +++ b/src/chrome/content/overlay.js @@ -16,7 +16,7 @@ if ("undefined" == typeof(firetray)) { firetray.Main = { - onLoad: function(e) { + onLoad: function(win) { // initialization code this.strings = document.getElementById("firetray-strings"); @@ -32,7 +32,7 @@ firetray.Main = { } let init = firetray.Handler.initialized || firetray.Handler.init(); - firetray.Handler.registerWindow(window); + firetray.Handler.registerWindow(win); // update unread messages count if (firetray.Handler.inMailApp && firetray.Messaging.initialized) @@ -50,12 +50,12 @@ firetray.Main = { return true; }, - onQuit: function(e) { + onQuit: function(win) { // Remove observer let that = this; firetray.Utils.prefService.removeObserver("", that); - firetray.Handler.unregisterWindow(window); + firetray.Handler.unregisterWindow(win); /* NOTE: don't firetray.Handler.initialized=false here, otherwise after a window close, a new window will create a new handler (and hence, a new @@ -64,8 +64,7 @@ firetray.Main = { }, /* GTK TEST - // TODO: prevent preceding warning about closing multiple tabs - // (browser.tabs.warnOnClose) + // TODO: prevent preceding warning about closing multiple tabs (browser.tabs.warnOnClose) onClose: function(event) { LOG('Firetray CLOSE'); let hides_on_close = firetray.Utils.prefService.getBoolPref('hides_on_close'); @@ -90,13 +89,14 @@ firetray.Main = { // https://developer.mozilla.org/en/Extensions/Performance_best_practices_in_extensions // https://developer.mozilla.org/en/XUL_School/JavaScript_Object_Management.html // https://developer.mozilla.org/en/Extensions/Performance_best_practices_in_extensions#Removing_Event_Listeners +let thatWindow = window; window.addEventListener( 'load', function (e) { removeEventListener('load', arguments.callee, true); - firetray.Main.onLoad(); }, + firetray.Main.onLoad(thatWindow); }, false); window.addEventListener( 'unload', function (e) { removeEventListener('unload', arguments.callee, true); - firetray.Main.onQuit(); }, + firetray.Main.onQuit(thatWindow); }, false); diff --git a/src/modules/FiretrayHandler.jsm b/src/modules/FiretrayHandler.jsm index d09e234..c061b4c 100644 --- a/src/modules/FiretrayHandler.jsm +++ b/src/modules/FiretrayHandler.jsm @@ -113,7 +113,7 @@ firetray.Handler = { setText: function(text, color) {}, setTooltip: function(localizedMessage) {}, setTooltipDefault: function() {}, - showHideToTray: function() {}, + showHideAllWindows: function() {}, registerWindow: function(win) {}, unregisterWindow: function(win) {}, diff --git a/src/modules/gdk.jsm b/src/modules/gdk.jsm index 4726bc9..1743f00 100644 --- a/src/modules/gdk.jsm +++ b/src/modules/gdk.jsm @@ -61,8 +61,53 @@ function gdk_defines(lib) { this.GDK_FILTER_TRANSLATE = 1; this.GDK_FILTER_REMOVE = 2; this.GdkWindowState = ctypes.int; // enum - this.GDK_WINDOW_STATE_ICONIFIED = 2; - this.GDK_WINDOW_STATE_MAXIMIZED = 4; + this.GDK_WINDOW_STATE_WITHDRAWN = 1 << 0, + this.GDK_WINDOW_STATE_ICONIFIED = 1 << 1, + this.GDK_WINDOW_STATE_MAXIMIZED = 1 << 2, + this.GDK_WINDOW_STATE_STICKY = 1 << 3, + this.GDK_WINDOW_STATE_FULLSCREEN = 1 << 4, + this.GDK_WINDOW_STATE_ABOVE = 1 << 5, + this.GDK_WINDOW_STATE_BELOW = 1 << 6; + this.GdkEventType = ctypes.int; // enum + this.GDK_NOTHING = -1; + this.GDK_DELETE = 0; + this.GDK_DESTROY = 1; + this.GDK_EXPOSE = 2; + this.GDK_MOTION_NOTIFY = 3; + this.GDK_BUTTON_PRESS = 4; + this.GDK_2BUTTON_PRESS = 5; + this.GDK_3BUTTON_PRESS = 6; + this.GDK_BUTTON_RELEASE = 7; + this.GDK_KEY_PRESS = 8; + this.GDK_KEY_RELEASE = 9; + this.GDK_ENTER_NOTIFY = 10; + this.GDK_LEAVE_NOTIFY = 11; + this.GDK_FOCUS_CHANGE = 12; + this.GDK_CONFIGURE = 13; + this.GDK_MAP = 14; + this.GDK_UNMAP = 15; + this.GDK_PROPERTY_NOTIFY = 16; + this.GDK_SELECTION_CLEAR = 17; + this.GDK_SELECTION_REQUEST = 18; + this.GDK_SELECTION_NOTIFY = 19; + this.GDK_PROXIMITY_IN = 20; + this.GDK_PROXIMITY_OUT = 21; + this.GDK_DRAG_ENTER = 22; + this.GDK_DRAG_LEAVE = 23; + this.GDK_DRAG_MOTION = 24; + this.GDK_DRAG_STATUS = 25; + this.GDK_DROP_START = 26; + this.GDK_DROP_FINISHED = 27; + this.GDK_CLIENT_EVENT = 28; + this.GDK_VISIBILITY_NOTIFY = 29; + this.GDK_NO_EXPOSE = 30; + this.GDK_SCROLL = 31; + this.GDK_WINDOW_STATE = 32; + this.GDK_SETTING = 33; + this.GDK_OWNER_CHANGE = 34; + this.GDK_GRAB_BROKEN = 35; + this.GDK_DAMAGE = 36; + this.GDK_EVENT_LAST = 37; /* helper variable for decls */ this.GdkWindow = ctypes.StructType("GdkWindow"); this.GdkByteOrder = ctypes.int; // enum @@ -124,6 +169,13 @@ function gdk_defines(lib) { this.GdkEvent = ctypes.void_t; this.GdkDisplay = ctypes.StructType("GdkDisplay"); this.GdkFilterFunc = ctypes.voidptr_t; + this.GdkEventWindowState = ctypes.StructType("GdkEventWindowState", [ + { "type": this.GdkEventType }, + { "window": this.GdkWindow.ptr }, + { "send_event": gobject.gint8 }, + { "changed_mask": this.GdkWindowState }, + { "new_window_state": this.GdkWindowState }, + ]); this.GdkFilterFunc_t = ctypes.FunctionType( ctypes.default_abi, this.GdkFilterReturn, @@ -134,7 +186,7 @@ function gdk_defines(lib) { 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); lib.lazy_bind("gdk_window_hide", ctypes.void_t, this.GdkWindow.ptr); - lib.lazy_bind("gdk_window_show", ctypes.void_t, this.GdkWindow.ptr); + lib.lazy_bind("gdk_window_show_unraised", ctypes.void_t, this.GdkWindow.ptr); lib.lazy_bind("gdk_screen_get_default", this.GdkScreen.ptr); lib.lazy_bind("gdk_screen_get_toplevel_windows", gobject.GList.ptr, this.GdkScreen.ptr); lib.lazy_bind("gdk_pixbuf_new_from_file", this.GdkPixbuf.ptr, gobject.gchar.ptr, glib.GError.ptr.ptr); @@ -169,6 +221,11 @@ function gdk_defines(lib) { lib.lazy_bind("gdk_display_get_default", this.GdkDisplay.ptr); lib.lazy_bind("gdk_x11_display_get_xdisplay", x11.Display.ptr, this.GdkDisplay.ptr); lib.lazy_bind("gdk_window_get_state", this.GdkWindowState, this.GdkWindow.ptr); + lib.lazy_bind("gdk_window_get_position", ctypes.void_t, this.GdkWindow.ptr, gobject.gint.ptr, gobject.gint.ptr); + lib.lazy_bind("gdk_drawable_get_size", ctypes.void_t, this.GdkDrawable.ptr, gobject.gint.ptr, gobject.gint.ptr); + // lib.lazy_bind("gdk_window_get_geometry", ctypes.void_t, this.GdkWindow.ptr, gobject.gint.ptr, gobject.gint.ptr, gobject.gint.ptr, gobject.gint.ptr, gobject.gint.ptr); + lib.lazy_bind("gdk_window_move_resize", ctypes.void_t, this.GdkWindow.ptr, gobject.gint, gobject.gint, gobject.gint, gobject.gint); + } if (!gdk) { diff --git a/src/modules/gobject.jsm b/src/modules/gobject.jsm index d20f9c5..0854c1a 100644 --- a/src/modules/gobject.jsm +++ b/src/modules/gobject.jsm @@ -89,6 +89,7 @@ function gobject_defines(lib) { this.guint32 = ctypes.uint32_t; this.guint16 = ctypes.uint16_t; this.gint = ctypes.int; + this.gint8 = ctypes.int8_t; this.gchar = ctypes.char; this.guchar = ctypes.unsigned_char; this.gboolean = this.gint; diff --git a/src/modules/gtk.jsm b/src/modules/gtk.jsm index 7a36814..89543f0 100644 --- a/src/modules/gtk.jsm +++ b/src/modules/gtk.jsm @@ -63,6 +63,9 @@ function gtk_defines(lib) { this.GCallbackGenericEvent_t = ctypes.FunctionType( ctypes.default_abi, gobject.gboolean, [this.GtkWidget.ptr, gdk.GdkEvent.ptr, gobject.gpointer]).ptr; + this.GCallbackWindowStateEvent_t = ctypes.FunctionType( + ctypes.default_abi, gobject.gboolean, + [this.GtkWidget.ptr, gdk.GdkEventWindowState.ptr, gobject.gpointer]).ptr; lib.lazy_bind("gtk_status_icon_new", this.GtkStatusIcon.ptr); lib.lazy_bind("gtk_status_icon_set_from_file", ctypes.void_t, @@ -110,6 +113,15 @@ function gtk_defines(lib) { lib.lazy_bind("gtk_widget_get_events", gobject.gint, this.GtkWidget.ptr); lib.lazy_bind("gtk_widget_add_events", ctypes.void_t, this.GtkWidget.ptr, gobject.gint); lib.lazy_bind("gtk_window_get_type", gobject.GType); + lib.lazy_bind("gtk_window_get_position", ctypes.void_t, this.GtkWindow.ptr, gobject.gint.ptr, gobject.gint.ptr); + lib.lazy_bind("gtk_window_move", ctypes.void_t, this.GtkWindow.ptr, gobject.gint, gobject.gint); + lib.lazy_bind("gtk_window_get_size", ctypes.void_t, this.GtkWindow.ptr, gobject.gint.ptr, gobject.gint.ptr); + lib.lazy_bind("gtk_window_resize", ctypes.void_t, this.GtkWindow.ptr, gobject.gint, gobject.gint); + lib.lazy_bind("gtk_window_iconify", gobject.gint, this.GtkWindow.ptr); + lib.lazy_bind("gtk_window_stick", gobject.gint, this.GtkWindow.ptr); + lib.lazy_bind("gtk_window_maximize", gobject.gint, this.GtkWindow.ptr); + lib.lazy_bind("gtk_window_fullscreen", gobject.gint, this.GtkWindow.ptr); + } if (!gtk) { diff --git a/src/modules/gtk2/FiretrayStatusIcon.jsm b/src/modules/gtk2/FiretrayStatusIcon.jsm index d6ae933..977aae2 100644 --- a/src/modules/gtk2/FiretrayStatusIcon.jsm +++ b/src/modules/gtk2/FiretrayStatusIcon.jsm @@ -47,8 +47,8 @@ firetray.StatusIcon = { firetray.Handler.setTooltipDefault(); - LOG("showHideToTray: "+firetray.Handler.hasOwnProperty("showHideToTray")); - firetray_iconActivateCb = gtk.GCallbackStatusIconActivate_t(firetray.Handler.showHideToTray); + LOG("showHideAllWindows: "+firetray.Handler.hasOwnProperty("showHideAllWindows")); + firetray_iconActivateCb = gtk.GCallbackStatusIconActivate_t(firetray.Handler.showHideAllWindows); let res = gobject.g_signal_connect(firetray.StatusIcon.trayIcon, "activate", firetray_iconActivateCb, null); LOG("g_connect activate="+res); diff --git a/src/modules/gtk2/FiretrayWindow.jsm b/src/modules/gtk2/FiretrayWindow.jsm index 58017b1..3cd83a9 100644 --- a/src/modules/gtk2/FiretrayWindow.jsm +++ b/src/modules/gtk2/FiretrayWindow.jsm @@ -48,11 +48,13 @@ firetray.Handler.registerWindow = function(win) { // register let [gtkWin, gdkWin, xid] = firetray.Window.getWindowsFromChromeWindow(win); - /* NOTE: it should not be necessary to gtk_widget_add_events(gtkWin, - gdk.GDK_ALL_EVENTS_MASK); */ - this.windows[xid] = {}; // windows.hasOwnProperty(xid) is true, remove with: delete windows[xid] + this.windows[xid] = {}; + this.windows[xid].win = win; this.windows[xid].gtkWin = gtkWin; this.windows[xid].gdkWin = gdkWin; + LOG("window "+xid+" registered"); + /* NOTE: it should not be necessary to gtk_widget_add_events(gtkWin, + gdk.GDK_ALL_EVENTS_MASK); */ try { @@ -60,20 +62,21 @@ firetray.Handler.registerWindow = function(win) { "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 + let mozDeleteEventCbId = gobject.g_signal_handler_find(gtkWin, gobject.G_SIGNAL_MATCH_ID, deleteEventId, 0, null, null, null); + LOG("mozDeleteEventCbId="+mozDeleteEventCbId); + gobject.g_signal_handler_block(gtkWin, mozDeleteEventCbId); // not _disconnect ! + this.windows[xid].mozDeleteEventCbId = mozDeleteEventCbId; 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); + // NOTE: it'd be nice to pass the xid to g_signal_connect... + this.windows[xid].windowDeleteCbId = gobject.g_signal_connect(gtkWin, "delete-event", that.windows[xid].windowDeleteCb, null); + LOG("g_connect delete-event="+this.windows[xid].windowDeleteCbId); /* 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); + this.windows[xid].windowStateCb = gtk.GCallbackWindowStateEvent_t(firetray.Window.windowState); + this.windows[xid].windowStateCbId = gobject.g_signal_connect(gtkWin, "window-state-event", this.windows[xid].windowStateCb, null); + LOG("g_connect window-state-event="+this.windows[xid].windowStateCbId); } catch (x) { this._unregisterWindowByXID(xid); @@ -87,30 +90,55 @@ firetray.Handler.registerWindow = function(win) { firetray.Handler.unregisterWindow = function(win) { LOG("unregister window"); - let [gtkWin, gdkWin, xid] = firetray.Window.getWindowsFromChromeWindow(win); - return this._unregisterWindowByXID(xid); + try { + let [gtkWin, gdkWin, xid] = firetray.Window.getWindowsFromChromeWindow(win); + + // unblock Moz original delete-event handler + gobject.g_signal_handler_disconnect(gtkWin, this.windows[xid].windowDeleteCbId); + gobject.g_signal_handler_unblock(gtkWin, this.windows[xid].mozDeleteEventCbId); + + return this._unregisterWindowByXID(xid); + } catch (x) { + ERROR(x); + } + return null; }; firetray.Handler._unregisterWindowByXID = function(xid) { + try { if (this.windows.hasOwnProperty(xid)) delete this.windows[xid]; else { ERROR("can't unregister unknown window "+xid); return false; } - return true; -}; - -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); } + LOG("window "+xid+" unregistered"); + return true; +}; + +firetray.Handler.showSingleWindow = function(xid) { + try { + // keep z-order - and try to restore previous state + LOG("gdkWin="+firetray.Handler.windows[xid].gdkWin); + gdk.gdk_window_show_unraised(firetray.Handler.windows[xid].gdkWin); + // need to restore *after* showing for correction + // firetray.Window._restoreWindowPositionSizeState(xid); + } catch (x) { + ERROR(x); + } +}; + +firetray.Handler.showHideAllWindows = function(gtkStatusIcon, userData) { + LOG("showHideAllWindows: "+userData); + + // NOTE: showHideAllWindows being a callback, we need to use 'firetray.Handler' + // explicitely instead of 'this' + for (let xid in firetray.Handler.windows) { + LOG("show xid="+xid); + firetray.Handler.showSingleWindow(xid); } let stopPropagation = true; @@ -136,6 +164,7 @@ firetray.Window = { // Tag the base window let oldTitle = baseWindow.title; + LOG("oldTitle="+oldTitle); baseWindow.title = Services2.uuid.generateUUID().toString(); try { @@ -205,6 +234,16 @@ firetray.Window = { return gdk.gdk_x11_drawable_get_xid(ctypes.cast(gdkWin, gdk.GdkDrawable.ptr)); }, + getXIDFromGtkWidget: function(gtkWid) { + try { + let gdkWin = gtk.gtk_widget_get_window(gtkWid); + return gdk.gdk_x11_drawable_get_xid(ctypes.cast(gdkWin, gdk.GdkDrawable.ptr)); + } catch (x) { + ERROR(x); + } + return null; + }, + getWindowsFromChromeWindow: function(win) { let gtkWin = firetray.Window.getGtkWindowHandle(win); LOG("gtkWin="+gtkWin); @@ -217,18 +256,68 @@ firetray.Window = { windowDelete: function(gtkWidget, gdkEv, userData){ LOG("gtk_widget_hide: "+gtkWidget+", "+gdkEv+", "+userData); - try{ + let xid = firetray.Window.getXIDFromGtkWidget(gtkWidget); + LOG("windowDelete XID="+xid); + + try { + firetray.Window._saveWindowPositionSizeState(xid); + + // hide window - NOTE: we don't use BaseWindow.visibility to have full + // control 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"); + _saveWindowPositionSizeState: function(xid) { + let gdkWin = firetray.Handler.windows[xid].gdkWin; + + try { + let gx = new gobject.gint; let gy = new gobject.gint; + // gtk.gtk_window_get_position(gtkWin, gx.address(), gy.address()); + gdk.gdk_window_get_position(gdkWin, gx.address(), gy.address()); + let gwidth = new gobject.gint; let gheight = new gobject.gint; + // gtk.gtk_window_get_size(gtkWin, gwidth.address(), gheight.address()); + gdk.gdk_drawable_get_size(ctypes.cast(gdkWin, gdk.GdkDrawable.ptr), gwidth.address(), gheight.address()); + let windowState = gdk.gdk_window_get_state(firetray.Handler.windows[xid].gdkWin); + LOG("gx="+gx+", gy="+gy+", gwidth="+gwidth+", gheight="+gheight+", windowState="+windowState); + firetray.Handler.windows[xid].savedX = gx; + firetray.Handler.windows[xid].savedY = gy; + firetray.Handler.windows[xid].savedWidth = gwidth; + firetray.Handler.windows[xid].savedHeight = gheight; + firetray.Handler.windows[xid].savedState = windowState; + } catch (x) { + ERROR(x); + } + + }, + + _restoreWindowPositionSizeState: function(xid) { + let gdkWin = firetray.Handler.windows[xid].gdkWin; + if (!firetray.Handler.windows[xid].savedX) + return; // windows[xid].saved* may not be initialized + + LOG("restore gdkWin: "+gdkWin+", x="+firetray.Handler.windows[xid].savedX+", y="+firetray.Handler.windows[xid].savedY+", w="+firetray.Handler.windows[xid].savedWidth+", h="+firetray.Handler.windows[xid].savedHeight); + try { + gdk.gdk_window_move_resize(gdkWin, + firetray.Handler.windows[xid].savedX, + firetray.Handler.windows[xid].savedY, + firetray.Handler.windows[xid].savedWidth, + firetray.Handler.windows[xid].savedHeight); + // firetray.Handler.windows[xid].savedState + } catch (x) { + ERROR(x); + } + }, + + windowState: function(gtkWidget, gdkEventState, userData){ + // LOG("window-state-event"); + // if(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED){ let stopPropagation = true; return stopPropagation; }