/* -*- 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/ctypes/ctypesMap.jsm"); Cu.import("resource://firetray/ctypes/linux/gobject.jsm"); 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/commons.js"); if ("undefined" == typeof(firetray.Handler)) ERROR("This module MUST be imported from/after FiretrayHandler !"); const Services2 = {}; XPCOMUtils.defineLazyServiceGetter( Services2, "uuid", "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator" ); const FIRETRAY_XWINDOW_HIDDEN = 1 << 0; // when minimized also const FIRETRAY_XWINDOW_MAXIMIZED = 1 << 1; /** * 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 } ]); // NOTE: storing ctypes pointers into a JS object doesn't work: pointers are // "evolving" after a while (maybe due to back and forth conversion). So we // need to store them into a real ctypes array ! firetray.Handler.gtkWindows = new ctypesMap(gtk.GtkWindow.ptr), firetray.Handler.gdkWindows = new ctypesMap(gdk.GdkWindow.ptr), firetray.Handler.gtkPopupMenuWindowItems = new ctypesMap(gtk.GtkImageMenuItem.ptr), firetray.Window = { init: function() { this.initialized = true; }, shutdown: function() { firetray.Utils.tryCloseLibs([gobject, gdk, gtk, libc, x11]); this.initialized = false; }, /** * 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; LOG("oldTitle="+oldTitle); 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) { let data = ctypes.cast(userData, _find_data_t.ptr); let inTitle = data.contents.inTitle; let gtkWin = ctypes.cast(gtkWidget, gtk.GtkWindow.ptr); let winTitle = gtk.gtk_window_get_title(gtkWin); if (!winTitle.isNull()) { LOG(inTitle+" = "+winTitle); if (libc.strcmp(inTitle, winTitle) == 0) data.contents.outWindow = gtkWin; } }, getGdkWindowFromGtkWindow: function(gtkWin) { try { let gtkWid = ctypes.cast(gtkWin, gtk.GtkWidget.ptr); return gtk.gtk_widget_get_window(gtkWid); } catch (x) { ERROR(x); } return null; }, getXIDFromGdkWindow: function(gdkWin) { 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; }, /** consider using getXIDFromChromeWindow() if you only need the XID */ getWindowsFromChromeWindow: function(win) { let gtkWin = firetray.Window.getGtkWindowHandle(win); let gdkWin = firetray.Window.getGdkWindowFromGtkWindow(gtkWin); let xid = firetray.Window.getXIDFromGdkWindow(gdkWin); LOG("XID="+xid); return [gtkWin, gdkWin, xid]; }, getXIDFromChromeWindow: function(win) { for (let xid in firetray.Handler.windows) if (firetray.Handler.windows[xid].chromeWin === win) return xid; ERROR("unknown window while lookup"); return null; }, unregisterWindowByXID: function(xid) { firetray.Handler.windowsCount -= 1; if (firetray.Handler.windows[xid].visibility) firetray.Handler.visibleWindowsCount -= 1; if (firetray.Handler.windows.hasOwnProperty(xid)) { if (!delete firetray.Handler.windows[xid]) throw new DeleteError(); firetray.Handler.gtkWindows.remove(xid); firetray.Handler.gdkWindows.remove(xid); firetray.PopupMenu.removeWindowItem(xid); } else { ERROR("can't unregister unknown window "+xid); return false; } LOG("window "+xid+" unregistered"); return true; }, showSingleStateful: function(xid) { LOG("showSingleStateful 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 here instead of before firetray.Window.setVisibility(xid, true); firetray.Window.restoreDesktop(xid); // after show firetray.Window.activate(xid); firetray.PopupMenu.hideSingleWindowItemAndSeparatorMaybe(xid); firetray.Handler.showHideIcon(); }, showSingleStatelessOnce: function(xid) { LOG("showSingleStateless"); firetray.Window.setVisibility(xid, true); firetray.PopupMenu.hideSingleWindowItemAndSeparatorMaybe(xid); firetray.Handler.showHideIcon(); firetray.Handler.windows[xid].show = firetray.Window.showSingleStateful; // reset }, // NOTE: we keep using high-level cross-plat BaseWindow.visibility (instead of // gdk_window_show_unraised) /* FIXME: hiding windows should also hide child windows */ hideSingleStateful: function(xid) { LOG("hideSingleStateful"); firetray.Window.savePositionAndSize(xid); firetray.Window.saveStates(xid); firetray.Window.saveDesktop(xid); firetray.Window.setVisibility(xid, false); firetray.PopupMenu.showSingleWindowItem(xid); firetray.Handler.showHideIcon(); }, /** * hides without saving window states (position, size, ...) This is needed * when application starts hidden: as windows are not realized, their state * is not accurate. */ hideSingleStatelessOnce: function(xid) { LOG("hideSingleStateless"); firetray.Window.setVisibility(xid, false); firetray.PopupMenu.showSingleWindowItem(xid); firetray.Handler.showHideIcon(); firetray.Handler.windows[xid].hide = firetray.Window.hideSingleStateful; // reset }, 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("save: gx="+gx.value+", gy="+gy.value+", gwidth="+gwidth.value+", gheight="+gheight.value); }, restorePositionAndSize: function(xid) { if ("undefined" === typeof(firetray.Handler.windows[xid].savedX)) return; // windows[xid].saved* may not be initialized LOG("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, index, array) { delete firetray.Handler.windows[xid][element]; }); }, saveStates: function(xid) { let winStates = firetray.Window.getXWindowStates(x11.Window(xid)); firetray.Handler.windows[xid].savedStates = winStates; LOG("save: windowStates="+winStates); }, restoreStates: function(xid) { let winStates = firetray.Handler.windows[xid].savedStates; LOG("restored WindowStates: " + winStates); if (winStates & FIRETRAY_XWINDOW_MAXIMIZED) { firetray.Handler.windows[xid].chromeWin.maximize(); } let hides_on_minimize = firetray.Utils.prefService.getBoolPref('hides_on_minimize'); if (!hides_on_minimize && (winStates & FIRETRAY_XWINDOW_HIDDEN)) { firetray.Handler.windows[xid].chromeWin.minimize(); } delete firetray.Handler.windows[xid].savedStates; }, saveDesktop: function(xid) { let winDesktop = firetray.Window.getXWindowDesktop(x11.Window(xid)); firetray.Handler.windows[xid].savedDesktop = winDesktop; LOG("save: windowDesktop="+winDesktop); }, restoreDesktop: function(xid) { 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("restored to desktop: "+desktopDest); delete firetray.Handler.windows[xid].savedDesktop; }, setVisibility: function(xid, visibility) { firetray.Handler.windows[xid].baseWin.visibility = visibility; firetray.Handler.windows[xid].visibility = visibility; firetray.Handler.visibleWindowsCount = visibility ? firetray.Handler.visibleWindowsCount + 1 : firetray.Handler.visibleWindowsCount - 1 ; }, 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