2011-12-16 22:15:02 -05:00
|
|
|
/* -*- 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");
|
2011-12-31 19:45:48 -05:00
|
|
|
Cu.import("resource://firetray/ctypesMap.jsm");
|
2011-12-16 22:15:02 -05:00
|
|
|
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.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
|
2011-12-31 19:45:48 -05:00
|
|
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIWebNavigation)
|
|
|
|
.QueryInterface(Ci.nsIBaseWindow);
|
2011-12-16 22:15:02 -05:00
|
|
|
|
|
|
|
// Tag the base window
|
|
|
|
let oldTitle = baseWindow.title;
|
2011-12-22 11:19:59 -05:00
|
|
|
LOG("oldTitle="+oldTitle);
|
2011-12-16 22:15:02 -05:00
|
|
|
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) {
|
2011-12-23 10:44:09 -05:00
|
|
|
// LOG("GTK Window: "+gtkWidget+", "+userData);
|
2011-12-16 22:15:02 -05:00
|
|
|
|
|
|
|
let data = ctypes.cast(userData, _find_data_t.ptr);
|
|
|
|
let inTitle = data.contents.inTitle;
|
2011-12-23 10:44:09 -05:00
|
|
|
// LOG("inTitle="+inTitle.readString());
|
2011-12-16 22:15:02 -05:00
|
|
|
|
|
|
|
let gtkWin = ctypes.cast(gtkWidget, gtk.GtkWindow.ptr);
|
|
|
|
let winTitle = gtk.gtk_window_get_title(gtkWin);
|
|
|
|
|
2011-12-31 19:45:48 -05:00
|
|
|
if (!winTitle.isNull()) {
|
|
|
|
LOG(inTitle+" = "+winTitle);
|
|
|
|
if (libc.strcmp(inTitle, winTitle) == 0)
|
|
|
|
data.contents.outWindow = gtkWin;
|
2011-12-16 22:15:02 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
getGdkWindowFromGtkWindow: function(gtkWin) {
|
|
|
|
try {
|
|
|
|
let gtkWid = ctypes.cast(gtkWin, gtk.GtkWidget.ptr);
|
2011-12-18 13:39:56 -05:00
|
|
|
return gtk.gtk_widget_get_window(gtkWid);
|
2011-12-16 22:15:02 -05:00
|
|
|
} catch (x) {
|
|
|
|
ERROR(x);
|
|
|
|
}
|
2011-12-18 13:39:56 -05:00
|
|
|
return null;
|
2011-12-16 22:15:02 -05:00
|
|
|
},
|
|
|
|
|
2011-12-18 13:39:56 -05:00
|
|
|
getXIDFromGdkWindow: function(gdkWin) {
|
|
|
|
return gdk.gdk_x11_drawable_get_xid(ctypes.cast(gdkWin, gdk.GdkDrawable.ptr));
|
|
|
|
},
|
|
|
|
|
2011-12-22 11:19:59 -05:00
|
|
|
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;
|
|
|
|
},
|
|
|
|
|
2011-12-23 10:44:09 -05:00
|
|
|
/** consider using getXIDFromChromeWindow() if you only need the XID */
|
2011-12-18 13:39:56 -05:00
|
|
|
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];
|
2011-12-16 22:15:02 -05:00
|
|
|
},
|
|
|
|
|
2011-12-23 10:44:09 -05:00
|
|
|
getXIDFromChromeWindow: function(win) {
|
|
|
|
for (let xid in firetray.Handler.windows)
|
2012-01-01 11:30:51 -05:00
|
|
|
if (firetray.Handler.windows[xid].chromeWin === win)
|
2011-12-23 10:44:09 -05:00
|
|
|
return xid;
|
|
|
|
ERROR("unknown window while lookup");
|
|
|
|
return null;
|
|
|
|
},
|
2011-12-22 11:19:59 -05:00
|
|
|
|
2012-01-01 11:30:51 -05:00
|
|
|
saveWindowPositionAndSize: 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+", gy="+gy+", gwidth="+gwidth+", gheight="+gheight);
|
2011-12-22 11:19:59 -05:00
|
|
|
},
|
|
|
|
|
2012-01-01 11:30:51 -05:00
|
|
|
restoreWindowPositionAndSize: function(xid) {
|
2011-12-22 11:19:59 -05:00
|
|
|
if (!firetray.Handler.windows[xid].savedX)
|
|
|
|
return; // windows[xid].saved* may not be initialized
|
|
|
|
|
2011-12-31 19:45:48 -05:00
|
|
|
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);
|
2012-01-01 11:30:51 -05:00
|
|
|
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
|
|
|
|
},
|
|
|
|
|
|
|
|
saveWindowState: function(xid) {
|
|
|
|
// FIXME: windowState = STATE_MINIMIZED when we're on another virtual
|
|
|
|
// desktop... besides we may want to restore the window onto its orininal
|
|
|
|
// desktop
|
|
|
|
firetray.Handler.windows[xid].savedWindowState =
|
|
|
|
firetray.Handler.windows[xid].chromeWin.windowState;
|
|
|
|
LOG("save: windowState="+firetray.Handler.windows[xid].savedWindowState);
|
|
|
|
},
|
|
|
|
|
|
|
|
restoreWindowState: function(xid) {
|
|
|
|
switch (firetray.Handler.windows[xid].savedWindowState) {
|
|
|
|
case Ci.nsIDOMChromeWindow.STATE_MAXIMIZED: // 1
|
|
|
|
firetray.Handler.windows[xid].chromeWin.maximize();
|
|
|
|
break;
|
|
|
|
case Ci.nsIDOMChromeWindow.STATE_MINIMIZED: // 2
|
|
|
|
firetray.Handler.windows[xid].chromeWin.minimize();
|
|
|
|
break;
|
|
|
|
case Ci.nsIDOMChromeWindow.STATE_NORMAL: // 3
|
|
|
|
break;
|
|
|
|
case Ci.nsIDOMChromeWindow.STATE_FULLSCREEN: // 4
|
|
|
|
// FIXME: NOT IMPLEMENTED YET
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
LOG("restored WindowState: " + firetray.Handler.windows[xid].chromeWin.windowState);
|
2011-12-22 11:19:59 -05:00
|
|
|
},
|
|
|
|
|
2011-12-31 19:45:48 -05:00
|
|
|
onWindowState: function(gtkWidget, gdkEventState, userData){
|
2011-12-22 11:19:59 -05:00
|
|
|
// LOG("window-state-event");
|
|
|
|
// if(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED){
|
2011-12-16 22:15:02 -05:00
|
|
|
let stopPropagation = true;
|
|
|
|
return stopPropagation;
|
|
|
|
}
|
|
|
|
|
|
|
|
}; // firetray.Window
|
2011-12-31 19:45:48 -05:00
|
|
|
|
|
|
|
|
|
|
|
///////////////////////// firetray.Handler overriding /////////////////////////
|
|
|
|
|
|
|
|
// 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),
|
|
|
|
|
|
|
|
/** debug facility */
|
|
|
|
firetray.Handler.dumpWindows = function() {
|
|
|
|
LOG(firetray.Handler.windowsCount);
|
|
|
|
for (let winId in firetray.Handler.windows)
|
|
|
|
LOG(winId+"="+firetray.Handler.gtkWindows.get(winId));
|
|
|
|
};
|
|
|
|
|
|
|
|
firetray.Handler.registerWindow = function(win) {
|
|
|
|
LOG("register window");
|
|
|
|
|
|
|
|
// register
|
|
|
|
let [gtkWin, gdkWin, xid] = firetray.Window.getWindowsFromChromeWindow(win);
|
|
|
|
this.windows[xid] = {};
|
2012-01-01 11:30:51 -05:00
|
|
|
this.windows[xid].chromeWin = win;
|
|
|
|
this.windows[xid].baseWin = firetray.Handler.getWindowInterface(win, "nsIBaseWindow");
|
2012-01-01 07:30:42 -05:00
|
|
|
try {
|
|
|
|
this.gtkWindows.insert(xid, gtkWin);
|
|
|
|
this.gdkWindows.insert(xid, gdkWin);
|
|
|
|
} catch (x) {
|
|
|
|
if (x.name === "RangeError") // instanceof not working :-(
|
|
|
|
win.alert(x+"\n\nYou seem to have more than "+FIRETRAY_WINDOW_COUNT_MAX
|
|
|
|
+" windows open. This breaks FireTray and most probably "
|
|
|
|
+firetray.Handler.appNameOriginal+".");
|
|
|
|
}
|
2011-12-31 19:45:48 -05:00
|
|
|
this.windowsCount += 1;
|
2012-01-01 11:30:51 -05:00
|
|
|
// NOTE: no need to check for window state to set visibility because all
|
|
|
|
// windows *are* shown at startup
|
|
|
|
this.windows[xid].visibility = true; // this.windows[xid].baseWin.visibility always true :-(
|
2011-12-31 19:45:48 -05:00
|
|
|
this.visibleWindowsCount += 1;
|
|
|
|
LOG("window "+xid+" registered");
|
2012-01-01 11:30:51 -05:00
|
|
|
// NOTE: shouldn't be necessary to gtk_widget_add_events(gtkWin, gdk.GDK_ALL_EVENTS_MASK);
|
2011-12-31 19:45:48 -05:00
|
|
|
|
|
|
|
try {
|
2012-01-01 11:30:51 -05:00
|
|
|
// NOTE: we could try to catch the "delete-event" here and block
|
|
|
|
// delete_event_cb (in gtk2/nsWindow.cpp), but we prefer to use the
|
|
|
|
// provided 'close' JS event
|
2011-12-31 19:45:48 -05:00
|
|
|
|
|
|
|
/* we'll catch minimize events with Gtk:
|
2012-01-01 11:30:51 -05:00
|
|
|
http://stackoverflow.com/questions/8018328/what-is-the-gtk-event-called-when-a-window-minimizes */
|
2011-12-31 19:45:48 -05:00
|
|
|
this.windows[xid].onWindowStateCb = gtk.GCallbackWindowStateEvent_t(firetray.Window.onWindowState);
|
|
|
|
this.windows[xid].onWindowStateCbId = gobject.g_signal_connect(gtkWin, "window-state-event", this.windows[xid].onWindowStateCb, null);
|
|
|
|
LOG("g_connect window-state-event="+this.windows[xid].onWindowStateCbId);
|
|
|
|
|
|
|
|
} catch (x) {
|
|
|
|
this._unregisterWindowByXID(xid);
|
|
|
|
ERROR(x);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG("AFTER"); firetray.Handler.dumpWindows();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
firetray.Handler._unregisterWindowByXID = function(xid) {
|
|
|
|
this.windowsCount -= 1;
|
|
|
|
if (this.windows[xid].visibility) this.visibleWindowsCount -= 1;
|
|
|
|
if (this.windows.hasOwnProperty(xid)) {
|
|
|
|
if (!delete this.windows[xid])
|
|
|
|
throw new DeleteError();
|
|
|
|
this.gtkWindows.remove(xid);
|
|
|
|
this.gdkWindows.remove(xid);
|
|
|
|
} else {
|
|
|
|
ERROR("can't unregister unknown window "+xid);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
LOG("window "+xid+" unregistered");
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2012-01-01 11:30:51 -05:00
|
|
|
firetray.Handler.getWindowIdFromChromeWindow = firetray.Window.getXIDFromChromeWindow;
|
|
|
|
|
2011-12-31 19:45:48 -05:00
|
|
|
firetray.Handler.unregisterWindow = function(win) {
|
|
|
|
LOG("unregister window");
|
|
|
|
|
2012-01-01 11:30:51 -05:00
|
|
|
let xid = firetray.Window.getWinXIDFromChromeWindow(win);
|
|
|
|
return this._unregisterWindowByXID(xid);
|
2011-12-31 19:45:48 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
firetray.Handler.showSingleWindow = function(xid) {
|
|
|
|
LOG("show xid="+xid);
|
2012-01-01 11:30:51 -05:00
|
|
|
|
|
|
|
// try to restore previous state. TODO: z-order respected ?
|
|
|
|
firetray.Window.restoreWindowPositionAndSize(xid);
|
|
|
|
firetray.Window.restoreWindowState(xid); // no need to be saved
|
|
|
|
firetray.Handler.windows[xid].baseWin.visibility = true; // show
|
|
|
|
|
2011-12-31 19:45:48 -05:00
|
|
|
firetray.Handler.windows[xid].visibility = true;
|
|
|
|
firetray.Handler.visibleWindowsCount += 1;
|
|
|
|
};
|
|
|
|
|
2012-01-01 11:30:51 -05:00
|
|
|
// NOTE: we keep using high-level cross-plat BaseWindow.visibility (instead of
|
|
|
|
// gdk_window_show_unraised)
|
2011-12-31 19:45:48 -05:00
|
|
|
firetray.Handler.hideSingleWindow = function(xid) {
|
|
|
|
LOG("hideSingleWindow");
|
2012-01-01 11:30:51 -05:00
|
|
|
|
|
|
|
firetray.Window.saveWindowPositionAndSize(xid);
|
|
|
|
firetray.Window.saveWindowState(xid);
|
|
|
|
firetray.Handler.windows[xid].baseWin.visibility = false; // hide
|
|
|
|
|
2011-12-31 19:45:48 -05:00
|
|
|
firetray.Handler.windows[xid].visibility = false;
|
|
|
|
firetray.Handler.visibleWindowsCount -= 1;
|
|
|
|
};
|
|
|
|
|
|
|
|
firetray.Handler.showHideAllWindows = function(gtkStatusIcon, userData) {
|
|
|
|
LOG("showHideAllWindows: "+userData);
|
|
|
|
// NOTE: showHideAllWindows being a callback, we need to use
|
|
|
|
// 'firetray.Handler' explicitely instead of 'this'
|
|
|
|
|
|
|
|
LOG("visibleWindowsCount="+firetray.Handler.visibleWindowsCount);
|
|
|
|
LOG("windowsCount="+firetray.Handler.windowsCount);
|
|
|
|
let visibilityRate = firetray.Handler.visibleWindowsCount/firetray.Handler.windowsCount;
|
|
|
|
LOG("visibilityRate="+visibilityRate);
|
|
|
|
if (visibilityRate > 0.5) // TODO: should be configurable
|
|
|
|
firetray.Handler.hideAllWindows();
|
|
|
|
else
|
|
|
|
firetray.Handler.showAllWindows();
|
|
|
|
|
|
|
|
let stopPropagation = true;
|
|
|
|
return stopPropagation;
|
|
|
|
};
|