1
0
mirror of https://github.com/moparisthebest/FireTray synced 2024-08-13 15:53:47 -04:00

* complete per-window registration and show/hide handeling

* fix storage of Gdk-/GtkWindows pointers (ctypesMap.jsm)
This commit is contained in:
foudfou 2012-01-01 01:45:48 +01:00
parent 6b4fd546d2
commit aadf727816
7 changed files with 272 additions and 149 deletions

View File

@ -24,7 +24,10 @@ var firetrayChrome = {
return false;
}
LOG("Handler initialized: "+firetray.Handler.initialized);
let init = firetray.Handler.initialized || firetray.Handler.init();
LOG("ONLOAD"); firetray.Handler.dumpWindows();
firetray.Handler.registerWindow(win);
// update unread messages count
@ -55,15 +58,19 @@ var firetrayChrome = {
// TODO: prevent preceding warning about closing multiple tabs (browser.tabs.warnOnClose)
onClose: function(event) {
LOG('Firetray CLOSE');
let win = event.originalTarget;
if (!win instanceof ChromeWindow)
throw new TypeError('originalTarget not a ChromeWindow');
let hides_on_close = firetray.Utils.prefService.getBoolPref('hides_on_close');
let hides_single_window = firetray.Utils.prefService.getBoolPref('hides_single_window');
LOG('hides_on_close: '+hides_on_close+', hides_single_window='+hides_single_window);
LOG('event.originalTarget: '+event.originalTarget);
if (hides_on_close) {
if (hides_single_window)
firetray.Window.hideWindow(window);
else
firetray.Handler.hideAllWindows(window);
if (hides_single_window) {
let winId = firetray.Handler.getWindowIdFromChromeWindow(win);
firetray.Handler.hideSingleWindow(winId);
} else
firetray.Handler.hideAllWindows();
event && event.preventDefault(); // no event when called directly (xul)
}
},

View File

@ -5,7 +5,7 @@
<em:unpack>true</em:unpack> <!-- needed for embedded icons -->
<em:type>2</em:type>
<em:name>FireTray</em:name>
<em:version>0.4.0a5</em:version>
<em:version>0.4.0a6</em:version>
<em:creator>Hua Luo, Francesco Solero, Foudil BRÉTEL</em:creator>
<em:contributor>Hua Luo, Francesco Solero (Firetray original authors)</em:contributor>
<em:homepageURL>https://github.com/foudfou/firetray</em:homepageURL>

View File

@ -20,7 +20,7 @@ if ("undefined" == typeof(firetray)) {
};
/**
* Singleton object and abstraction for tray icon management.
* Singleton object and abstraction for windows and tray icon management.
*/
// NOTE: modules work outside of the window scope. Unlike scripts in the
// chrome, modules don't have access to objects such as window, document, or
@ -35,7 +35,9 @@ firetray.Handler = {
FILENAME_NEWMAIL: null,
runtimeOS: null,
inMailApp: false,
windows: [],
windows: {},
windowsCount: 0,
visibleWindowsCount: 0,
init: function() { // does creates icon
this.appName = Services.appinfo.name.toLowerCase();
@ -58,17 +60,16 @@ firetray.Handler = {
LOG('FiretrayStatusIcon imported');
Cu.import("resource://firetray/gtk2/FiretrayWindow.jsm");
LOG('FiretrayWindow imported');
// instanciate tray icon
firetray.StatusIcon.init();
LOG('StatusIcon initialized');
break;
default:
ERROR("FIRETRAY: only Linux platform supported at this time. Firetray not loaded");
return false;
}
// instanciate tray icon
firetray.StatusIcon.init();
LOG('StatusIcon initialized');
// check if in mail app
var mozAppId = Services.appinfo.ID;
if (mozAppId === THUNDERBIRD_ID || mozAppId === SEAMONKEY_ID) {
@ -107,15 +108,30 @@ firetray.Handler = {
return true;
},
// these get overridden in OS-specific Icon handlers
// these get overridden in OS-specific Window handlers
setImage: function(filename) {},
setImageDefault: function() {},
setText: function(text, color) {},
setTooltip: function(localizedMessage) {},
setTooltipDefault: function() {},
showHideAllWindows: function() {},
registerWindow: function(win) {},
unregisterWindow: function(win) {},
getWindowIdFromChromeWindow: function(win) {},
hideSingleWindow: function(winId) {},
showSingleWindow: function(winId) {},
showHideAllWindows: function() {},
showAllWindows: function() {
for (let winId in firetray.Handler.windows)
if (!firetray.Handler.windows[winId].visibility)
firetray.Handler.showSingleWindow(winId);
},
hideAllWindows: function() {
for (let winId in firetray.Handler.windows) {
if (firetray.Handler.windows[winId].visibility)
firetray.Handler.hideSingleWindow(winId);
}
},
_getBaseOrXULWindowFromDOMWindow: function(win, winType) {
let winInterface, winOut;

View File

@ -198,3 +198,11 @@ function isEmpty(obj) {
function strEquals(obj1, obj2) {
return obj1.toString() === obj2.toString();
}
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Custom_Error_Types
function DeleteError(message) {
this.name = "DeleteError";
this.message = message || "Could not delete object memeber";
}
DeleteError.prototype = new Error();
DeleteError.prototype.constructor = DeleteError;

68
src/modules/ctypesMap.jsm Normal file
View File

@ -0,0 +1,68 @@
/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
var EXPORTED_SYMBOLS = [ "ctypesMap", "CTYPES_ARRAY_MAX_SIZE" ];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/ctypes.jsm");
Cu.import("resource://firetray/logging.jsm");
Cu.import("resource://firetray/commons.js");
const FIRETRAY_WINDOW_COUNT_MAX = 64;
/**
* basic Hash mapping a key (of any type) to a cell in a ctypes array
*/
function ctypesMap(t) {
this.array = ctypes.ArrayType(t)(FIRETRAY_WINDOW_COUNT_MAX);
this.indexLast = -1;
this.freedCells = []; // indices of freed cells
this.count = 0; // count of actually stored things
this.map = {}; // map key -> index
};
ctypesMap.prototype.get = function(key) {
if (!this.map.hasOwnProperty(key))
throw new RangeError('Unknown key: '+key);
return this.array[this.map[key]];
};
ctypesMap.prototype.insert = function(key, item) {
if (this.map.hasOwnProperty(key)) { // replace
LOG("REPLACE");
this.array[this.map[key]] = item;
} else if (this.freedCells.length) {
LOG("USE FREE CELL");
let idx = this.freedCells.shift();
this.array[idx] = item;
this.map[key] = idx;
this.count += 1;
} else {
let indexNext = this.indexLast + 1;
if (indexNext >= FIRETRAY_WINDOW_COUNT_MAX)
throw new RangeError('Array overflow');
this.indexLast = indexNext;
this.array[this.indexLast] = item;
this.map[key] = this.indexLast;
this.count += 1;
}
};
ctypesMap.prototype.remove = function(key) {
if (!this.map.hasOwnProperty(key))
throw new RangeError('Unknown key: '+key);
LOG("FREE CELL");
let idx = this.map[key];
if (!delete this.map[key])
throw new DeleteError();
this.freedCells.unshift(idx);
this.count -= 1;
};

View File

@ -112,6 +112,7 @@ firetray.StatusIcon = {
}; // firetray.StatusIcon
firetray.Handler.setImage = function(filename) {
if (!firetray.StatusIcon.trayIcon)
return false;

View File

@ -16,6 +16,7 @@ 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/ctypesMap.jsm");
Cu.import("resource://firetray/gobject.jsm");
Cu.import("resource://firetray/gdk.jsm");
Cu.import("resource://firetray/gtk.jsm");
@ -42,95 +43,6 @@ var _find_data_t = ctypes.StructType("_find_data_t", [
]);
firetray.Handler.registerWindow = function(win) {
LOG("register window");
let that = this;
// register
let [gtkWin, gdkWin, xid] = firetray.Window.getWindowsFromChromeWindow(win);
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 {
/* 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 */
/* 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.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);
ERROR(x);
return false;
}
return true;
};
firetray.Handler.unregisterWindow = function(win) {
LOG("unregister window");
try {
let xid = firetray.Window.getXIDFromChromeWindow(win);
return this._unregisterWindowByXID(xid);
} catch (x) {
ERROR(x);
}
return false;
};
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;
}
} catch (x) {
ERROR(x);
return false;
}
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;
return stopPropagation;
};
firetray.Window = {
/**
@ -143,9 +55,9 @@ firetray.Window = {
*/
getGtkWindowHandle: function(window) {
let baseWindow = window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIBaseWindow);
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIBaseWindow);
// Tag the base window
let oldTitle = baseWindow.title;
@ -194,14 +106,10 @@ firetray.Window = {
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);
if (!winTitle.isNull()) {
LOG(inTitle+" = "+winTitle);
if (libc.strcmp(inTitle, winTitle) == 0)
data.contents.outWindow = gtkWin;
}
},
@ -232,9 +140,7 @@ firetray.Window = {
/** consider using getXIDFromChromeWindow() if you only need the XID */
getWindowsFromChromeWindow: function(win) {
let gtkWin = firetray.Window.getGtkWindowHandle(win);
LOG("gtkWin="+gtkWin);
let gdkWin = firetray.Window.getGdkWindowFromGtkWindow(gtkWin);
LOG("gdkWin="+gdkWin);
let xid = firetray.Window.getXIDFromGdkWindow(gdkWin);
LOG("XID="+xid);
return [gtkWin, gdkWin, xid];
@ -248,33 +154,17 @@ firetray.Window = {
return null;
},
hideWindow: function(win) {
LOG("hideWindow");
let xid = this.getXIDFromChromeWindow(win);
LOG("found xid="+xid);
try {
firetray.Window._saveWindowPositionSizeState(xid);
// hide window - NOTE: we don't use BaseWindow.visibility to have full
// control
gdk.gdk_window_hide(firetray.Handler.windows[xid].gdkWin);
} catch (x) {
ERROR(x);
}
},
_saveWindowPositionSizeState: function(xid) {
let gdkWin = firetray.Handler.windows[xid].gdkWin;
saveWindowPositionSizeState: function(xid) {
let gtkWin = firetray.Handler.gtkWindows.get(xid);
let gdkWin = firetray.Handler.gdkWindows.get(xid);
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());
gtk.gtk_window_get_position(gtkWin, 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);
gtk.gtk_window_get_size(gtkWin, gwidth.address(), gheight.address());
let windowState = gdk.gdk_window_get_state(gdkWin);
LOG("save: 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;
@ -286,25 +176,29 @@ firetray.Window = {
},
_restoreWindowPositionSizeState: function(xid) {
let gdkWin = firetray.Handler.windows[xid].gdkWin;
restoreWindowPositionSizeState: function(xid) {
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);
let gtkWin = firetray.Handler.gtkWindows.get(xid);
let gdkWin = firetray.Handler.gdkWindows.get(xid);
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);
// NOTE: unfortunately, this is the best way I found *inside GTK* to
// restore position and size: gdk.gdk_window_move_resize doesn't work
// well. And unfortunately, we need to show the window before restoring
// position and size :-( TODO: Might be worth trying with x11 or
// BaseWindow.visibility ?
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);
gtk.gtk_window_move(gtkWin, firetray.Handler.windows[xid].savedX, firetray.Handler.windows[xid].savedY);
gtk.gtk_window_resize(gtkWin, firetray.Handler.windows[xid].savedWidth, firetray.Handler.windows[xid].savedHeight);
// firetray.Handler.windows[xid].savedState
} catch (x) {
ERROR(x);
}
},
windowState: function(gtkWidget, gdkEventState, userData){
onWindowState: function(gtkWidget, gdkEventState, userData){
// LOG("window-state-event");
// if(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED){
let stopPropagation = true;
@ -312,3 +206,132 @@ firetray.Window = {
}
}; // firetray.Window
///////////////////////// 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] = {};
this.windows[xid].win = win;
this.gtkWindows.insert(xid, gtkWin);
this.gdkWindows.insert(xid, gdkWin);
this.windowsCount += 1;
this.visibleWindowsCount += 1;
this.windows[xid].visibility = true;
LOG("window "+xid+" registered");
/* NOTE: it should not be necessary to gtk_widget_add_events(gtkWin,
gdk.GDK_ALL_EVENTS_MASK); */
try {
/* 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 */
/* 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].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;
};
firetray.Handler.unregisterWindow = function(win) {
LOG("unregister window");
try {
let xid = firetray.Window.getXIDFromChromeWindow(win);
return this._unregisterWindowByXID(xid);
} catch (x) {
ERROR(x);
}
return false;
};
firetray.Handler.getWindowIdFromChromeWindow = firetray.Window.getXIDFromChromeWindow;
firetray.Handler.showSingleWindow = function(xid) {
LOG("show xid="+xid);
try {
// try to restore previous state. TODO: z-order respected ?
gdk.gdk_window_show_unraised(firetray.Handler.gdkWindows.get(xid));
// need to restore *after* showing for correctness
firetray.Window.restoreWindowPositionSizeState(xid);
} catch (x) {
ERROR(x);
}
firetray.Handler.windows[xid].visibility = true;
firetray.Handler.visibleWindowsCount += 1;
};
firetray.Handler.hideSingleWindow = function(xid) {
LOG("hideSingleWindow");
try {
firetray.Window.saveWindowPositionSizeState(xid);
// NOTE: we don't use BaseWindow.visibility to have full control
gdk.gdk_window_hide(firetray.Handler.gdkWindows.get(xid));
} catch (x) {
ERROR(x);
}
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;
};