From d52245cc87e35e5eca30d4e1c77f2c16afa77ebd Mon Sep 17 00:00:00 2001 From: foudfou Date: Sun, 24 Nov 2013 14:25:27 +0100 Subject: [PATCH] * un-/register windows (begin) => refactoring * filter icon events (begin) --- src/modules/FiretrayWindow.jsm | 32 + src/modules/ctypes/winnt/win32.jsm | 29 + src/modules/linux/FiretrayWindow.jsm | 1123 +++++++++++----------- src/modules/winnt/FiretrayStatusIcon.jsm | 65 +- src/modules/winnt/FiretrayWin32.jsm | 9 - src/modules/winnt/FiretrayWindow.jsm | 90 +- 6 files changed, 711 insertions(+), 637 deletions(-) create mode 100644 src/modules/FiretrayWindow.jsm diff --git a/src/modules/FiretrayWindow.jsm b/src/modules/FiretrayWindow.jsm new file mode 100644 index 0000000..98bb1a5 --- /dev/null +++ b/src/modules/FiretrayWindow.jsm @@ -0,0 +1,32 @@ +/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// https://developer.mozilla.org/en/Code_snippets/Preferences + +var EXPORTED_SYMBOLS = [ "FiretrayWindow" ]; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import("resource://firetray/commons.js"); + +let log = firetray.Logging.getLogger("firetray.FiretrayWindow"); + +if ("undefined" == typeof(firetray.Handler)) + log.error("This module MUST be imported from/after FiretrayHandler !"); + +function FiretrayWindow () {} +FiretrayWindow.prototype = { + + updateVisibility: function(winId, visibility) { + let win = firetray.Handler.windows[winId]; + if (win.visible === visibility) + log.warn("window (winId="+winId+") was already visible="+win.visible); + + firetray.Handler.visibleWindowsCount = visibility ? + firetray.Handler.visibleWindowsCount + 1 : + firetray.Handler.visibleWindowsCount - 1 ; + + win.visible = visibility; // nsIBaseWin.visibility always true :-( + }, + +}; diff --git a/src/modules/ctypes/winnt/win32.jsm b/src/modules/ctypes/winnt/win32.jsm index 3ffac7c..790ab23 100644 --- a/src/modules/ctypes/winnt/win32.jsm +++ b/src/modules/ctypes/winnt/win32.jsm @@ -74,4 +74,33 @@ var win32 = { ERROR_INVALID_WINDOW_HANDLE: 1400, ERROR_RESOURCE_TYPE_NOT_FOUND: 1813, + // WinUser.h + WM_USER: 0x0400, + + WM_CONTEXTMENU: 0x007B, + + WM_MOUSEFIRST: 0x0200, + WM_MOUSEMOVE: 0x0200, + WM_LBUTTONDOWN: 0x0201, + WM_LBUTTONUP: 0x0202, + WM_LBUTTONDBLCLK: 0x0203, + WM_RBUTTONDOWN: 0x0204, + WM_RBUTTONUP: 0x0205, + WM_RBUTTONDBLCLK: 0x0206, + WM_MBUTTONDOWN: 0x0207, + WM_MBUTTONUP: 0x0208, + WM_MBUTTONDBLCLK: 0x0209, + WM_MOUSEWHEEL: 0x020A, + WM_XBUTTONDOWN: 0x020B, + WM_XBUTTONUP: 0x020C, + WM_XBUTTONDBLCLK: 0x020D, + WM_MOUSELAST: 0x020D, + WM_MOUSELAST: 0x020A, + }; + +// ShellAPI.h +let nin_select = win32.WM_USER + 0; +win32.NIN_SELECT = nin_select; +win32.NINF_KEY = 0x1; +win32.NIN_KEYSELECT = (win32.NIN_SELECT | win32.NINF_KEY); diff --git a/src/modules/linux/FiretrayWindow.jsm b/src/modules/linux/FiretrayWindow.jsm index 12b498c..1986776 100644 --- a/src/modules/linux/FiretrayWindow.jsm +++ b/src/modules/linux/FiretrayWindow.jsm @@ -22,6 +22,7 @@ Cu.import("resource://firetray/ctypes/linux/gdk.jsm"); Cu.import("resource://firetray/ctypes/linux/gtk.jsm"); Cu.import("resource://firetray/ctypes/linux/libc.jsm"); Cu.import("resource://firetray/ctypes/linux/x11.jsm"); +Cu.import("resource://firetray/FiretrayWindow.jsm"); Cu.import("resource://firetray/commons.js"); firetray.Handler.subscribeLibsForClosing([gobject, gdk, gtk, libc, x11, glib]); @@ -57,606 +58,594 @@ firetray.Handler.gdkWindows = new ctypesMap(gdk.GdkWindow.ptr), firetray.Handler.gtkPopupMenuWindowItems = new ctypesMap(gtk.GtkImageMenuItem.ptr), -firetray.Window = { - signals: {'focus-in': {callback: {}, handler: {}}}, +firetray.Window = new FiretrayWindow(); - init: function() { - let gtkVersionCheck = gtk.gtk_check_version( - gtk.FIRETRAY_REQUIRED_GTK_MAJOR_VERSION, - gtk.FIRETRAY_REQUIRED_GTK_MINOR_VERSION, - gtk.FIRETRAY_REQUIRED_GTK_MICRO_VERSION - ); - if (!gtkVersionCheck.isNull()) - log.error("gtk_check_version="+gtkVersionCheck.readString()); +firetray.Window.signals = {'focus-in': {callback: {}, handler: {}}}; - if (firetray.Handler.isChatEnabled()) { - Cu.import("resource://firetray/FiretrayChat.jsm"); - Cu.import("resource://firetray/linux/FiretrayChatStatusIcon.jsm"); - } +firetray.Window.init = function() { + let gtkVersionCheck = gtk.gtk_check_version( + gtk.FIRETRAY_REQUIRED_GTK_MAJOR_VERSION, + gtk.FIRETRAY_REQUIRED_GTK_MINOR_VERSION, + gtk.FIRETRAY_REQUIRED_GTK_MICRO_VERSION + ); + if (!gtkVersionCheck.isNull()) + log.error("gtk_check_version="+gtkVersionCheck.readString()); - this.initialized = true; - }, + if (firetray.Handler.isChatEnabled()) { + Cu.import("resource://firetray/FiretrayChat.jsm"); + Cu.import("resource://firetray/linux/FiretrayChatStatusIcon.jsm"); + } - shutdown: function() { - this.initialized = false; - }, + this.initialized = 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. - * - * @author Nils Maier (stolen from MiniTrayR) - * @param window nsIDOMWindow from Services.wm - * @return a gtk.GtkWindow.ptr - */ - getGtkWindowFromChromeWindow: function(window) { - let baseWindow = window - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIBaseWindow); +firetray.Window.shutdown = function() { + this.initialized = false; +}; - // Tag the base window - let oldTitle = baseWindow.title; - log.debug("oldTitle="+oldTitle); - baseWindow.title = Services2.uuid.generateUUID().toString(); +/** + * 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 + */ +firetray.Window.getGtkWindowFromChromeWindow = function(window) { + let baseWindow = window + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIBaseWindow); - 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.debug("userData="+userData); - gobject.g_list_foreach(widgets, findGtkWindowByTitleCb, userData); - gobject.g_list_free(widgets); + // Tag the base window + let oldTitle = baseWindow.title; + log.debug("oldTitle="+oldTitle); + baseWindow.title = Services2.uuid.generateUUID().toString(); - if (userData.contents.outWindow.isNull()) - throw new Error("Window not found!"); + 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.debug("userData="+userData); + gobject.g_list_foreach(widgets, findGtkWindowByTitleCb, userData); + gobject.g_list_free(widgets); - log.debug("found window: "+userData.contents.outWindow); - } catch (x) { - log.error(x); - } finally { - // Restore - baseWindow.title = oldTitle; - } + if (userData.contents.outWindow.isNull()) + throw new Error("Window not found!"); - return userData.contents.outWindow; - }, + log.debug("found window: "+userData.contents.outWindow); + } catch (x) { + log.error(x); + } finally { + // Restore + baseWindow.title = oldTitle; + } - /** - * 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) { - let data = ctypes.cast(userData, _find_data_t.ptr); - let inTitle = data.contents.inTitle; + return userData.contents.outWindow; +}; - let gtkWin = ctypes.cast(gtkWidget, gtk.GtkWindow.ptr); - let winTitle = gtk.gtk_window_get_title(gtkWin); +/** + * compares a GtkWindow's title with a string passed in userData + * @param gtkWidget: GtkWidget from gtk_window_list_toplevels() + * @param userData: _find_data_t + */ +firetray.Window._findGtkWindowByTitle = function(gtkWidget, userData) { + let data = ctypes.cast(userData, _find_data_t.ptr); + let inTitle = data.contents.inTitle; - if (!winTitle.isNull()) { - log.debug(inTitle+" = "+winTitle); - if (libc.strcmp(inTitle, winTitle) == 0) - data.contents.outWindow = gtkWin; - } - }, + let gtkWin = ctypes.cast(gtkWidget, gtk.GtkWindow.ptr); + let winTitle = gtk.gtk_window_get_title(gtkWin); - getGdkWindowFromGtkWindow: function(gtkWin) { - try { - let gtkWid = ctypes.cast(gtkWin, gtk.GtkWidget.ptr); - return gtk.gtk_widget_get_window(gtkWid); - } catch (x) { - log.error(x); - } + if (!winTitle.isNull()) { + log.debug(inTitle+" = "+winTitle); + if (libc.strcmp(inTitle, winTitle) == 0) + data.contents.outWindow = gtkWin; + } +}; + +firetray.Window.getGdkWindowFromGtkWindow = function(gtkWin) { + try { + let gtkWid = ctypes.cast(gtkWin, gtk.GtkWidget.ptr); + return gtk.gtk_widget_get_window(gtkWid); + } catch (x) { + log.error(x); + } + return null; +}; + +firetray.Window.getXIDFromGdkWindow = function(gdkWin) { + return gdk.gdk_x11_drawable_get_xid(ctypes.cast(gdkWin, gdk.GdkDrawable.ptr)); +}; + +firetray.Window.getXIDFromGtkWidget = function(gtkWid) { + let gdkWin = gtk.gtk_widget_get_window(gtkWid); + return gdk.gdk_x11_drawable_get_xid(ctypes.cast(gdkWin, gdk.GdkDrawable.ptr)); +}; + +firetray.Window.addrPointedByInHex = function(ptr) { + return "0x"+ctypes.cast(ptr, ctypes.uintptr_t.ptr).contents.toString(16); +}; + +firetray.Window.getGdkWindowFromNativeHandle = function(nativeHandle) { + let gdkw = new gdk.GdkWindow.ptr(ctypes.UInt64(nativeHandle)); // a new pointer to the GdkWindow + gdkw = gdk.gdk_window_get_toplevel(gdkw); + log.debug("gdkw="+gdkw+" *gdkw="+this.addrPointedByInHex(gdkw)); + return gdkw; +}; + +firetray.Window.getGtkWindowFromGdkWindow = function(gdkWin) { + let gptr = new gobject.gpointer; + gdk.gdk_window_get_user_data(gdkWin, gptr.address()); + log.debug("gptr="+gptr+" *gptr="+this.addrPointedByInHex(gptr)); + let gtkw = ctypes.cast(gptr, gtk.GtkWindow.ptr); + log.debug("gtkw="+gtkw+" *gtkw="+this.addrPointedByInHex(gtkw)); + return gtkw; +}; + +/* consider using getXIDFromChromeWindow() if you only need the XID */ +firetray.Window.getWindowsFromChromeWindow = function(win) { + let baseWin = firetray.Handler.getWindowInterface(win, "nsIBaseWindow"); + let nativeHandle = baseWin.nativeHandle; // Moz' private pointer to the GdkWindow + log.debug("nativeHandle="+nativeHandle); + let gtkWin, gdkWin; + if (nativeHandle) { // Gecko 17+ + gdkWin = firetray.Window.getGdkWindowFromNativeHandle(nativeHandle); + gtkWin = firetray.Window.getGtkWindowFromGdkWindow(gdkWin); + } else { + gtkWin = firetray.Window.getGtkWindowFromChromeWindow(win); + gdkWin = firetray.Window.getGdkWindowFromGtkWindow(gtkWin); + } + let xid = firetray.Window.getXIDFromGdkWindow(gdkWin); + log.debug("XID="+xid); + return [baseWin, gtkWin, gdkWin, xid]; +}; + +firetray.Window.getXIDFromChromeWindow = function(win) { + for (let xid in firetray.Handler.windows) + if (firetray.Handler.windows[xid].chromeWin === win) return xid; + log.error("unknown window while lookup"); + return null; +}; + +firetray.Window.unregisterWindowByXID = function(xid) { + if (!firetray.Handler.windows.hasOwnProperty(xid)) { + log.error("can't unregister unknown window "+xid); + return false; + } + + firetray.Window.detachOnFocusInCallback(xid); + if (firetray.Handler.isChatEnabled() && firetray.Chat.initialized) { + firetray.Chat.detachSelectListeners(firetray.Handler.windows[xid].chromeWin); + } + + if (!delete firetray.Handler.windows[xid]) + throw new DeleteError(); + firetray.Handler.gtkWindows.remove(xid); + firetray.Handler.gdkWindows.remove(xid); + firetray.Handler.windowsCount -= 1; + firetray.Handler.visibleWindowsCount -= 1; + + firetray.PopupMenu.removeWindowItem(xid); + + log.debug("window "+xid+" unregistered"); + return true; +}; + +firetray.Window.show = function(xid) { + log.debug("show xid="+xid); + + // try to restore previous state. TODO: z-order respected ? + firetray.Window.restorePositionAndSize(xid); + firetray.Window.restoreStates(xid); + + // better visual effect if visibility set after restorePosition, but some + // WMs like compiz seem not to honor position setting if window not visible + firetray.Window.setVisibility(xid, true); + + // after show + firetray.Window.restoreDesktop(xid); + if (firetray.Utils.prefService.getBoolPref('show_activates')) + firetray.Window.activate(xid); + + firetray.PopupMenu.hideWindowItemAndSeparatorMaybe(xid); + firetray.Handler.showHideIcon(); +}; + +/* FIXME: hiding windows should also hide child windows, like message windows + in Thunderbird */ +firetray.Window.hide = function(xid) { + log.debug("hide"); + + firetray.Window.savePositionAndSize(xid); + firetray.Window.saveStates(xid); + firetray.Window.saveDesktop(xid); + + firetray.Window.setVisibility(xid, false); + + firetray.PopupMenu.showWindowItem(xid); + firetray.Handler.showHideIcon(); +}; + +firetray.Window.startupHide = function(xid) { + log.debug('startupHide: '+xid); + + // also it seems cleaner, baseWin.visibility=false removes the possibility + // to restore the app by calling it from the command line. Not sure why... + firetray.Window.setVisibility(xid, false); + + firetray.PopupMenu.showWindowItem(xid); + firetray.Handler.showHideIcon(); +}; + +firetray.Window.savePositionAndSize = function(xid) { + let gx = {}, gy = {}, gwidth = {}, gheight = {}; + firetray.Handler.windows[xid].baseWin.getPositionAndSize(gx, gy, gwidth, gheight); + firetray.Handler.windows[xid].savedX = gx.value; + firetray.Handler.windows[xid].savedY = gy.value; + firetray.Handler.windows[xid].savedWidth = gwidth.value; + firetray.Handler.windows[xid].savedHeight = gheight.value; + log.debug("save: gx="+gx.value+", gy="+gy.value+", gwidth="+gwidth.value+", gheight="+gheight.value); +}; + +firetray.Window.restorePositionAndSize = function(xid) { + if ("undefined" === typeof(firetray.Handler.windows[xid].savedX)) + return; // windows[xid].saved* may not be initialized + + log.debug("restore: x="+firetray.Handler.windows[xid].savedX+", y="+firetray.Handler.windows[xid].savedY+", w="+firetray.Handler.windows[xid].savedWidth+", h="+firetray.Handler.windows[xid].savedHeight); + firetray.Handler.windows[xid].baseWin.setPositionAndSize( + firetray.Handler.windows[xid].savedX, + firetray.Handler.windows[xid].savedY, + firetray.Handler.windows[xid].savedWidth, + firetray.Handler.windows[xid].savedHeight, + false); // repaint + + ['savedX', 'savedX', 'savedWidth', 'savedHeight'].forEach(function(element) { + delete firetray.Handler.windows[xid][element]; + }); +}; + +firetray.Window.saveStates = function(xid) { + let winStates = firetray.Window.getXWindowStates(x11.Window(xid)); + firetray.Handler.windows[xid].savedStates = winStates; + log.debug("save: windowStates="+winStates); +}; + +// NOTE: fluxbox bug probably: if hidden and restored iconified, then +// switching to desktop de-iconifies it ?! +firetray.Window.restoreStates = function(xid) { + let winStates = firetray.Handler.windows[xid].savedStates; + log.debug("restored WindowStates: " + winStates); + + if (winStates & FIRETRAY_XWINDOW_HIDDEN) { + firetray.Handler.windows[xid].chromeWin.minimize(); + log.debug("restored minimized"); + } + + /* we expect the WM to actually show the window *not* minimized once + restored */ + if (firetray.Utils.prefService.getBoolPref('hides_on_minimize')) + // help prevent getting iconify event following show() + firetray.Handler.windows[xid].chromeWin.restore(); // nsIDOMChromeWindow.idl + + if (winStates & FIRETRAY_XWINDOW_MAXIMIZED) { + firetray.Handler.windows[xid].chromeWin.maximize(); + log.debug("restored maximized"); + } + + delete firetray.Handler.windows[xid].savedStates; +}; + +firetray.Window.saveDesktop = function(xid) { + if (!firetray.Utils.prefService.getBoolPref('remember_desktop')) + return; + + let winDesktop = firetray.Window.getXWindowDesktop(x11.Window(xid)); + firetray.Handler.windows[xid].savedDesktop = winDesktop; + log.debug("save: windowDesktop="+winDesktop); +}; + +firetray.Window.restoreDesktop = function(xid) { + if (!firetray.Utils.prefService.getBoolPref('remember_desktop')) + return; + + let desktopDest = firetray.Handler.windows[xid].savedDesktop; + if (desktopDest === null || "undefined" === typeof(desktopDest)) return; + + let dataSize = 1; + let data = ctypes.long(dataSize); + data[0] = desktopDest; + this.xSendClientMessgeEvent(xid, x11.current.Atoms._NET_WM_DESKTOP, data, dataSize); + + log.debug("restored to desktop: "+desktopDest); + delete firetray.Handler.windows[xid].savedDesktop; +}; + +firetray.Window.setVisibility = function(xid, visibility) { + log.debug("setVisibility="+visibility); + let gtkWidget = ctypes.cast(firetray.Handler.gtkWindows.get(xid), gtk.GtkWidget.ptr); + if (visibility) + gtk.gtk_widget_show_all(gtkWidget); + else + gtk.gtk_widget_hide(gtkWidget); + + this.updateVisibility(xid, visibility); +}; +// firetray.Window.updateVisibility inherited + +firetray.Window.xSendClientMessgeEvent = function(xid, atom, data, dataSize) { + let xev = new x11.XClientMessageEvent; + xev.type = x11.ClientMessage; + xev.window = x11.Window(xid); + xev.message_type = atom; + xev.format = 32; + for (let i=0; i