[Pkg-mozext-commits] [firetray] 19/38: FreeBSD

David Prévot taffit at moszumanska.debian.org
Mon Apr 6 15:58:21 UTC 2015


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository firetray.

commit 6b6e1753ef5fa9c3894097e7c9596fd3c35c9f3b
Author: Scott Furry <scott.wl.furry at gmail.com>
Date:   Sun Mar 1 19:08:54 2015 -0700

    FreeBSD
---
 src/Makefile                                       |   5 +-
 src/chrome/skin/icons/freebsd                      |   1 -
 .../icons/freebsd/hicolor/22x22/apps/chatzilla.png |   1 +
 .../icons/freebsd/hicolor/22x22/apps/firefox.png   |   1 +
 .../icons/freebsd/hicolor/22x22/apps/seamonkey.png |   1 +
 .../freebsd/hicolor/22x22/apps/thunderbird.png     |   1 +
 .../icons/freebsd/hicolor/22x22/apps/zotero.png    |   1 +
 .../freebsd/hicolor/22x22/status/mail-unread.png   |   1 +
 .../hicolor/22x22/status/user-available.png        |   1 +
 .../freebsd/hicolor/22x22/status/user-away.png     |   1 +
 .../freebsd/hicolor/22x22/status/user-busy.png     |   1 +
 .../freebsd/hicolor/22x22/status/user-offline.png  |   1 +
 src/lib/freebsd                                    |   1 -
 src/lib/freebsd/Makefile                           |  41 ++
 src/lib/freebsd/firetray.c                         |  20 +
 src/lib/freebsd/firetray.h                         |  11 +
 src/modules/ctypes/freebsd                         |   1 -
 src/modules/ctypes/freebsd/appindicator.jsm        |  58 ++
 src/modules/ctypes/freebsd/cairo.jsm               |  26 +
 src/modules/ctypes/freebsd/gdk.jsm                 | 333 +++++++++
 src/modules/ctypes/freebsd/gio.jsm                 |  46 ++
 src/modules/ctypes/freebsd/glib.jsm                |  27 +
 src/modules/ctypes/freebsd/gobject.jsm             | 140 ++++
 src/modules/ctypes/freebsd/gtk.jsm                 | 174 +++++
 src/modules/ctypes/freebsd/libc.jsm                |  31 +
 src/modules/ctypes/freebsd/pango.jsm               |  47 ++
 src/modules/ctypes/freebsd/pangocairo.jsm          |  22 +
 src/modules/ctypes/freebsd/x11.jsm                 | 255 +++++++
 src/modules/freebsd                                |   1 -
 src/modules/freebsd/FiretrayAppIndicator.jsm       | 157 +++++
 src/modules/freebsd/FiretrayChat.jsm               | 332 +++++++++
 src/modules/freebsd/FiretrayChatStatusIcon.jsm     | 301 ++++++++
 src/modules/freebsd/FiretrayGtkIcons.jsm           |  67 ++
 src/modules/freebsd/FiretrayGtkStatusIcon.jsm      | 341 +++++++++
 src/modules/freebsd/FiretrayPopupMenu.jsm          | 262 +++++++
 src/modules/freebsd/FiretrayStatusIcon.jsm         | 219 ++++++
 src/modules/freebsd/FiretrayWindow.jsm             | 761 +++++++++++++++++++++
 37 files changed, 3685 insertions(+), 5 deletions(-)

diff --git a/src/Makefile b/src/Makefile
index eaa7f7d..bb30a86 100755
--- a/src/Makefile
+++ b/src/Makefile
@@ -96,7 +96,8 @@ chrome_sources := $(chrome_sources_js)								\
                $(wildcard $(chrome_source_root)/skin/icons/*.gif)				\
                $(wildcard $(chrome_source_root)/skin/icons/*.png)				\
                $(wildcard $(chrome_source_root)/skin/icons/*.svg)				\
-               $(wildcard $(chrome_source_root)/skin/icons/linux/hicolor/22x22/*/*.png)		\
+               $(wildcard $(chrome_source_root)/skin/icons/freebsd/hicolor/22x22/*/*.png) \
+               $(wildcard $(chrome_source_root)/skin/icons/linux/hicolor/22x22/*/*.png) \
                $(wildcard $(chrome_source_root)/skin/icons/winnt/*.bmp)				\
                $(wildcard $(chrome_source_root)/skin/icons/winnt/*.ico)				\
                $(wildcard $(chrome_source_root)/locale/*/*.dtd)					\
@@ -109,8 +110,10 @@ modules_dir := modules
 modules_sources := $(wildcard $(modules_dir)/*.js)		\
 		$(wildcard $(modules_dir)/*.jsm)		\
 		$(wildcard $(modules_dir)/ctypes/*.jsm)		\
+		$(wildcard $(modules_dir)/ctypes/freebsd/*.jsm)	\
 		$(wildcard $(modules_dir)/ctypes/linux/*.jsm)	\
 		$(wildcard $(modules_dir)/ctypes/winnt/*.jsm)	\
+		$(wildcard $(modules_dir)/freebsd/*.jsm)	\
 		$(wildcard $(modules_dir)/linux/*.jsm)		\
 		$(wildcard $(modules_dir)/winnt/*.jsm)
 
diff --git a/src/chrome/skin/icons/freebsd b/src/chrome/skin/icons/freebsd
deleted file mode 120000
index 24905c6..0000000
--- a/src/chrome/skin/icons/freebsd
+++ /dev/null
@@ -1 +0,0 @@
-./src/chrome/skin/icons/linux
\ No newline at end of file
diff --git a/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/chatzilla.png b/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/chatzilla.png
new file mode 120000
index 0000000..0bf53df
--- /dev/null
+++ b/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/chatzilla.png
@@ -0,0 +1 @@
+../../../../img/chatzilla22.png
\ No newline at end of file
diff --git a/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/firefox.png b/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/firefox.png
new file mode 120000
index 0000000..be7eea5
--- /dev/null
+++ b/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/firefox.png
@@ -0,0 +1 @@
+../../../../img/firefox22.png
\ No newline at end of file
diff --git a/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/seamonkey.png b/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/seamonkey.png
new file mode 120000
index 0000000..25c1150
--- /dev/null
+++ b/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/seamonkey.png
@@ -0,0 +1 @@
+../../../../img/seamonkey22.png
\ No newline at end of file
diff --git a/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/thunderbird.png b/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/thunderbird.png
new file mode 120000
index 0000000..c7733f5
--- /dev/null
+++ b/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/thunderbird.png
@@ -0,0 +1 @@
+../../../../img/thunderbird22.png
\ No newline at end of file
diff --git a/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/zotero.png b/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/zotero.png
new file mode 120000
index 0000000..ed6861f
--- /dev/null
+++ b/src/chrome/skin/icons/freebsd/hicolor/22x22/apps/zotero.png
@@ -0,0 +1 @@
+../../../../img/zotero22.png
\ No newline at end of file
diff --git a/src/chrome/skin/icons/freebsd/hicolor/22x22/status/mail-unread.png b/src/chrome/skin/icons/freebsd/hicolor/22x22/status/mail-unread.png
new file mode 120000
index 0000000..50c5ecb
--- /dev/null
+++ b/src/chrome/skin/icons/freebsd/hicolor/22x22/status/mail-unread.png
@@ -0,0 +1 @@
+../../../../img/mail-unread.png
\ No newline at end of file
diff --git a/src/chrome/skin/icons/freebsd/hicolor/22x22/status/user-available.png b/src/chrome/skin/icons/freebsd/hicolor/22x22/status/user-available.png
new file mode 120000
index 0000000..4a9d15a
--- /dev/null
+++ b/src/chrome/skin/icons/freebsd/hicolor/22x22/status/user-available.png
@@ -0,0 +1 @@
+../../../../img/pidgin-tray-available.png
\ No newline at end of file
diff --git a/src/chrome/skin/icons/freebsd/hicolor/22x22/status/user-away.png b/src/chrome/skin/icons/freebsd/hicolor/22x22/status/user-away.png
new file mode 120000
index 0000000..00139c3
--- /dev/null
+++ b/src/chrome/skin/icons/freebsd/hicolor/22x22/status/user-away.png
@@ -0,0 +1 @@
+../../../../img/pidgin-tray-away.png
\ No newline at end of file
diff --git a/src/chrome/skin/icons/freebsd/hicolor/22x22/status/user-busy.png b/src/chrome/skin/icons/freebsd/hicolor/22x22/status/user-busy.png
new file mode 120000
index 0000000..2fa96b0
--- /dev/null
+++ b/src/chrome/skin/icons/freebsd/hicolor/22x22/status/user-busy.png
@@ -0,0 +1 @@
+../../../../img/pidgin-tray-busy.png
\ No newline at end of file
diff --git a/src/chrome/skin/icons/freebsd/hicolor/22x22/status/user-offline.png b/src/chrome/skin/icons/freebsd/hicolor/22x22/status/user-offline.png
new file mode 120000
index 0000000..55463da
--- /dev/null
+++ b/src/chrome/skin/icons/freebsd/hicolor/22x22/status/user-offline.png
@@ -0,0 +1 @@
+../../../../img/pidgin-tray-offline.png
\ No newline at end of file
diff --git a/src/lib/freebsd b/src/lib/freebsd
deleted file mode 120000
index cd6c4a0..0000000
--- a/src/lib/freebsd
+++ /dev/null
@@ -1 +0,0 @@
-./src/lib/linux
\ No newline at end of file
diff --git a/src/lib/freebsd/Makefile b/src/lib/freebsd/Makefile
new file mode 100644
index 0000000..864147f
--- /dev/null
+++ b/src/lib/freebsd/Makefile
@@ -0,0 +1,41 @@
+deps = gdk-2.0 gdk-pixbuf-2.0 atk
+platform = $(shell uname -p)
+
+CC = gcc
+GCCVERSION = $(shell gcc -dumpversion | cut -f1 -d.)
+CFLAGS += -O3 -fPIC -g -mtune=generic $(shell pkg-config --cflags $(deps))
+LDFLAGS += -shared -rdynamic
+#-Wl,--version-script -Wl,export-versionscript
+#-Wl,-soname,libmystuff.so.1 -o libgnome.so.1.0.1
+LIBS += $(shell pkg-config --libs $(deps))
+
+libs = firetray_$(platform)-gcc$(GCCVERSION).so
+
+all: $(libs)
+	@echo
+	@echo add this line to chrome.manifest:
+	@echo "resource	firetray-lib			lib/linux/$(libs)	abi=Linux_x86_64-gcc3"
+	@echo
+	@echo and use
+	@echo 'Cu.import("resource://firetray/ctypes/libfiretray.jsm");'
+	@echo 'firetray.Handler.subscribeLibsForClosing([libfiretray]);'
+	@echo
+
+$(libs): firetray.o
+	$(CC) $(CFLAGS) $(LDFLAGS) $< $(LIBS) -o $@
+
+%.o: %.c
+	$(CC) $(CFLAGS) -c -o $@ $<
+
+strip: $(libs)
+	strip $<
+
+clean:
+	rm -rf $(libs) *.o
+
+cp:
+	cp $(libs) ../../lib/
+
+install: clean all strip cp
+
+.PHONY: all clean strip cp install
diff --git a/src/lib/freebsd/firetray.c b/src/lib/freebsd/firetray.c
new file mode 100644
index 0000000..d63e619
--- /dev/null
+++ b/src/lib/freebsd/firetray.c
@@ -0,0 +1,20 @@
+/* pkg-config --libs --cflags gtk+-2.0 */
+
+#include <gtk/gtk.h>
+#include "firetray.h"
+
+int gdk_is_window(void* obj) {
+  return GDK_IS_WINDOW(obj) ? 1 : 0;
+}
+
+int gtk_is_window(void* obj) {
+  return GTK_IS_WINDOW(obj) ? 1 : 0;
+}
+
+int gtk_is_widget(void* obj) {
+  return GTK_IS_WIDGET(obj) ? 1 : 0;
+}
+
+unsigned int gtk_get_major_version(void) {return (unsigned int)gtk_major_version;}
+unsigned int gtk_get_minor_version(void) {return (unsigned int)gtk_minor_version;}
+unsigned int gtk_get_micro_version(void) {return (unsigned int)gtk_micro_version;}
diff --git a/src/lib/freebsd/firetray.h b/src/lib/freebsd/firetray.h
new file mode 100644
index 0000000..ba6fd5e
--- /dev/null
+++ b/src/lib/freebsd/firetray.h
@@ -0,0 +1,11 @@
+#include <gdk/gdk.h>
+
+extern int gdk_is_window(void* obj);
+extern int gtk_is_window(void* obj);
+extern int gtk_is_widget(void* obj);
+
+/* the library version (not headers). These functions are provided natively in
+ * gtk+-3.0 */
+extern unsigned int gtk_get_major_version(void);
+extern unsigned int gtk_get_minor_version(void);
+extern unsigned int gtk_get_micro_version(void);
diff --git a/src/modules/ctypes/freebsd b/src/modules/ctypes/freebsd
deleted file mode 120000
index 182668b..0000000
--- a/src/modules/ctypes/freebsd
+++ /dev/null
@@ -1 +0,0 @@
-./src/modules/ctypes/linux
\ No newline at end of file
diff --git a/src/modules/ctypes/freebsd/appindicator.jsm b/src/modules/ctypes/freebsd/appindicator.jsm
new file mode 100644
index 0000000..ed7a10e
--- /dev/null
+++ b/src/modules/ctypes/freebsd/appindicator.jsm
@@ -0,0 +1,58 @@
+/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "appind3" ];
+
+const APPINDICATOR_LIBNAME = "appindicator3";
+const APPINDICATOR_ABIS    = [ 1 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/ctypes/ctypes-utils.jsm");
+Cu.import("resource://firetray/ctypes/linux/gobject.jsm");
+Cu.import("resource://firetray/ctypes/linux/gtk.jsm");
+
+function appindicator_defines(lib) {
+  this.AppIndicator = ctypes.StructType("AppIndicator");
+
+  this.INDICATOR_APPLICATION_DBUS_ADDR  = "com.canonical.indicator.application";
+  this.INDICATOR_APPLICATION_DBUS_OBJ   = "/com/canonical/indicator/application/service";
+  this.INDICATOR_APPLICATION_DBUS_IFACE = "com.canonical.indicator.application.service";
+  this.NOTIFICATION_WATCHER_DBUS_ADDR   = "org.kde.StatusNotifierWatcher";
+  this.NOTIFICATION_WATCHER_DBUS_OBJ    = "/StatusNotifierWatcher";
+  this.NOTIFICATION_WATCHER_DBUS_IFACE  = "org.kde.StatusNotifierWatcher";
+  this.NOTIFICATION_ITEM_DBUS_IFACE     = "org.kde.StatusNotifierItem";
+  this.NOTIFICATION_ITEM_DEFAULT_OBJ    = "/StatusNotifierItem";
+  this.NOTIFICATION_APPROVER_DBUS_IFACE = "org.ayatana.StatusNotifierApprover";
+
+  this.AppIndicatorCategory = ctypes.int;             // enum
+  this.APP_INDICATOR_CATEGORY_APPLICATION_STATUS = 0; /*< nick=ApplicationStatus >*/
+  this.APP_INDICATOR_CATEGORY_COMMUNICATIONS     = 1; /*< nick=Communications >*/
+  this.APP_INDICATOR_CATEGORY_SYSTEM_SERVICES    = 2; /*< nick=SystemServices >*/
+  this.APP_INDICATOR_CATEGORY_HARDWARE           = 3; /*< nick=Hardware >*/
+  this.APP_INDICATOR_CATEGORY_OTHER              = 4; /*< nick=Other >*/
+
+  this.AppIndicatorStatus = ctypes.int;    // enum
+  this.APP_INDICATOR_STATUS_PASSIVE   = 0; /*< nick=Passive >*/
+  this.APP_INDICATOR_STATUS_ACTIVE    = 1; /*< nick=Active >*/
+  this.APP_INDICATOR_STATUS_ATTENTION = 2; /*< nick=NeedsAttention >*/
+
+  lib.lazy_bind("app_indicator_new", this.AppIndicator.ptr, gobject.gchar.ptr, gobject.gchar.ptr, this.AppIndicatorCategory);
+  lib.lazy_bind("app_indicator_set_status", ctypes.void_t, this.AppIndicator.ptr, this.AppIndicatorStatus);
+  lib.lazy_bind("app_indicator_get_status", this.AppIndicatorStatus, this.AppIndicator.ptr);
+  lib.lazy_bind("app_indicator_set_menu", ctypes.void_t, this.AppIndicator.ptr, gtk.GtkMenu.ptr);
+  lib.lazy_bind("app_indicator_set_icon", ctypes.void_t, this.AppIndicator.ptr, gobject.gchar.ptr);
+  lib.lazy_bind("app_indicator_set_attention_icon", ctypes.void_t, this.AppIndicator.ptr, gobject.gchar.ptr);
+  lib.lazy_bind("app_indicator_set_label", ctypes.void_t, this.AppIndicator.ptr, gobject.gchar.ptr, gobject.gchar.ptr);
+  lib.lazy_bind("app_indicator_set_secondary_activate_target", ctypes.void_t, this.AppIndicator.ptr, gtk.GtkWidget.ptr);
+
+  this.ConnectionChangedCb_t = ctypes.FunctionType(
+    ctypes.default_abi, ctypes.void_t, [this.AppIndicator.ptr, gobject.gboolean, gobject.gpointer]).ptr;
+
+  this.OnScrollCb_t = ctypes.FunctionType(
+    ctypes.default_abi, ctypes.void_t, [this.AppIndicator.ptr, gobject.gint, gobject.guint, gobject.gpointer]).ptr;
+};
+
+var appind3 = new ctypes_library(APPINDICATOR_LIBNAME, APPINDICATOR_ABIS, appindicator_defines, this);
diff --git a/src/modules/ctypes/freebsd/cairo.jsm b/src/modules/ctypes/freebsd/cairo.jsm
new file mode 100644
index 0000000..7a3bccc
--- /dev/null
+++ b/src/modules/ctypes/freebsd/cairo.jsm
@@ -0,0 +1,26 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "cairo" ];
+
+const CAIRO_LIBNAME = "cairo";
+const CAIRO_ABIS    = [ 2 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/ctypes/ctypes-utils.jsm");
+
+function cairo_defines(lib) {
+  this.cairo_t = ctypes.StructType("cairo_t");
+
+  lib.lazy_bind("cairo_rectangle", ctypes.void_t, this.cairo_t.ptr, ctypes.double, ctypes.double, ctypes.double, ctypes.double);
+  lib.lazy_bind("cairo_set_source_rgb", ctypes.void_t, this.cairo_t.ptr, ctypes.double, ctypes.double, ctypes.double);
+  lib.lazy_bind("cairo_fill", ctypes.void_t, this.cairo_t.ptr);
+  lib.lazy_bind("cairo_move_to", ctypes.void_t, this.cairo_t.ptr, ctypes.double, ctypes.double);
+  lib.lazy_bind("cairo_destroy", ctypes.void_t, this.cairo_t.ptr);
+
+}
+
+new ctypes_library(CAIRO_LIBNAME, CAIRO_ABIS, cairo_defines, this);
diff --git a/src/modules/ctypes/freebsd/gdk.jsm b/src/modules/ctypes/freebsd/gdk.jsm
new file mode 100644
index 0000000..27cf198
--- /dev/null
+++ b/src/modules/ctypes/freebsd/gdk.jsm
@@ -0,0 +1,333 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *	 Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firetray
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Ltd.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *    Mike Conley <mconley at mozillamessaging.com>
+ *    Foudil Brétel <foudil.newbie+amo at gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var EXPORTED_SYMBOLS = [ "gdk" ];
+
+const GDK_LIBNAME = "gdk-x11-2.0";
+const GDK_ABIS    = [ 0 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/ctypes/ctypes-utils.jsm");
+Cu.import("resource://firetray/ctypes/linux/cairo.jsm");
+Cu.import("resource://firetray/ctypes/linux/glib.jsm");
+Cu.import("resource://firetray/ctypes/linux/gobject.jsm");
+Cu.import("resource://firetray/ctypes/linux/x11.jsm");
+
+function gdk_defines(lib) {
+  this.GdkInterpType = ctypes.int; // enum
+  this.GDK_INTERP_NEAREST  = 0;
+  this.GDK_INTERP_TILES    = 1;
+  this.GDK_INTERP_BILINEAR = 2;
+  this.GDK_INTERP_HYPE     = 3;
+  this.GdkFilterReturn = ctypes.int; // enum
+  this.GDK_FILTER_CONTINUE  = 0;
+  this.GDK_FILTER_TRANSLATE = 1;
+  this.GDK_FILTER_REMOVE    = 2;
+  this.GdkWindowState = ctypes.int; // enum
+  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.GdkPropMode = ctypes.int; // enum
+  this.GDK_PROP_MODE_REPLACE = 0;
+  this.GDK_PROP_MODE_PREPEN  = 1;
+  this.GDK_PROP_MODE_APPEND  = 2;
+  this.GdkScrollDirection = ctypes.int; // enum
+  this.GDK_SCROLL_UP    = 0;
+  this.GDK_SCROLL_DOWN  = 1;
+  this.GDK_SCROLL_LEFT  = 2;
+  this.GDK_SCROLL_RIGHT = 3;
+  this.GdkEventMask = ctypes.int; // enum
+  this.GDK_EXPOSURE_MASK            = 1 << 1,
+  this.GDK_POINTER_MOTION_MASK      = 1 << 2,
+  this.GDK_POINTER_MOTION_HINT_MASK = 1 << 3,
+  this.GDK_BUTTON_MOTION_MASK       = 1 << 4,
+  this.GDK_BUTTON1_MOTION_MASK      = 1 << 5,
+  this.GDK_BUTTON2_MOTION_MASK      = 1 << 6,
+  this.GDK_BUTTON3_MOTION_MASK      = 1 << 7,
+  this.GDK_BUTTON_PRESS_MASK        = 1 << 8,
+  this.GDK_BUTTON_RELEASE_MASK      = 1 << 9,
+  this.GDK_KEY_PRESS_MASK           = 1 << 10,
+  this.GDK_KEY_RELEASE_MASK         = 1 << 11,
+  this.GDK_ENTER_NOTIFY_MASK        = 1 << 12,
+  this.GDK_LEAVE_NOTIFY_MASK        = 1 << 13,
+  this.GDK_FOCUS_CHANGE_MASK        = 1 << 14,
+  this.GDK_STRUCTURE_MASK           = 1 << 15,
+  this.GDK_PROPERTY_CHANGE_MASK     = 1 << 16,
+  this.GDK_VISIBILITY_NOTIFY_MASK   = 1 << 17,
+  this.GDK_PROXIMITY_IN_MASK        = 1 << 18,
+  this.GDK_PROXIMITY_OUT_MASK       = 1 << 19,
+  this.GDK_SUBSTRUCTURE_MASK        = 1 << 20,
+  this.GDK_SCROLL_MASK              = 1 << 21,
+  this.GDK_ALL_EVENTS_MASK          = 0x3FFFFE
+  this.GdkColorspace = ctypes.int;     // enum
+  this.GDK_COLORSPACE_RGB = 0;
+
+  this.GdkWindow = ctypes.StructType("GdkWindow");
+  this.GdkByteOrder = ctypes.int; // enum
+  this.GdkVisualType = ctypes.int; // enum
+  this.GdkVisual = ctypes.StructType("GdkVisual", [
+    { "parent_instance": gobject.GObject },
+    { "type": this.GdkVisualType },
+    { "depth": gobject.gint },
+    { "byte": this.GdkByteOrder },
+    { "colormap": gobject.gint },
+    { "bits": gobject.gint },
+    { "red_mask": gobject.guint32 },
+    { "red_shift": gobject.gint },
+    { "red_prec": gobject.gint },
+    { "green_mask": gobject.guint32 },
+    { "green_shift": gobject.gint },
+    { "green_prec": gobject.gint },
+    { "blue_mask": gobject.guint32 },
+    { "blue_shift": gobject.gint },
+    { "blue_prec": gobject.gint }
+  ]);
+  this.GdkColor = ctypes.StructType("GdkColor", [
+    { "pixel": gobject.guint32 },
+    { "red": gobject.guint16 },
+    { "green": gobject.guint16 },
+    { "blue": gobject.guint16 }
+  ]);
+  this.GdkColormap = ctypes.StructType("GdkColormap", [
+    { "size": gobject.gint },
+    { "colors": this.GdkColor.ptr }
+  ]);
+  this.GdkWindowType = ctypes.StructType("GdkWindowType");
+  this.GdkCursor = ctypes.StructType("GdkCursor");
+  this.GdkWindowTypeHint = ctypes.StructType("GdkWindowTypeHint");
+  this.GdkWindowClass = ctypes.StructType("GdkWindowClass");
+  this.GdkWindowAttributes = ctypes.StructType("GdkWindowAttributes", [
+    { "title": gobject.gchar },
+    { "event_mask": gobject.gint },
+    { "x": gobject.gint },
+    { "y": gobject.gint },
+    { "width": gobject.gint },
+    { "height": gobject.gint },
+    { "wclass": gobject.gint },
+    { "visual": this.GdkVisual.ptr },
+    { "colormap": this.GdkColormap.ptr },
+    { "window_type": gobject.gint },
+    { "cursor": this.GdkCursor.ptr },
+    { "wmclass_name": gobject.gchar },
+    { "wmclass_class": gobject.gchar },
+    { "override_redirect": gobject.gboolean },
+    { "type_hint": gobject.gint }
+  ]);
+  this.GdkPixbuf = ctypes.StructType("GdkPixbuf");
+  this.GdkScreen = ctypes.StructType("GdkScreen");
+  this.GdkPixmap = ctypes.StructType("GdkPixmap");
+  this.GdkDrawable = ctypes.StructType("GdkDrawable");
+  this.GdkGC = ctypes.StructType("GdkGC");
+  this.GdkXEvent = ctypes.void_t; // will probably be cast to XEvent
+  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.GdkDevice = ctypes.StructType("GdkDevice");
+  this.GdkEventScroll = ctypes.StructType("GdkEventScroll", [
+    { "type": this.GdkEventType },
+    { "window": this.GdkWindow.ptr },
+    { "send_event": gobject.gint8 },
+    { "time": gobject.guint32 },
+    { "x": gobject.gdouble },
+    { "y": gobject.gdouble },
+    { "state": gobject.guint },
+    { "direction": this.GdkScrollDirection },
+    { "device": this.GdkDevice.ptr },
+    { "x_root": gobject.gdouble },
+    { "y_root": gobject.gdouble }
+  ]);
+  this.GdkAtom = ctypes.StructType("GdkAtom");
+  this.GdkEventButton = ctypes.StructType("GdkEventButton", [
+    { "type": this.GdkEventType },
+    { "window": this.GdkWindow.ptr },
+    { "send_event": gobject.gint8 },
+    { "time": gobject.guint32 },
+    { "x": gobject.gdouble },
+    { "y": gobject.gdouble },
+    { "axes": gobject.gdouble.ptr },
+    { "state": gobject.guint },
+    { "button": gobject.guint },
+    { "device": this.GdkDevice.ptr },
+    { "x_root": gobject.gdouble },
+    { "y_root": gobject.gdouble }
+  ]);
+  this.GdkEventFocus = ctypes.StructType("GdkEventFocus", [
+    { "type": this.GdkEventType },
+    { "window": this.GdkWindow.ptr },
+    { "send_event": gobject.gint8 },
+    { "in": gobject.gint16 },
+  ]);
+
+  this.GdkFilterFunc_t = ctypes.FunctionType(
+    ctypes.default_abi, this.GdkFilterReturn,
+    [this.GdkXEvent.ptr, this.GdkEvent.ptr, gobject.gpointer]).ptr;
+
+  lib.lazy_bind("gdk_flush", ctypes.void_t);
+  lib.lazy_bind("gdk_error_trap_push", ctypes.void_t);
+  lib.lazy_bind("gdk_error_trap_pop", gobject.gint);
+
+  lib.lazy_bind("gdk_x11_drawable_get_xid", x11.XID, this.GdkDrawable.ptr);
+  lib.lazy_bind("gdk_window_new", this.GdkWindow.ptr, this.GdkWindow.ptr, this.GdkWindowAttributes.ptr, gobject.gint);
+  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_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_screen_get_number", gobject.gint, this.GdkScreen.ptr);
+  lib.lazy_bind("gdk_screen_get_display", this.GdkDisplay.ptr, this.GdkScreen.ptr);
+  lib.lazy_bind("gdk_x11_get_xatom_by_name_for_display", x11.Atom, this.GdkDisplay.ptr, gobject.gchar.ptr);
+  lib.lazy_bind("gdk_pixbuf_new_from_file", this.GdkPixbuf.ptr, gobject.gchar.ptr, glib.GError.ptr.ptr);
+  lib.lazy_bind("gdk_pixbuf_copy", this.GdkPixbuf.ptr, this.GdkPixbuf.ptr);
+  lib.lazy_bind("gdk_pixbuf_composite", ctypes.void_t, this.GdkPixbuf.ptr, this.GdkPixbuf.ptr, ctypes.int, ctypes.int, ctypes.int, ctypes.int, ctypes.double, ctypes.double, ctypes.double, ctypes.double, ctypes.int, ctypes.int);
+  lib.lazy_bind("gdk_pixbuf_get_has_alpha", gobject.gboolean, this.GdkPixbuf.ptr);
+  lib.lazy_bind("gdk_pixbuf_add_alpha", this.GdkPixbuf.ptr, this.GdkPixbuf.ptr, gobject.gboolean, gobject.guchar, gobject.guchar, gobject.guchar);
+  lib.lazy_bind("gdk_pixbuf_get_colorspace", this.GdkColorspace, this.GdkPixbuf.ptr);
+  lib.lazy_bind("gdk_pixbuf_get_n_channels", ctypes.int, this.GdkPixbuf.ptr);
+  lib.lazy_bind("gdk_pixbuf_get_has_alpha", gobject.gboolean, this.GdkPixbuf.ptr);
+  lib.lazy_bind("gdk_pixbuf_get_bits_per_sample", ctypes.int, this.GdkPixbuf.ptr);
+  lib.lazy_bind("gdk_pixbuf_get_pixels", gobject.guchar.ptr, this.GdkPixbuf.ptr);
+  lib.lazy_bind("gdk_pixbuf_get_width", ctypes.int, this.GdkPixbuf.ptr);
+  lib.lazy_bind("gdk_pixbuf_get_height", ctypes.int, this.GdkPixbuf.ptr);
+  lib.lazy_bind("gdk_pixbuf_get_rowstride", ctypes.int, this.GdkPixbuf.ptr);
+  lib.lazy_bind("gdk_pixbuf_get_byte_length", gobject.gsize, this.GdkPixbuf.ptr);
+  lib.lazy_bind("gdk_pixbuf_copy", this.GdkPixbuf.ptr, this.GdkPixbuf.ptr);
+
+  lib.lazy_bind("gdk_screen_get_system_colormap", this.GdkColormap.ptr, this.GdkScreen.ptr);
+  lib.lazy_bind("gdk_colormap_get_visual", this.GdkVisual.ptr, this.GdkColormap.ptr);
+  lib.lazy_bind("gdk_color_parse", gobject.gboolean, gobject.gchar.ptr, this.GdkColor.ptr);
+  lib.lazy_bind("gdk_colormap_alloc_color", gobject.gboolean, this.GdkColormap.ptr, this.GdkColor.ptr, gobject.gboolean, gobject.gboolean);
+  lib.lazy_bind("gdk_pixmap_new", this.GdkPixmap.ptr, this.GdkDrawable.ptr, gobject.gint, gobject.gint, gobject.gint);
+
+  // DEPRECATED
+  // lib.lazy_bind("gdk_gc_new", this.GdkGC.ptr, this.GdkDrawable.ptr);
+  // lib.lazy_bind("gdk_gc_set_foreground", ctypes.void_t, this.GdkGC.ptr, this.GdkColor.ptr);
+  // lib.lazy_bind("gdk_draw_rectangle", ctypes.void_t, this.GdkDrawable.ptr, this.GdkGC.ptr, gobject.gboolean, gobject.gint, gobject.gint, gobject.gint, gobject.gint);
+
+  lib.lazy_bind("gdk_cairo_create", cairo.cairo_t.ptr, this.GdkDrawable.ptr);
+  lib.lazy_bind("gdk_cairo_set_source_color", ctypes.void_t, cairo.cairo_t.ptr, this.GdkColor.ptr);
+  lib.lazy_bind("gdk_pixbuf_get_from_drawable", this.GdkPixbuf.ptr, this.GdkPixbuf.ptr, this.GdkDrawable.ptr, this.GdkColormap.ptr, ctypes.int, ctypes.int, ctypes.int, ctypes.int, ctypes.int, ctypes.int);
+  lib.lazy_bind("gdk_pixbuf_add_alpha", this.GdkPixbuf.ptr, this.GdkPixbuf.ptr, gobject.gboolean, gobject.guchar, gobject.guchar, gobject.guchar);
+  lib.lazy_bind("gdk_pixbuf_composite", ctypes.void_t, this.GdkPixbuf.ptr, this.GdkPixbuf.ptr, ctypes.int, ctypes.int, ctypes.int, ctypes.int, ctypes.double, ctypes.double, ctypes.double, ctypes.double, this.GdkInterpType, ctypes.int);
+
+  lib.lazy_bind("gdk_window_stick", ctypes.void_t, this.GdkWindow.ptr);
+  lib.lazy_bind("gdk_window_iconify", ctypes.void_t, this.GdkWindow.ptr);
+  lib.lazy_bind("gdk_window_deiconify", ctypes.void_t, this.GdkWindow.ptr);
+  lib.lazy_bind("gdk_window_set_title", ctypes.void_t, this.GdkWindow.ptr, gobject.gchar.ptr);
+  lib.lazy_bind("gdk_window_beep", ctypes.void_t, this.GdkWindow.ptr);
+  lib.lazy_bind("gdk_window_get_width", ctypes.int, this.GdkWindow.ptr);
+
+  lib.lazy_bind("gdk_window_get_events", this.GdkEventMask, this.GdkWindow.ptr);
+  lib.lazy_bind("gdk_window_set_events", ctypes.void_t, this.GdkWindow.ptr, this.GdkEventMask);
+  lib.lazy_bind("gdk_window_add_filter", ctypes.void_t, this.GdkWindow.ptr, this.GdkFilterFunc, gobject.gpointer);
+  lib.lazy_bind("gdk_window_remove_filter", ctypes.void_t, this.GdkWindow.ptr, this.GdkFilterFunc, gobject.gpointer);
+  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);
+  lib.lazy_bind("gdk_window_get_user_data", ctypes.void_t, this.GdkWindow.ptr, gobject.gpointer.ptr);
+  lib.lazy_bind("gdk_atom_intern", this.GdkAtom, gobject.gchar.ptr, gobject.gboolean);
+  lib.lazy_bind("gdk_property_change", ctypes.void_t, this.GdkWindow.ptr, this.GdkAtom, this.GdkAtom, gobject.gint, this.GdkPropMode, gobject.guchar.ptr, gobject.gint);
+  lib.lazy_bind("gdk_window_get_toplevel", this.GdkWindow.ptr, this.GdkWindow.ptr);
+  lib.lazy_bind("gdk_window_get_effective_toplevel", this.GdkWindow.ptr, this.GdkWindow.ptr);
+  lib.lazy_bind("gdk_screen_get_active_window", this.GdkWindow.ptr, this.GdkScreen.ptr);
+
+  lib.lazy_bind("gdk_display_get_n_screens", gobject.gint, this.GdkDisplay.ptr);
+  lib.lazy_bind("gdk_display_get_screen", this.GdkScreen.ptr, this.GdkDisplay.ptr, gobject.gint);
+}
+
+new ctypes_library(GDK_LIBNAME, GDK_ABIS, gdk_defines, this);
diff --git a/src/modules/ctypes/freebsd/gio.jsm b/src/modules/ctypes/freebsd/gio.jsm
new file mode 100644
index 0000000..ccd7e2d
--- /dev/null
+++ b/src/modules/ctypes/freebsd/gio.jsm
@@ -0,0 +1,46 @@
+/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "gio" ];
+
+const GIO_LIBNAME = "gio-2.0";
+const GIO_ABIS    = [ "0" ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/ctypes/ctypes-utils.jsm");
+Cu.import("resource://firetray/ctypes/linux/glib.jsm");
+Cu.import("resource://firetray/ctypes/linux/gobject.jsm");
+
+function gio_defines(lib) {
+  this.GIcon = ctypes.StructType("GIcon");
+  this.GThemedIcon = ctypes.StructType("GThemedIcon");
+
+  lib.lazy_bind("g_themed_icon_new", this.GIcon.ptr, ctypes.char.ptr);
+  lib.lazy_bind("g_themed_icon_new_from_names", this.GIcon.ptr, ctypes.char.ptr.ptr, ctypes.int);
+  lib.lazy_bind("g_themed_icon_get_names", gobject.gchar.ptr.ptr, this.GThemedIcon.ptr);
+
+  this.GBusType = ctypes.int; // enum
+  this.G_BUS_TYPE_STARTER = -1;
+  this.G_BUS_TYPE_NONE    = 0;
+  this.G_BUS_TYPE_SYSTEM  = 1;
+  this.G_BUS_TYPE_SESSION = 2;
+  this.GDBusProxyFlags = ctypes.int; // enum
+  this.G_DBUS_PROXY_FLAGS_NONE                   = 0;
+  this.G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES = (1<<0);
+  this.G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = (1<<1);
+  this.G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START      = (1<<2);
+
+  this.GDBusConnection = ctypes.StructType("GDBusConnection");
+  this.GCancellable = ctypes.StructType("GCancellable");
+  this.GDBusProxy = ctypes.StructType("GDBusProxy");
+  this.GDBusInterfaceInfo = ctypes.StructType("GDBusInterfaceInfo");
+
+  lib.lazy_bind("g_bus_get_sync", this.GDBusConnection.ptr, this.GBusType, this.GCancellable.ptr, glib.GError.ptr.ptr);
+  lib.lazy_bind("g_dbus_proxy_new_for_bus_sync", this.GDBusProxy.ptr, this.GBusType, this.GDBusProxyFlags, this.GDBusInterfaceInfo.ptr, gobject.gchar.ptr, gobject.gchar.ptr, gobject.gchar.ptr, this.GCancellable.ptr, glib.GError.ptr.ptr);
+  lib.lazy_bind("g_dbus_proxy_get_name_owner", gobject.gchar.ptr, this.GDBusProxy.ptr);
+}
+
+new ctypes_library(GIO_LIBNAME, GIO_ABIS, gio_defines, this);
diff --git a/src/modules/ctypes/freebsd/glib.jsm b/src/modules/ctypes/freebsd/glib.jsm
new file mode 100644
index 0000000..963bd5b
--- /dev/null
+++ b/src/modules/ctypes/freebsd/glib.jsm
@@ -0,0 +1,27 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "glib" ];
+
+const GLIB_LIBNAME = "glib-2.0";
+const GLIB_ABIS    = [ 0 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/ctypes/ctypes-utils.jsm");
+
+function glib_defines(lib) {
+  /* mutual inclusion not possible */
+  this.GQuark = ctypes.uint32_t; // this.GQuark = gobject.guint32;
+  this.GError = ctypes.StructType("GError", [
+    { domain: this.GQuark },
+    { code: ctypes.int },        // gint
+    { message: ctypes.char.ptr } // gchar.ptr
+  ]);
+  lib.lazy_bind("g_error_free", ctypes.void_t, this.GError.ptr);
+  lib.lazy_bind("g_strfreev", ctypes.void_t, ctypes.char.ptr.ptr);
+};
+
+new ctypes_library(GLIB_LIBNAME, GLIB_ABIS, glib_defines, this);
diff --git a/src/modules/ctypes/freebsd/gobject.jsm b/src/modules/ctypes/freebsd/gobject.jsm
new file mode 100644
index 0000000..cf95946
--- /dev/null
+++ b/src/modules/ctypes/freebsd/gobject.jsm
@@ -0,0 +1,140 @@
+/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *	 Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firetray
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Ltd.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *    Mike Conley <mconley at mozillamessaging.com>
+ *    Foudil Brétel <foudil.newbie+amo at gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var EXPORTED_SYMBOLS = [ "gobject", "glib" ];
+
+const GOBJECT_LIBNAME = "gobject-2.0";
+const GOBJECT_ABIS = [ 0 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/ctypes/ctypes-utils.jsm");
+Cu.import("resource://firetray/ctypes/linux/glib.jsm");
+
+function gobject_defines(lib) {
+
+  this.GSignalMatchType = ctypes.int; // enum
+  this.G_SIGNAL_MATCH_ID            = 1 << 0;
+  this.G_SIGNAL_MATCH_DETAIL        = 1 << 1;
+  this.G_SIGNAL_MATCH_CLOSURE       = 1 << 2;
+  this.G_SIGNAL_MATCH_FUNC          = 1 << 3;
+  this.G_SIGNAL_MATCH_DATA          = 1 << 4;
+  this.G_SIGNAL_MATCH_UNBLOCKED     = 1 << 5;
+  this.gpointer = ctypes.voidptr_t;
+  this.gulong = ctypes.unsigned_long;
+  this.guint = ctypes.unsigned_int;
+  this.guint32 = ctypes.uint32_t;
+  this.guint16 = ctypes.uint16_t;
+  this.gint = ctypes.int;
+  this.gint8 = ctypes.int8_t;
+  this.gint16 = ctypes.int16_t;
+  this.gchar = ctypes.char;
+  this.guchar = ctypes.unsigned_char;
+  this.gboolean = this.gint;
+  this.FALSE = this.gboolean(0);
+  this.gfloat = ctypes.float;
+  this.gdouble = ctypes.double;
+  this.gsize = ctypes.unsigned_long;
+  this.GCallback = ctypes.voidptr_t;
+  this.GClosureNotify = this.gpointer;
+  this.GFunc = ctypes.void_t.ptr;
+  this.GList = ctypes.StructType("GList");
+  this.GConnectFlags = this.guint; // enum
+  this.G_CONNECT_AFTER   = 1 << 0;
+  this.G_CONNECT_SWAPPED = 1 << 1;
+
+  this.GType = this.gsize;
+  this.GData = ctypes.StructType("GData");
+  this._GTypeClass = ctypes.StructType("_GTypeClass", [
+    {g_type: this.GType}]);
+  this._GTypeInstance = ctypes.StructType("_GTypeInstance", [
+    {g_class: this._GTypeClass.ptr}]);
+  /* "All the fields in the GObject structure are private to the GObject
+   * implementation and should never be accessed directly." but we need to tell
+   * something about it to access GdkVisual fields */
+  this.GObject = ctypes.StructType("GObject", [
+    { g_type_instance: this._GTypeInstance },
+    { ref_count: this.guint },
+    { qdata: this.GData.ptr },
+  ]);
+  this.GClosure = ctypes.StructType("GClosure", [
+    { in_marshal: this.guint },
+    { is_invalid: this.guint },
+  ]);
+
+  /* NOTE: if we needed more/different args, we'd need to implement another
+     FunctionType */
+  this.GCallback_t = ctypes.FunctionType(
+    ctypes.default_abi, ctypes.void_t, [this.gpointer]).ptr;
+  // intended for g_list_foreach.
+  this.GFunc_t = ctypes.FunctionType(
+    ctypes.default_abi, ctypes.void_t, [this.gpointer, this.gpointer]).ptr;
+
+  lib.lazy_bind("g_object_unref", ctypes.void_t, this.gpointer);
+  lib.lazy_bind("g_signal_connect_data", this.gulong, this.gpointer, this.gchar.ptr, this.GCallback, this.gpointer, this.GClosureNotify, this.GConnectFlags);
+
+  this.g_signal_connect = function(instance, detailed_signal, handler, data) {
+    return this.g_signal_connect_data(instance, detailed_signal, handler, data, null, 0);
+  };
+  this.g_signal_connect_after = function(instance, detailed_signal, handler, data) {
+    return this.g_signal_connect_data(instance, detailed_signal, handler, data, null, this.G_CONNECT_AFTER);
+  };
+
+  lib.lazy_bind("g_free", ctypes.void_t, this.gpointer);
+  lib.lazy_bind("g_object_unref", ctypes.void_t, this.gpointer);
+  lib.lazy_bind("g_list_free", ctypes.void_t, this.GList.ptr);
+  lib.lazy_bind("g_list_length", this.guint, this.GList.ptr);
+  lib.lazy_bind("g_list_foreach", ctypes.void_t, this.GList.ptr, this.GFunc, this.gpointer);
+  lib.lazy_bind("g_signal_lookup", this.guint, this.gchar.ptr, this.GType);
+  lib.lazy_bind("g_signal_handler_find", this.gulong, this.gpointer, this.GSignalMatchType, this.guint, glib.GQuark, this.GClosure.ptr, this.gpointer, this.gpointer);
+  lib.lazy_bind("g_signal_handler_disconnect", ctypes.void_t, this.gpointer, this.gulong);
+  lib.lazy_bind("g_signal_handler_block", ctypes.void_t, this.gpointer, this.gulong);
+  lib.lazy_bind("g_signal_handler_unblock", ctypes.void_t, this.gpointer, this.gulong);
+
+  /* NOTE: we can't easily work with g_object_get_property() because it uses
+  GValue, which is an opaque struct, and thus can't be initialized by ctypes */
+  this.GValue = ctypes.StructType("GValue");
+  lib.lazy_bind("g_object_get_property", ctypes.void_t, this.GObject.ptr, this.gchar.ptr, this.GValue.ptr);
+  lib.lazy_bind("g_object_get", ctypes.void_t, this.gpointer, this.gchar.ptr, "...");
+}
+
+new ctypes_library(GOBJECT_LIBNAME, GOBJECT_ABIS, gobject_defines, this);
diff --git a/src/modules/ctypes/freebsd/gtk.jsm b/src/modules/ctypes/freebsd/gtk.jsm
new file mode 100644
index 0000000..267aabb
--- /dev/null
+++ b/src/modules/ctypes/freebsd/gtk.jsm
@@ -0,0 +1,174 @@
+/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "gtk" ];
+
+const GTK_LIBNAME = "gtk-x11-2.0";
+const GTK_ABIS    = [ 0 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/ctypes/ctypes-utils.jsm");
+Cu.import("resource://firetray/ctypes/linux/gdk.jsm");
+Cu.import("resource://firetray/ctypes/linux/gio.jsm");
+Cu.import("resource://firetray/ctypes/linux/gobject.jsm");
+Cu.import("resource://firetray/ctypes/linux/pango.jsm");
+
+function gtk_defines(lib) {
+  this.FIRETRAY_REQUIRED_GTK_MAJOR_VERSION = 2;
+  this.FIRETRAY_REQUIRED_GTK_MINOR_VERSION = 20;
+  this.FIRETRAY_REQUIRED_GTK_MICRO_VERSION = 0;
+
+  this.GtkIconSize = ctypes.int; // enum
+  this.GTK_ICON_SIZE_INVALID = 0;
+  this.GTK_ICON_SIZE_MENU = 1;
+  this.GTK_ICON_SIZE_SMALL_TOOLBAR = 2;
+  this.GTK_ICON_SIZE_LARGE_TOOLBAR = 3;
+  this.GTK_ICON_SIZE_BUTTON = 4;
+  this.GTK_ICON_SIZE_DND = 5;
+  this.GTK_ICON_SIZE_DIALOG = 6;
+
+  this.GTK_WINDOW_TOPLEVEL = 0; // enum GtkWindowType
+
+  this.GtkStatusIcon = ctypes.StructType("GtkStatusIcon");
+  this.GtkStyle = ctypes.StructType("GtkStyle");
+  this.GtkRequisition = ctypes.StructType("GtkRequisition", [
+    { width: gobject.gint },
+    { height: gobject.gint }
+  ]);
+  this.GtkAllocation = ctypes.StructType("GtkAllocation", [
+    { x: gobject.gint },
+    { y: gobject.gint },
+    { width: gobject.gint },
+    { height: gobject.gint }
+  ]);
+  /* NOTE: recursive struct needs define() and included structs MUST be
+   * defined ! */
+  this.GtkWidget = ctypes.StructType("GtkWidget");
+  this.GtkWidget.define([
+    { "style": this.GtkStyle.ptr },
+    { "requisition": this.GtkRequisition },
+    { "allocation": this.GtkAllocation },
+    { "window": gdk.GdkWindow.ptr },
+    { "parent": this.GtkWidget.ptr }
+  ]);
+
+  this.GtkIconTheme = ctypes.StructType("GtkIconTheme");
+  this.GtkMenu = ctypes.StructType("GtkMenu");
+  // use ctypes.cast(menu, LibGtkStatusIcon.GtkMenuShell.ptr);
+  this.GtkMenuShell = ctypes.StructType("GtkMenuShell");
+  this.GtkMenuItem = ctypes.StructType("GtkMenuItem");
+  this.GtkImageMenuItem = ctypes.StructType("GtkImageMenuItem");
+  this.GtkWindow = ctypes.StructType("GtkWindow");
+  this.GtkWindowType = ctypes.int; // enum
+  this.GtkSeparatorMenuItem = ctypes.StructType("GtkSeparatorMenuItem");
+  this.GtkIconInfo = ctypes.StructType("GtkIconInfo");
+  this.GtkIconLookupFlags = ctypes.int; // enum
+  this.GTK_ICON_LOOKUP_NO_SVG           = 1 << 0;
+  this.GTK_ICON_LOOKUP_FORCE_SVG        = 1 << 1;
+  this.GTK_ICON_LOOKUP_USE_BUILTIN      = 1 << 2;
+  this.GTK_ICON_LOOKUP_GENERIC_FALLBACK = 1 << 3;
+  this.GTK_ICON_LOOKUP_FORCE_SIZE       = 1 << 4;
+
+  this.GtkMenuPositionFunc_t = ctypes.FunctionType(
+    ctypes.default_abi, ctypes.void_t,
+    [this.GtkMenu.ptr, gobject.gint.ptr, gobject.gint.ptr,
+     gobject.gboolean.ptr, gobject.gpointer]).ptr;
+  this.GCallbackStatusIconActivate_t = ctypes.FunctionType(
+    ctypes.default_abi, gobject.gboolean,
+    [this.GtkStatusIcon.ptr, gobject.gpointer]).ptr;
+  this.GCallbackMenuPopup_t = ctypes.FunctionType(
+    ctypes.default_abi, ctypes.void_t,
+    [this.GtkStatusIcon.ptr, gobject.guint, gobject.guint,
+     gobject.gpointer]).ptr;
+  this.GCallbackOnScroll_t = ctypes.FunctionType(
+    ctypes.default_abi, gobject.gboolean,
+    [this.GtkStatusIcon.ptr, gdk.GdkEvent.ptr, gobject.gpointer]).ptr;
+  this.GCallbackStatusIconMiddleClick_t = this.GCallbackOnScroll_t;
+  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;
+  this.GCallbackWidgetFocusEvent_t = ctypes.FunctionType(
+    ctypes.default_abi, gobject.gboolean,
+    [this.GtkWidget.ptr, gdk.GdkEventFocus.ptr, gobject.gpointer]).ptr;
+
+  lib.lazy_bind("gtk_check_version", gobject.gchar.ptr, gobject.guint, gobject.guint, gobject.guint);
+
+  lib.lazy_bind("gtk_icon_theme_get_default", this.GtkIconTheme.ptr);
+  lib.lazy_bind("gtk_icon_theme_get_for_screen", this.GtkIconTheme.ptr, gdk.GdkScreen.ptr);
+  lib.lazy_bind("gtk_icon_theme_get_search_path", ctypes.void_t, this.GtkIconTheme.ptr, gobject.gchar.ptr.ptr.array(), gobject.gint.ptr);
+  lib.lazy_bind("gtk_icon_theme_append_search_path", ctypes.void_t, this.GtkIconTheme.ptr, gobject.gchar.ptr);
+  lib.lazy_bind("gtk_icon_theme_prepend_search_path", ctypes.void_t, this.GtkIconTheme.ptr, gobject.gchar.ptr);
+  lib.lazy_bind("gtk_icon_theme_choose_icon", this.GtkIconInfo.ptr, this.GtkIconTheme.ptr, gobject.gchar.ptr.array(), gobject.gint, this.GtkIconLookupFlags);
+  lib.lazy_bind("gtk_icon_info_load_icon", gdk.GdkPixbuf.ptr, this.GtkIconInfo.ptr, glib.GError.ptr.ptr);
+  lib.lazy_bind("gtk_icon_info_free", ctypes.void_t, this.GtkIconInfo.ptr);
+
+  lib.lazy_bind("gtk_status_icon_new", this.GtkStatusIcon.ptr);
+  lib.lazy_bind("gtk_status_icon_set_from_file", ctypes.void_t, this.GtkStatusIcon.ptr, ctypes.char.ptr);
+  lib.lazy_bind("gtk_status_icon_set_from_icon_name", ctypes.void_t, this.GtkStatusIcon.ptr, gobject.gchar.ptr);
+  lib.lazy_bind("gtk_status_icon_set_from_gicon", ctypes.void_t, this.GtkStatusIcon.ptr, gio.GIcon.ptr);
+  lib.lazy_bind("gtk_status_icon_set_tooltip_text", ctypes.void_t, this.GtkStatusIcon.ptr, ctypes.char.ptr);
+  lib.lazy_bind("gtk_status_icon_set_blinking", ctypes.void_t, this.GtkStatusIcon.ptr, gobject.gboolean); // deprecated in gtk3
+  lib.lazy_bind("gtk_status_icon_set_visible", ctypes.void_t, this.GtkStatusIcon.ptr, gobject.gboolean);
+  lib.lazy_bind("gtk_menu_new", this.GtkMenu.ptr);
+  lib.lazy_bind("gtk_menu_item_set_label", ctypes.void_t, this.GtkMenuItem.ptr, gobject.gchar.ptr);
+  lib.lazy_bind("gtk_image_menu_item_new", this.GtkImageMenuItem.ptr);
+  lib.lazy_bind("gtk_image_menu_item_new_with_label", this.GtkImageMenuItem.ptr, gobject.gchar.ptr);
+  lib.lazy_bind("gtk_image_new_from_stock", this.GtkWidget.ptr, gobject.gchar.ptr, ctypes.int); // enum
+  lib.lazy_bind("gtk_image_menu_item_set_image", ctypes.void_t, this.GtkImageMenuItem.ptr, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_menu_shell_append", ctypes.void_t, this.GtkMenuShell.ptr, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_menu_shell_prepend", ctypes.void_t, this.GtkMenuShell.ptr, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_menu_shell_insert", ctypes.void_t, this.GtkMenuShell.ptr, this.GtkWidget.ptr, gobject.gint);
+  lib.lazy_bind("gtk_menu_popup", ctypes.void_t, this.GtkMenu.ptr, this.GtkWidget.ptr, this.GtkWidget.ptr, this.GtkMenuPositionFunc_t, gobject.gpointer, gobject.guint, gobject.guint);
+  lib.lazy_bind("gtk_status_icon_position_menu", ctypes.void_t, this.GtkMenu.ptr, gobject.gint.ptr, gobject.gint.ptr, gobject.gboolean.ptr, gobject.gpointer);
+  lib.lazy_bind("gtk_separator_menu_item_new", this.GtkWidget.ptr);
+
+  lib.lazy_bind("gtk_window_new", this.GtkWidget.ptr, this.GtkWindowType);
+  lib.lazy_bind("gtk_widget_create_pango_layout", pango.PangoLayout.ptr, this.GtkWidget.ptr, gobject.gchar.ptr);
+  lib.lazy_bind("gtk_widget_destroy", ctypes.void_t, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_status_icon_set_from_pixbuf", ctypes.void_t, this.GtkStatusIcon.ptr, gdk.GdkPixbuf.ptr);
+  lib.lazy_bind("gtk_status_icon_get_pixbuf", gdk.GdkPixbuf.ptr, this.GtkStatusIcon.ptr);
+  lib.lazy_bind("gtk_status_icon_get_gicon", gio.GIcon.ptr, this.GtkStatusIcon.ptr);
+  lib.lazy_bind("gtk_status_icon_get_storage_type", ctypes.int, this.GtkStatusIcon.ptr); // TEST
+  lib.lazy_bind("gtk_window_list_toplevels", gobject.GList.ptr);
+  lib.lazy_bind("gtk_window_get_title", gobject.gchar.ptr, this.GtkWindow.ptr);
+  lib.lazy_bind("gtk_window_is_active", gobject.gboolean, this.GtkWindow.ptr);
+  lib.lazy_bind("gtk_window_has_toplevel_focus", gobject.gboolean, this.GtkWindow.ptr);
+  lib.lazy_bind("gtk_widget_get_has_window", gobject.gboolean, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_widget_get_window", gdk.GdkWindow.ptr, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_widget_get_parent_window", gdk.GdkWindow.ptr, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_window_set_decorated", ctypes.void_t, this.GtkWindow.ptr, gobject.gboolean);
+  lib.lazy_bind("gtk_window_set_urgency_hint", ctypes.void_t, this.GtkWindow.ptr, gobject.gboolean);
+
+  lib.lazy_bind("gtk_widget_is_focus", gobject.gboolean, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_widget_has_focus", gobject.gboolean, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_widget_get_visible", gobject.gboolean, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_widget_hide_on_delete", gobject.gboolean, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_widget_hide", ctypes.void_t, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_widget_show", ctypes.void_t, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_widget_show_all", ctypes.void_t, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_widget_get_events", gobject.gint, this.GtkWidget.ptr);
+  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_widget_get_toplevel", this.GtkWidget.ptr, this.GtkWidget.ptr);
+  lib.lazy_bind("gtk_widget_set_sensitive", ctypes.void_t, this.GtkWidget.ptr, gobject.gboolean);
+  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", ctypes.void_t, this.GtkWindow.ptr);
+  lib.lazy_bind("gtk_window_deiconify", ctypes.void_t, this.GtkWindow.ptr);
+  lib.lazy_bind("gtk_window_stick", ctypes.void_t, this.GtkWindow.ptr);
+  lib.lazy_bind("gtk_window_maximize", ctypes.void_t, this.GtkWindow.ptr);
+  lib.lazy_bind("gtk_window_fullscreen", ctypes.void_t, this.GtkWindow.ptr);
+  lib.lazy_bind("gtk_window_present", ctypes.void_t, this.GtkWindow.ptr);
+
+}
+
+new ctypes_library(GTK_LIBNAME, GTK_ABIS, gtk_defines, this);
diff --git a/src/modules/ctypes/freebsd/libc.jsm b/src/modules/ctypes/freebsd/libc.jsm
new file mode 100644
index 0000000..a50c37a
--- /dev/null
+++ b/src/modules/ctypes/freebsd/libc.jsm
@@ -0,0 +1,31 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "libc" ];
+
+const LIBC_LIBNAME = "c";
+const LIBC_ABIS    = [ 6 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/ctypes/ctypes-utils.jsm");
+
+function libc_defines(lib) {
+  this.FILE = ctypes.StructType("FILE");
+  // this.stderr = this.fdopen(2, "a");
+  this.pid_t = ctypes.int;
+
+  lib.lazy_bind("fdopen", this.FILE.ptr, ctypes.int, ctypes.char.ptr);
+  lib.lazy_bind("puts", ctypes.int32_t, ctypes.char.ptr);
+  lib.lazy_bind("fputs", ctypes.int32_t, ctypes.char.ptr, this.FILE.ptr);
+  lib.lazy_bind("fflush", ctypes.int32_t, this.FILE.ptr);
+  lib.lazy_bind("getpid", this.pid_t);
+  lib.lazy_bind("strcmp", ctypes.int, ctypes.char.ptr, ctypes.char.ptr);
+  lib.lazy_bind("popen", this.FILE.ptr, ctypes.char.ptr, ctypes.char.ptr);
+  lib.lazy_bind("pclose", ctypes.int, this.FILE.ptr);
+  lib.lazy_bind("fread", ctypes.size_t, ctypes.voidptr_t, ctypes.size_t, ctypes.size_t, this.FILE.ptr);
+};
+
+var libc = new ctypes_library(LIBC_LIBNAME, LIBC_ABIS, libc_defines, this);
diff --git a/src/modules/ctypes/freebsd/pango.jsm b/src/modules/ctypes/freebsd/pango.jsm
new file mode 100644
index 0000000..a19a755
--- /dev/null
+++ b/src/modules/ctypes/freebsd/pango.jsm
@@ -0,0 +1,47 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "pango", "pangocairo" ];
+
+const PANGO_LIBNAME = "pango-1.0";
+const PANGO_ABIS    = [ 0 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/ctypes/ctypes-utils.jsm");
+Cu.import("resource://firetray/ctypes/linux/cairo.jsm");
+Cu.import("resource://firetray/ctypes/linux/gobject.jsm");
+
+function pango_defines(lib) {
+  this.PANGO_WEIGHT_THIN       = 100;
+  this.PANGO_WEIGHT_ULTRALIGHT = 200;
+  this.PANGO_WEIGHT_LIGHT      = 300;
+  this.PANGO_WEIGHT_BOOK       = 380;
+  this.PANGO_WEIGHT_NORMAL     = 400;
+  this.PANGO_WEIGHT_MEDIUM     = 500;
+  this.PANGO_WEIGHT_SEMIBOLD   = 600;
+  this.PANGO_WEIGHT_BOLD       = 700;
+  this.PANGO_WEIGHT_ULTRABOLD  = 800;
+  this.PANGO_WEIGHT_HEAVY      = 900;
+  this.PANGO_WEIGHT_ULTRAHEAVY = 1000;
+  this.PANGO_SCALE = 1024;
+
+  this.PangoFontDescription = ctypes.StructType("PangoFontDescription");
+  this.PangoLayout = ctypes.StructType("PangoLayout");
+  this.PangoWeight = ctypes.int; // enum
+
+  lib.lazy_bind("pango_font_description_from_string", this.PangoFontDescription.ptr, ctypes.char.ptr);
+  lib.lazy_bind("pango_font_description_set_weight", ctypes.void_t, this.PangoFontDescription.ptr, this.PangoWeight);
+  lib.lazy_bind("pango_layout_set_spacing", ctypes.void_t, this.PangoLayout.ptr, ctypes.int);
+  lib.lazy_bind("pango_layout_set_font_description", ctypes.void_t, this.PangoLayout.ptr, this.PangoFontDescription.ptr);
+  lib.lazy_bind("pango_layout_set_text", ctypes.void_t, this.PangoLayout.ptr, ctypes.char.ptr, ctypes.int);
+  lib.lazy_bind("pango_layout_get_pixel_size", ctypes.void_t, this.PangoLayout.ptr, ctypes.int.ptr, ctypes.int.ptr);
+  lib.lazy_bind("pango_font_description_get_size", gobject.gint, this.PangoFontDescription.ptr);
+  lib.lazy_bind("pango_font_description_set_size", ctypes.void_t, this.PangoFontDescription.ptr, gobject.gint);
+  lib.lazy_bind("pango_font_description_free", ctypes.void_t, this.PangoFontDescription.ptr);
+
+}
+
+new ctypes_library(PANGO_LIBNAME, PANGO_ABIS, pango_defines, this);
diff --git a/src/modules/ctypes/freebsd/pangocairo.jsm b/src/modules/ctypes/freebsd/pangocairo.jsm
new file mode 100644
index 0000000..e763951
--- /dev/null
+++ b/src/modules/ctypes/freebsd/pangocairo.jsm
@@ -0,0 +1,22 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "pangocairo" ];
+
+const PANGOCAIRO_LIBNAME = "pangocairo-1.0";
+const PANGOCAIRO_ABIS    = [ 0 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/ctypes/ctypes-utils.jsm");
+Cu.import("resource://firetray/ctypes/linux/cairo.jsm");
+Cu.import("resource://firetray/ctypes/linux/gobject.jsm");
+Cu.import("resource://firetray/ctypes/linux/pango.jsm");
+
+function pangocairo_defines(lib) {
+  lib.lazy_bind("pango_cairo_show_layout", ctypes.void_t, cairo.cairo_t.ptr, pango.PangoLayout.ptr);
+}
+
+new ctypes_library(PANGOCAIRO_LIBNAME, PANGOCAIRO_ABIS, pangocairo_defines, this);
diff --git a/src/modules/ctypes/freebsd/x11.jsm b/src/modules/ctypes/freebsd/x11.jsm
new file mode 100644
index 0000000..2895f45
--- /dev/null
+++ b/src/modules/ctypes/freebsd/x11.jsm
@@ -0,0 +1,255 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [
+  "x11",
+  "XATOMS", "XATOMS_ICCCM", "XATOMS_EWMH_GENERAL", "XATOMS_EWMH_WM_STATES",
+  "XPROP_MAX_COUNT", "XPROP_BASE_TYPE", "XPROP_BASE_TYPE_LONG_PROPORTION"
+];
+
+const X11_LIBNAME = "X11";
+const X11_ABIS    = [ 6 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/ctypes/ctypes-utils.jsm");
+
+const XATOMS_ICCCM = [ "WM_DELETE_WINDOW", "WM_STATE", "WM_CHANGE_STATE" ];
+const XATOMS_EWMH_GENERAL = [ "_NET_CLOSE_WINDOW", "_NET_WM_NAME",
+  "_NET_WM_VISIBLE_NAME", "_NET_WM_ICON_NAME", "_NET_WM_VISIBLE_ICON_NAME",
+  "_NET_WM_DESKTOP", "_NET_WM_WINDOW_TYPE", "_NET_WM_STATE",
+  "_NET_WM_ALLOWED_ACTIONS", "_NET_WM_STRUT", "_NET_WM_STRUT_PARTIAL",
+  "_NET_WM_ICON_GEOMETRY", "_NET_WM_ICON", "_NET_WM_PID",
+  "_NET_WM_HANDLED_ICONS", "_NET_WM_USER_TIME", "_NET_FRAME_EXTENTS"
+];
+const XATOMS_EWMH_WM_STATES =  [
+  "_NET_WM_STATE_MODAL", "_NET_WM_STATE_STICKY",
+  "_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_MAXIMIZED_HORZ",
+  "_NET_WM_STATE_SHADED", "_NET_WM_STATE_SKIP_TASKBAR",
+  "_NET_WM_STATE_SKIP_PAGER", "_NET_WM_STATE_HIDDEN",
+  "_NET_WM_STATE_FULLSCREEN", "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_BELOW",
+  "_NET_WM_STATE_DEMANDS_ATTENTION"
+];
+const XATOMS_EWMH_ROOT = [ "_NET_ACTIVE_WINDOW" ]
+const XATOMS = XATOMS_ICCCM
+  .concat(XATOMS_EWMH_WM_STATES)
+  .concat(XATOMS_EWMH_GENERAL)
+  .concat(XATOMS_EWMH_ROOT)
+  .concat(["CARDINAL"]);
+
+
+function x11_defines(lib) {
+  /* fundamental types need to be guessed :-( */
+  // http://mxr.mozilla.org/mozilla-central/source/configure.in
+  if (/^(Alpha|hppa|ia64|ppc64|s390|x86_64)-/.test(Services.appinfo.XPCOMABI)) {
+    this.CARD32 = ctypes.unsigned_int;
+    this.Atom = ctypes.unsigned_long;
+    this.Window = ctypes.unsigned_long;
+    this.Time = ctypes.unsigned_long;
+    this.XID = ctypes.unsigned_long;
+  } else {
+    this.CARD32 = ctypes.unsigned_long;
+    this.Atom = this.CARD32;
+    this.Window = this.CARD32;
+    this.Time =  this.CARD32;
+    this.XID =  this.CARD32;
+  }
+
+  // X.h
+  this.Success          = 0;
+  this.None             = 0;
+  this.AnyPropertyType  = 0;
+  this.BadValue         = 2;
+  this.BadWindow        = 3;
+  this.BadAtom          = 5;
+  this.BadMatch         = 8;
+  this.BadAlloc         = 11;
+  this.PropertyNewValue = 0;
+  this.PropertyDelete   = 1;
+  this.PropModeReplace  = 0;
+  this.PropModePrepend  = 1;
+  this.PropModeAppend   = 2;
+  // Event names
+  this.KeyPress         = 2;
+  this.KeyRelease       = 3;
+  this.ButtonPress      = 4;
+  this.ButtonRelease    = 5;
+  this.MotionNotify     = 6;
+  this.EnterNotify      = 7;
+  this.LeaveNotify      = 8;
+  this.FocusIn          = 9;
+  this.FocusOut         = 10;
+  this.KeymapNotify     = 11;
+  this.Expose           = 12;
+  this.GraphicsExpose   = 13;
+  this.NoExpose         = 14;
+  this.VisibilityNotify = 15;
+  this.CreateNotify     = 16;
+  this.DestroyNotify    = 17;
+  this.UnmapNotify      = 18;
+  this.MapNotify        = 19;
+  this.MapRequest       = 20;
+  this.ReparentNotify   = 21;
+  this.ConfigureNotify  = 22;
+  this.ConfigureRequest = 23;
+  this.GravityNotify    = 24;
+  this.ResizeRequest    = 25;
+  this.CirculateNotify  = 26;
+  this.CirculateRequest = 27;
+  this.PropertyNotify   = 28;
+  this.SelectionClear   = 29;
+  this.SelectionRequest = 30;
+  this.SelectionNotify  = 31;
+  this.ColormapNotify   = 32;
+  this.ClientMessage    = 33;
+  this.MappingNotify    = 34;
+  this.GenericEvent     = 35;
+  this.LASTEvent        = 36; /* must be bigger than any event # */
+  // Xutils.h: definitions for initial window state
+  this.WithdrawnState = 0;      /* for windows that are not mapped */
+  this.NormalState    = 1;      /* most applications want to start this way */
+  this.IconicState    = 3;      /* application wants to start as an icon */
+  // Xatom
+  this.XA_ATOM     = 4;
+  this.XA_CARDINAL = 6;
+  // Input Event Masks
+  this.VisibilityChangeMask     = 1<<16
+  this.StructureNotifyMask      = 1<<17
+  this.SubstructureNotifyMask   = 1<<19;
+  this.SubstructureRedirectMask = 1<<20;
+  this.FocusChangeMask          = 1<<21
+  this.PropertyChangeMask       = 1<<22
+
+  this.Bool = ctypes.int;
+  this.Status = ctypes.int;
+  this.Pixmap = this.XID;
+  this.Cursor = this.XID;
+  this.Colormap = this.XID;
+  this.GContext = this.XID;
+  this.KeySym = this.XID;
+  this.Visual = ctypes.StructType("Visual");
+  this.Screen = ctypes.StructType("Screen");
+  this.Display = ctypes.StructType("Display");
+  // union not supported by js-ctypes
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=535378 "You can always
+  // typecast pointers, at least as long as you know which type is the biggest"
+  this.XEvent = ctypes.void_t;  // union
+  this.XAnyEvent = ctypes.StructType("XAnyEvent", [
+    { "type": ctypes.int },
+    { "serial": ctypes.unsigned_long },
+    { "send_event": this.Bool },
+    { "display": this.Display.ptr },
+    { "window": this.Window }
+  ]);
+  this.XClientMessageEvent = ctypes.StructType("XClientMessageEvent", [
+    { "type": ctypes.int },
+    { "serial": ctypes.unsigned_long },
+    { "send_event": this.Bool },
+    { "display": this.Display.ptr },
+    { "window": this.Window },
+    { "message_type": this.Atom },
+    { "format": ctypes.int },
+    { "data": ctypes.long.array(5) } // actually a union char b[20]; short s[10]; long l[5];
+  ]);
+  this.XPropertyEvent = ctypes.StructType("XPropertyEvent", [
+    { "type": ctypes.int },
+    { "serial": ctypes.unsigned_long },
+    { "send_event": this.Bool },
+    { "display": this.Display.ptr },
+    { "window": this.Window },
+    { "atom": this.Atom },
+    { "time": this.Time },
+    { "state": ctypes.int }     /* NewValue or Deleted */
+  ]);
+
+  this.XWindowAttributes = ctypes.StructType("XWindowAttributes", [
+    { "x": ctypes.int },
+    { "y": ctypes.int },                        /* location of window */
+    { "width": ctypes.int },
+    { "height": ctypes.int },                   /* width and height of window */
+    { "border_width": ctypes.int },             /* border width of window */
+    { "depth": ctypes.int },                    /* depth of window */
+    { "visual": this.Visual.ptr },              /* the associated visual structure */
+    { "root": this.Window },                    /* root of screen containing window */
+    { "class": ctypes.int },                    /* InputOutput, InputOnly*/
+    { "bit_gravity": ctypes.int },              /* one of bit gravity values */
+    { "win_gravity": ctypes.int },              /* one of the window gravity values */
+    { "backing_store": ctypes.int },            /* NotUseful, WhenMapped, Always */
+    { "backing_planes": ctypes.unsigned_long }, /* planes to be preserved if possible */
+    { "backing_pixel": ctypes.unsigned_long },  /* value to be used when restoring planes */
+    { "save_under": this.Bool },                /* boolean, should bits under be saved? */
+    { "colormap": this.Colormap },              /* color map to be associated with window */
+    { "map_installed": this.Bool },             /* boolean, is color map currently installed*/
+    { "map_state": ctypes.int },                /* IsUnmapped, IsUnviewable, IsViewable */
+    { "all_event_masks": ctypes.long },         /* set of events all people have interest in*/
+    { "your_event_mask": ctypes.long },         /* my event mask */
+    { "do_not_propagate_mask": ctypes.long },   /* set of events that should not propagate */
+    { "override_redirect": this.Bool },         /* boolean value for override-redirect */
+    { "screen": this.Screen.ptr }               /* back pointer to correct screen */
+  ]);
+
+  this.XSetWindowAttributes = ctypes.StructType("XSetWindowAttributes", [
+    { "background_pixmap": this.Pixmap },         /* background or None or ParentRelative */
+    { "background_pixel": ctypes.unsigned_long },	/* background pixel */
+    { "border_pixmap": this.Pixmap },             /* border of the window */
+    { "border_pixel": ctypes.unsigned_long },     /* border pixel value */
+    { "bit_gravity": ctypes.int },                /* one of bit gravity values */
+    { "win_gravity": ctypes.int },                /* one of the window gravity values */
+    { "backing_store": ctypes.int },              /* NotUseful, WhenMapped, Always */
+    { "backing_planes": ctypes.unsigned_long },   /* planes to be preseved if possible */
+    { "backing_pixel": ctypes.unsigned_long },    /* value to use in restoring planes */
+    { "save_under": this.Bool },                  /* should bits under be saved? (popups) */
+    { "event_mask": ctypes.long },                /* set of events that should be saved */
+    { "do_not_propagate_mask": ctypes.long },     /* set of events that should not propagate */
+    { "override_redirect": this.Bool },           /* boolean value for override-redirect */
+    { "colormap": this.Colormap },                /* color map to be associated with window */
+    { "cursor": this.Cursor }                     /* cursor to be displayed (or None) */
+  ]);
+
+  lib.lazy_bind("XFree", ctypes.int, ctypes.void_t.ptr);
+  lib.lazy_bind("XInternAtom", this.Atom, this.Display.ptr, ctypes.char.ptr, this.Bool); // only_if_exsits
+  lib.lazy_bind("XGetWindowProperty", ctypes.int, this.Display.ptr, this.Window, this.Atom, ctypes.long, ctypes.long, this.Bool, this.Atom, this.Atom.ptr, ctypes.int.ptr, ctypes.unsigned_long.ptr, ctypes.unsigned_long.ptr, ctypes.unsigned_char.ptr.ptr);
+  lib.lazy_bind("XChangeProperty", ctypes.int, this.Display.ptr, this.Window, this.Atom, this.Atom, ctypes.int, ctypes.int, ctypes.unsigned_char.ptr, ctypes.int);
+  lib.lazy_bind("XDefaultRootWindow", this.Window, this.Display.ptr);
+  lib.lazy_bind("XSendEvent", this.Status, this.Display.ptr, this.Window, this.Bool, ctypes.long, this.XEvent.ptr);
+  lib.lazy_bind("XRaiseWindow", ctypes.int, this.Display.ptr, this.Window);
+  lib.lazy_bind("XGetWindowAttributes", this.Status, this.Display.ptr, this.Window, this.XWindowAttributes.ptr);
+  lib.lazy_bind("XChangeWindowAttributes", ctypes.int, this.Display.ptr, this.Window, ctypes.unsigned_long, this.XSetWindowAttributes.ptr);
+  lib.lazy_bind("XGetSelectionOwner", this.Window, this.Display.ptr, this.Atom);
+  lib.lazy_bind("XGetAtomName", ctypes.char.ptr, this.Display.ptr, this.Atom);
+  lib.lazy_bind("XOpenDisplay", this.Display.ptr, ctypes.char.ptr);
+  lib.lazy_bind("XCloseDisplay", ctypes.int, this.Display.ptr);
+}
+
+new ctypes_library(X11_LIBNAME, X11_ABIS, x11_defines, this);
+
+
+/* Xorg 1.10.4
+#if defined (_LP64) || \
+    defined(__alpha) || defined(__alpha__) || \
+    defined(__ia64__) || defined(ia64) || \
+    defined(__sparc64__) || \
+    defined(__s390x__) || \
+    (defined(__hppa__) && defined(__LP64__)) || \
+    defined(__amd64__) || defined(amd64) || \
+    defined(__powerpc64__) || \
+    (defined(sgi) && (_MIPS_SZLONG == 64))
+#define LONG64
+#endif
+
+# ifdef LONG64
+typedef unsigned long CARD64;
+typedef unsigned int CARD32;
+# else
+typedef unsigned long CARD32;
+# endif
+
+#  ifndef _XSERVER64
+typedef unsigned long Atom;
+#  else
+typedef CARD32 Atom;
+#  endif
+*/
diff --git a/src/modules/freebsd b/src/modules/freebsd
deleted file mode 120000
index 9d646dc..0000000
--- a/src/modules/freebsd
+++ /dev/null
@@ -1 +0,0 @@
-./src/modules/linux
\ No newline at end of file
diff --git a/src/modules/freebsd/FiretrayAppIndicator.jsm b/src/modules/freebsd/FiretrayAppIndicator.jsm
new file mode 100644
index 0000000..b07311c
--- /dev/null
+++ b/src/modules/freebsd/FiretrayAppIndicator.jsm
@@ -0,0 +1,157 @@
+/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "firetray" ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+// FIXME: can't subscribeLibsForClosing([appind3])
+// https://bugs.launchpad.net/ubuntu/+source/firefox/+bug/1393256
+Cu.import("resource://firetray/ctypes/linux/appindicator.jsm");
+Cu.import("resource://firetray/ctypes/linux/gobject.jsm");
+Cu.import("resource://firetray/ctypes/linux/gtk.jsm");
+Cu.import("resource://firetray/commons.js");
+firetray.Handler.subscribeLibsForClosing([gobject, gtk]);
+
+let log = firetray.Logging.getLogger("firetray.AppIndicator");
+
+if ("undefined" == typeof(firetray.StatusIcon))
+  log.error("This module MUST be imported from/after FiretrayStatusIcon !");
+
+
+firetray.AppIndicator = {
+  initialized: false,
+  callbacks: {},
+  indicator: null,
+
+  init: function() {
+    this.indicator = appind3.app_indicator_new(
+      FIRETRAY_APPINDICATOR_ID,
+      firetray.StatusIcon.defaultAppIconName,
+      appind3.APP_INDICATOR_CATEGORY_COMMUNICATIONS
+    );
+    appind3.app_indicator_set_status(this.indicator,
+                                     appind3.APP_INDICATOR_STATUS_ACTIVE);
+    appind3.app_indicator_set_menu(this.indicator,
+                                   firetray.PopupMenu.menu); // mandatory
+    log.debug("indicator="+this.indicator);
+
+    this.addCallbacks();
+
+    for (let item in firetray.PopupMenu.menuItem) {
+      firetray.PopupMenu.showItem(firetray.PopupMenu.menuItem[item]);
+    }
+
+    this.attachMiddleClickCallback();
+    firetray.Handler.setIconTooltipDefault();
+
+    this.initialized = true;
+    return true;
+  },
+
+  shutdown: function() {
+    log.debug("Disabling AppIndicator");
+    gobject.g_object_unref(this.indicator);
+    this.initialized = false;
+  },
+
+  addCallbacks: function() {
+    this.callbacks.connChanged = appind3.ConnectionChangedCb_t(
+      firetray.AppIndicator.onConnectionChanged); // void return, no sentinel
+    gobject.g_signal_connect(this.indicator, "connection-changed",
+                             firetray.AppIndicator.callbacks.connChanged, null);
+
+    this.callbacks.onScroll = appind3.OnScrollCb_t(
+      firetray.AppIndicator.onScroll); // void return, no sentinel
+    gobject.g_signal_connect(this.indicator, "scroll-event",
+                             firetray.AppIndicator.callbacks.onScroll, null);
+  },
+
+  attachMiddleClickCallback: function(pref) {
+    let pref = firetray.Utils.prefService.getIntPref("middle_click");
+    if (pref === FIRETRAY_MIDDLE_CLICK_ACTIVATE_LAST) {
+      item = firetray.PopupMenu.menuItem.activateLast;
+      firetray.PopupMenu.showItem(firetray.PopupMenu.menuItem.activateLast);
+    } else if (pref === FIRETRAY_MIDDLE_CLICK_SHOW_HIDE) {
+      item = firetray.PopupMenu.menuItem.showHide;
+      firetray.PopupMenu.hideItem(firetray.PopupMenu.menuItem.activateLast);
+    } else {
+      log.error("Unknown pref value for 'middle_click': "+pref);
+      return false;
+    }
+    let menuItemShowHideWidget = ctypes.cast(item, gtk.GtkWidget.ptr);
+    appind3.app_indicator_set_secondary_activate_target(
+      this.indicator, menuItemShowHideWidget);
+    return true;
+  },
+
+  onConnectionChanged: function(indicator, connected, data) {
+    log.debug("AppIndicator connection-changed: "+connected);
+  },
+
+  // https://bugs.kde.org/show_bug.cgi?id=340978 broken under KDE4
+  onScroll: function(indicator, delta, direction, data) { // AppIndicator*, gint, GdkScrollDirection, gpointer
+    log.debug("onScroll: "+direction);
+    firetray.StatusIcon.onScroll(direction);
+  },
+
+};  // AppIndicator
+
+firetray.StatusIcon.initImpl =
+  firetray.AppIndicator.init.bind(firetray.AppIndicator);
+
+firetray.StatusIcon.shutdownImpl =
+  firetray.AppIndicator.shutdown.bind(firetray.AppIndicator);
+
+firetray.StatusIcon.middleClickActionChanged = function() {
+  log.debug("middleClickActionChanged");
+  firetray.AppIndicator.attachMiddleClickCallback();
+};
+
+
+firetray.Handler.setIconImageDefault = function() {
+  log.debug("setIconImageDefault");
+  appind3.app_indicator_set_icon(firetray.AppIndicator.indicator,
+                                 firetray.StatusIcon.defaultAppIconName);
+};
+
+firetray.Handler.setIconImageNewMail = function() {
+  log.debug("setIconImageNewMail");
+  appind3.app_indicator_set_icon(firetray.AppIndicator.indicator,
+                                 firetray.StatusIcon.defaultNewMailIconName);
+};
+
+firetray.Handler.setIconImageCustom = function(prefname) {
+  let prefCustomIconPath = firetray.Utils.prefService.getCharPref(prefname);
+  // Undocumented: ok to pass a *path* instead of an icon name! Otherwise we
+  // should be changing the default icons (which is maybe a better
+  // implementation anyway)...
+  appind3.app_indicator_set_icon(firetray.AppIndicator.indicator, prefCustomIconPath);
+};
+
+// No tooltips in AppIndicator
+// https://bugs.launchpad.net/indicator-application/+bug/527458
+firetray.Handler.setIconTooltip = function(toolTipStr) {
+  log.debug("setIconTooltip");
+  if (!firetray.AppIndicator.indicator)
+    return false;
+  firetray.PopupMenu.setItemLabel(firetray.PopupMenu.menuItem.tip,
+                                  toolTipStr);
+  return true;
+};
+
+// AppIndicator doesn't support pixbuf https://bugs.launchpad.net/bugs/812067
+firetray.Handler.setIconText = function(text, color) { };
+
+firetray.Handler.setIconVisibility = function(visible) {
+  if (!firetray.AppIndicator.indicator)
+    return false;
+
+  let status = visible ?
+        appind3.APP_INDICATOR_STATUS_ACTIVE :
+        appind3.APP_INDICATOR_STATUS_PASSIVE;
+  appind3.app_indicator_set_status(firetray.AppIndicator.indicator, status);
+  return true;
+};
diff --git a/src/modules/freebsd/FiretrayChat.jsm b/src/modules/freebsd/FiretrayChat.jsm
new file mode 100644
index 0000000..539500d
--- /dev/null
+++ b/src/modules/freebsd/FiretrayChat.jsm
@@ -0,0 +1,332 @@
+/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "firetray" ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource:///modules/imServices.jsm");
+Cu.import("resource://firetray/commons.js");
+Cu.import("resource://firetray/linux/FiretrayChatStatusIcon.jsm");
+Cu.import("resource://firetray/linux/FiretrayWindow.jsm");
+
+let log = firetray.Logging.getLogger("firetray.Chat");
+
+firetray.Chat = {
+  initialized: false,
+  observedTopics: {},
+  convsToAcknowledge: {
+    ids: {},
+    length: function(){return Object.keys(this.ids).length;}
+  },
+
+  init: function() {
+    if (this.initialized) {
+      log.warn("Chat already initialized");
+      return true;
+    }
+    log.debug("Enabling Chat");
+
+    firetray.Utils.addObservers(firetray.Chat, [
+      // "*", // debugging
+      "account-connected", "account-disconnected", "idle-time-changed",
+      "new-directed-incoming-message", "status-changed",
+      "unread-im-count-changed", "new-text"
+    ]);
+
+    firetray.ChatStatusIcon.init();
+    if (firetray.Utils.prefService.getBoolPref("chat_icon_blink") &&
+        firetray.Chat.convsToAcknowledge.length())
+      this.startGetAttention();
+    this.updateIcon();
+
+    this.initialized = true;
+    return true;
+  },
+
+  shutdown: function() {
+    if (!this.initialized) return false;
+    log.debug("Disabling Chat");
+
+    if (firetray.Chat.convsToAcknowledge.length())
+      this.stopGetAttention();
+
+    firetray.ChatStatusIcon.shutdown();
+    firetray.Utils.removeAllObservers(firetray.Chat);
+
+    this.initialized = false;
+    return true;
+  },
+
+  // FIXME: the listener should probably attached on the conv entry in the
+  // contactlist during startGetAttentionMaybe
+  attachSelectListeners: function(win) {
+    log.debug("attachSelectListeners");
+    ["contactlistbox", "tabmail"].forEach(function(eltId) {
+      win.document.getElementById(eltId)
+        .addEventListener('select', firetray.Chat.onSelect);
+    });
+  },
+
+  detachSelectListeners: function(win) {
+    ["contactlistbox", "tabmail"].forEach(function(eltId) {
+      win.document.getElementById(eltId)
+        .removeEventListener('select', firetray.Chat.onSelect);
+    });
+  },
+
+  observe: function(subject, topic, data) {
+    log.debug("RECEIVED Chat: "+topic+" subject="+subject+" data="+data);
+    let conv = null;
+
+    switch (topic) {
+    case "account-connected":
+    case "account-disconnected":
+    case "idle-time-changed":
+    case "status-changed":
+      this.updateIcon();
+      break;
+
+    case "new-directed-incoming-message": // when PM or cited in channel
+      conv = subject.QueryInterface(Ci.prplIMessage).conversation;
+      log.debug("conversation name="+conv.name); // normalizedName shouldn't be necessary
+      this.startGetAttentionMaybe(conv);
+      break;
+
+    /* Twitter is obviously considered a chatroom, not a private
+     conversation. This is why we need to detect incoming messages and switch
+     to the conversation differently. The actual read should be caught by
+     focus-in-event and 'select' event on tabmail and contactlist */
+    case "new-text":
+      let msg = subject.QueryInterface(Ci.prplIMessage);
+      conv = msg.conversation;
+      log.debug("new-text from "+conv.title);
+      let account = conv.account.QueryInterface(Ci.imIAccount);
+      let proto = account.protocol;
+
+      log.debug("msg from "+msg.who+", alias="+msg.alias+", account.normalizedName="+account.normalizedName);
+      if (msg.who === account.normalizedName) break; // ignore msg from self
+      if (proto.normalizedName !== 'twitter') break;
+      this.startGetAttentionMaybe(conv);
+      break;
+
+    case "unread-im-count-changed":
+      log.debug("unread-im-count-changed");
+      let unreadMsgCount = data;
+      if (unreadMsgCount == 0)
+        this.stopGetAttentionMaybe(firetray.Handler.getActiveWindow());
+
+      let localizedTooltip = PluralForm.get(
+        unreadMsgCount,
+        firetray.Utils.strings.GetStringFromName("tooltip.unread_messages"))
+        .replace("#1", unreadMsgCount);
+      firetray.ChatStatusIcon.setIconTooltip(localizedTooltip);
+      break;
+
+    default:
+      log.warn("unhandled topic: "+topic);
+    }
+  },
+
+  startGetAttentionMaybe: function(conv) {
+    log.debug('startGetAttentionMaybe conv.id='+conv.id);
+
+    let convIsCurrentlyShown =
+          this.isConvCurrentlyShown(conv, firetray.Handler.getActiveWindow());
+    log.debug("convIsCurrentlyShown="+convIsCurrentlyShown);
+    if (convIsCurrentlyShown) return; // don't blink when conv tab already on top
+
+    log.debug("firetray.ChatStatusIcon.isBlinking="+firetray.ChatStatusIcon.isBlinking);
+    if (firetray.Utils.prefService.getBoolPref("chat_icon_blink") &&
+        !firetray.ChatStatusIcon.isBlinking)
+      this.startGetAttention(conv);
+
+    this.convsToAcknowledge.ids[conv.id] = conv;
+    log.debug(conv.id+' added to convsToAcknowledge, length='+this.convsToAcknowledge.length());
+  },
+
+  startGetAttention: function(conv) {
+    log.debug("startGetAttention");
+    if (conv)
+      this.setUrgencyMaybe(conv);
+
+    let blinkStyle = firetray.Utils.prefService.getIntPref("chat_icon_blink_style");
+    log.debug("chat_icon_blink_style="+blinkStyle);
+    if (blinkStyle === FIRETRAY_CHAT_ICON_BLINK_STYLE_NORMAL)
+      firetray.ChatStatusIcon.startBlinking();
+    else if (blinkStyle === FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE)
+      firetray.ChatStatusIcon.startFading();
+    else
+      throw new Error("Undefined chat icon blink style.");
+  },
+
+  /**
+   * @param xid id of the window that MUST have initiated this event
+   */
+  stopGetAttentionMaybe: function(xid) {
+    log.debug("stopGetAttentionMaybe");
+    log.debug("convsToAcknowledgeLength="+this.convsToAcknowledge.length());
+    if (!firetray.ChatStatusIcon.isBlinking) return;
+
+    let selectedConv = this.getSelectedConv(xid);
+    if (!selectedConv) return;
+
+    for (let convId in this.convsToAcknowledge.ids) {
+      log.debug(convId+" == "+selectedConv.id);
+      if (convId == selectedConv.id) {
+        delete this.convsToAcknowledge.ids[convId];
+        break;
+      }
+    }
+
+    // don't check chat_icon_blink: stopGetAttention even if it was unset
+    log.debug("convsToAcknowledge.length()="+this.convsToAcknowledge.length());
+    if (this.convsToAcknowledge.length() === 0)
+      this.stopGetAttention(xid);
+  },
+
+  stopGetAttention: function(xid) {
+    log.debug("do stop get attention !!!");
+    if (xid)
+      firetray.ChatStatusIcon.setUrgency(xid, false);
+
+    let blinkStyle = firetray.Utils.prefService.getIntPref("chat_icon_blink_style");
+    if (blinkStyle === FIRETRAY_CHAT_ICON_BLINK_STYLE_NORMAL)
+      firetray.ChatStatusIcon.stopBlinking();
+    else if (blinkStyle === FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE)
+      firetray.ChatStatusIcon.stopFading();
+    else
+      throw new Error("Undefined chat icon blink style.");
+  },
+
+  onSelect: function(event) {
+    log.debug("select event ! ");
+    firetray.Chat.stopGetAttentionMaybe(firetray.Handler.getActiveWindow());
+  },
+
+  isConvCurrentlyShown: function(conv, activeWin) {
+    log.debug("isConvCurrentlyShown");
+    let selectedConv = this.getSelectedConv(activeWin);
+    if (!selectedConv) return false;
+
+    log.debug("conv.title='"+conv.title+"' selectedConv.title='"+selectedConv.title+"'");
+    return (conv.id == selectedConv.id);
+  },
+
+  getSelectedConv: function(activeWin) {
+    if (!firetray.Handler.windows[activeWin]) return null;
+    log.debug("getSelectedConv *");
+
+    let activeChatTab = this.findSelectedChatTab(activeWin);
+    if (!activeChatTab) return null;
+    log.debug("getSelectedConv **");
+
+    /* for now there is only one Chat tab, so we don't need to
+     findSelectedChatTabFromTab(activeChatTab.tabNode). And, as there is only
+     one forlderPaneBox, there will also probably be only one contactlistbox
+     for all Chat tabs anyway */
+    let selectedConv = this.findSelectedConv(activeWin);
+    if (!selectedConv) return null;
+    log.debug("getSelectedConv ***");
+
+    return selectedConv;
+  },
+
+  findSelectedChatTab: function(xid) {
+    let win = firetray.Handler.windows[xid].chromeWin;
+    let tabmail = win.document.getElementById("tabmail");
+    let chatTabs = tabmail.tabModes.chat.tabs;
+    for each (let tab in chatTabs)
+      if (tab.tabNode.selected) return tab;
+    return null;
+  },
+
+  findSelectedConv: function(xid) {
+    let win = firetray.Handler.windows[xid].chromeWin;
+    let selectedItem = win.document.getElementById("contactlistbox").selectedItem;
+    if (!selectedItem || selectedItem.localName != "imconv") return null;
+    return selectedItem.conv;
+  },
+
+  /* there can potentially be multiple windows, each with a Chat tab and the
+   same conv open... so we need to handle urgency for all windows */
+  setUrgencyMaybe: function(conv) {
+    for (let xid in firetray.Handler.windows) {
+      let win = firetray.Handler.windows[xid].chromeWin;
+      let contactlist = win.document.getElementById("contactlistbox");
+      for (let i=0; i<contactlist.itemCount; ++i) {
+        let item = contactlist.getItemAtIndex(i);
+        if (item.localName !== 'imconv')
+          continue;
+        /* item.conv is only initialized if chat tab is open */
+        if (item.hasOwnProperty('conv') && item.conv.target === conv) {
+          firetray.Window.setUrgency(xid, true);
+          break;
+        }
+      }
+    }
+  },
+
+  updateIcon: function() {
+    let globalConnectedStatus = this.globalConnectedStatus();
+    let userStatus;
+    if (globalConnectedStatus)
+      userStatus = Services.core.globalUserStatus.statusType;
+    else
+      userStatus = Ci.imIStatusInfo.STATUS_OFFLINE;
+    log.debug("IM status="+userStatus);
+
+    let iconName;
+    switch (userStatus) {
+    case Ci.imIStatusInfo.STATUS_OFFLINE:     // 1
+      iconName = FIRETRAY_IM_STATUS_OFFLINE;
+      break;
+    case Ci.imIStatusInfo.STATUS_IDLE:        // 4
+    case Ci.imIStatusInfo.STATUS_AWAY:        // 5
+      iconName = FIRETRAY_IM_STATUS_AWAY;
+      break;
+    case Ci.imIStatusInfo.STATUS_AVAILABLE:   // 7
+      iconName = FIRETRAY_IM_STATUS_AVAILABLE;
+      break;
+    case Ci.imIStatusInfo.STATUS_UNAVAILABLE: // 6
+      iconName = FIRETRAY_IM_STATUS_BUSY;
+      break;
+    case Ci.imIStatusInfo.STATUS_UNKNOWN:     // 0
+    case Ci.imIStatusInfo.STATUS_INVISIBLE:   // 2
+    case Ci.imIStatusInfo.STATUS_MOBILE:      // 3
+    default:                                  // ignore
+    }
+
+    log.debug("IM status changed="+iconName);
+    if (iconName)
+      firetray.ChatStatusIcon.setIconImage(iconName);
+  },
+
+  globalConnectedStatus: function() {
+    /* Because we may already be connected during init (for ex. when toggling
+     the chat_icon_enable pref), we need to updateIcon() during init(). But IM
+     accounts' list is not initialized at early stage... */
+    try {
+
+      let accounts = Services.accounts.getAccounts();
+      let globalConnected = false;
+
+      while (accounts.hasMoreElements()) {
+        let account = accounts.getNext().QueryInterface(Ci.imIAccount);
+        log.debug("account="+account+" STATUS="+account.statusInfo.statusType+" connected="+account.connected);
+        globalConnected = globalConnected || account.connected;
+      }
+      log.debug("globalConnected="+globalConnected);
+      return globalConnected;
+
+    } catch (e if e instanceof Components.Exception &&
+             e.result === Components.results.NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS &&
+             /_items is undefined/.test(e.message)) {
+      return false;             // ignore
+    } catch(e) {
+      log.error(e); return false;
+    }
+  }
+
+};
diff --git a/src/modules/freebsd/FiretrayChatStatusIcon.jsm b/src/modules/freebsd/FiretrayChatStatusIcon.jsm
new file mode 100644
index 0000000..17646d4
--- /dev/null
+++ b/src/modules/freebsd/FiretrayChatStatusIcon.jsm
@@ -0,0 +1,301 @@
+/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+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/gdk.jsm");
+Cu.import("resource://firetray/ctypes/linux/gio.jsm");
+Cu.import("resource://firetray/ctypes/linux/gobject.jsm");
+Cu.import("resource://firetray/ctypes/linux/gtk.jsm");
+Cu.import("resource://firetray/linux/FiretrayWindow.jsm");
+Cu.import("resource://firetray/commons.js");
+firetray.Handler.subscribeLibsForClosing([gdk, gio, gobject, gtk]);
+
+if ("undefined" == typeof(firetray.Handler))
+  log.error("This module MUST be imported from/after FiretrayHandler !");
+
+let log = firetray.Logging.getLogger("firetray.ChatStatusIcon");
+
+const ALPHA_STEP                       = 5;
+const ALPHA_STEP_SLEEP_MILLISECONDS    = 10;
+const FADE_OVER_SLEEP_MILLISECONDS     = 500;
+const BLINK_TOGGLE_PERIOD_MILLISECONDS = 500;
+
+
+firetray.ChatStatusIcon = {
+  GTK_THEME_ICON_PATH: null,
+
+  initialized: false,
+  trayIcon: null,
+  appId:      (function(){return Services.appinfo.ID;})(),
+  themedIcons: (function(){let o = {};
+    o[FIRETRAY_IM_STATUS_AVAILABLE] = null;
+    o[FIRETRAY_IM_STATUS_AWAY] = null;
+    o[FIRETRAY_IM_STATUS_BUSY] = null;
+    o[FIRETRAY_IM_STATUS_OFFLINE] = null;
+    return o;
+  })(),
+  themedIconNameCurrent: null,
+  timers: {'blink': null, 'fade-step': null, 'fade-loop': null},
+  events: {},
+  generators: {},
+  pixBuffer: {},
+  get isBlinking () {return (firetray.Chat.convsToAcknowledge.length() > 0);},
+
+  init: function() {
+    if (!firetray.Handler.appHasChat) throw "ChatStatusIcon for chat app only";
+    firetray.GtkIcons.init();
+
+    this.trayIcon = gtk.gtk_status_icon_new();
+    this.loadThemedIcons();
+    this.setIconImage(this.themedIconNameCurrent || FIRETRAY_IM_STATUS_OFFLINE); // updated in Chat anyway
+    this.setIconTooltipDefault();
+    this.initTimers();
+
+    this.initialized = true;
+    return true;
+  },
+
+  shutdown: function() {
+    this.destroyTimers();
+    this.destroyIcons();
+    this.initialized = false;
+  },
+
+  loadThemedIcons: function() {
+    for (let name in this.themedIcons)
+      this.themedIcons[name] = gio.g_themed_icon_new(name);
+  },
+
+  destroyIcons: function() {
+    for (let name in this.themedIcons) {
+      let gicon = this.themedIcons[name];
+      gicon = gobject.g_object_unref(gicon);
+    }
+    gobject.g_object_unref(this.trayIcon);
+  },
+
+  setIconImageFromGIcon: function(gicon) {
+    if (!firetray.ChatStatusIcon.trayIcon || !gicon)
+      log.error("Icon missing");
+    gtk.gtk_status_icon_set_from_gicon(firetray.ChatStatusIcon.trayIcon, gicon);
+  },
+
+  setIconImage: function(name) {
+    this.themedIconNameCurrent = name;
+
+    let blinkStyle = firetray.Utils.prefService.getIntPref("chat_icon_blink_style");
+    if (blinkStyle === FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE &&
+        this.isBlinking) {
+      this.events['icon-changed'] = true;
+      return;
+    }
+
+    this.setIconImageFromGIcon(this.themedIcons[name]);
+  },
+
+  setIconVoid: function() {
+    gtk.gtk_status_icon_set_from_pixbuf(this.trayIcon, null);
+  },
+
+  initTimers: function() {
+    for (let tname in this.timers)
+      this.timers[tname] = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+  },
+
+  destroyTimers: function() {
+    for (let tname in this.timers) {
+      this.timers[tname].cancel();
+      this.timers[tname] = null;
+    }
+    this.events = {};
+  },
+
+  buildPixBuf: function() {
+    let icon_theme = gtk.gtk_icon_theme_get_for_screen(gdk.gdk_screen_get_default());
+
+    // get pixbuf
+    let arry = gobject.gchar.ptr.array()(2);
+    arry[0] = gobject.gchar.array()(firetray.ChatStatusIcon.themedIconNameCurrent);
+    arry[1] = null;
+    log.debug("icon name="+firetray.ChatStatusIcon.themedIconNameCurrent+", theme="+icon_theme+", arry="+arry);
+    let icon_info = gtk.gtk_icon_theme_choose_icon(icon_theme, arry, 22, gtk.GTK_ICON_LOOKUP_FORCE_SIZE);
+
+    // create pixbuf
+    let pixbuf = gdk.gdk_pixbuf_copy(gtk.gtk_icon_info_load_icon(icon_info, null));
+    gtk.gtk_icon_info_free(icon_info);   // gobject.g_object_unref(icon_info) in 3.8
+
+    // checks
+    if (gdk.gdk_pixbuf_get_colorspace(pixbuf) != gdk.GDK_COLORSPACE_RGB)
+      log.error("wrong colorspace for pixbuf");
+    if (gdk.gdk_pixbuf_get_bits_per_sample(pixbuf) != 8)
+      log.error("wrong bits_per_sample for pixbuf");
+    if (!gdk.gdk_pixbuf_get_has_alpha(pixbuf))
+      log.error("pixbuf doesn't have alpha");
+    let n_channels = gdk.gdk_pixbuf_get_n_channels(pixbuf);
+    if (n_channels != 4)
+      log.error("wrong nb of channels for pixbuf");
+
+    // init transform
+    let width = gdk.gdk_pixbuf_get_width(pixbuf);
+    let height = gdk.gdk_pixbuf_get_height(pixbuf);
+    log.debug("width="+width+", height="+height);
+    let length = width*height*n_channels;
+    let pixels = ctypes.cast(gdk.gdk_pixbuf_get_pixels(pixbuf),
+                             gobject.guchar.array(length).ptr);
+    log.debug("pixels="+pixels);
+
+    // backup alpha for later fade-in
+    let buffer = new ArrayBuffer(width*height);
+    let alpha_bak = new Uint8Array(buffer);
+    for (let i=3; i<length; i+=n_channels)
+      alpha_bak[(i-3)/n_channels] = pixels.contents[i];
+
+    log.debug("pixbuf created");
+    this.pixBuffer = {
+      pixbuf: pixbuf,           // TO BE UNREFED WITH to g_object_unref() !!
+      width: width,
+      height: height,
+      length: length,
+      n_channels: n_channels,
+      pixels: pixels,
+      buffer: buffer,
+      alpha_bak: alpha_bak
+    };
+  },
+  dropPixBuf: function() {
+    gobject.g_object_unref(this.pixBuffer.pixbuf);
+    log.debug("pixbuf unref'd");
+    this.pixBuffer = {};
+  },
+
+  fadeGenerator: function() {
+    let pixbuf = firetray.ChatStatusIcon.pixBuffer;
+
+    for (let a=255; a>0; a-=ALPHA_STEP) {
+      for(let i=3; i<pixbuf.length; i+=pixbuf.n_channels)
+        if (pixbuf.pixels.contents[i]-ALPHA_STEP>0)
+          pixbuf.pixels.contents[i] -= ALPHA_STEP;
+      gtk.gtk_status_icon_set_from_pixbuf(firetray.ChatStatusIcon.trayIcon, pixbuf.pixbuf);
+      yield true;
+    }
+
+    for (let a=255; a>0; a-=ALPHA_STEP) {
+      for(let i=3; i<pixbuf.length; i+=pixbuf.n_channels)
+        if (pixbuf.pixels.contents[i]+ALPHA_STEP<=pixbuf.alpha_bak[(i-3)/pixbuf.n_channels]) {
+          pixbuf.pixels.contents[i] += ALPHA_STEP;
+        }
+      gtk.gtk_status_icon_set_from_pixbuf(firetray.ChatStatusIcon.trayIcon, pixbuf.pixbuf);
+      yield true;
+    }
+  },
+
+  fadeStep: function() {
+    try {
+      if (firetray.ChatStatusIcon.generators['fade'].next())
+        firetray.ChatStatusIcon.timers['fade-step'].initWithCallback(
+          { notify: firetray.ChatStatusIcon.fadeStep },
+          ALPHA_STEP_SLEEP_MILLISECONDS, Ci.nsITimer.TYPE_ONE_SHOT);
+
+    } catch (e if e instanceof StopIteration) {
+
+      if (firetray.ChatStatusIcon.events['stop-fade']) {
+        log.debug("stop-fade");
+        delete firetray.ChatStatusIcon.events['stop-fade'];
+        delete firetray.ChatStatusIcon.generators['fade'];
+        firetray.ChatStatusIcon.setIconImage(firetray.ChatStatusIcon.themedIconNameCurrent);
+        firetray.ChatStatusIcon.dropPixBuf();
+        return;
+      }
+
+      if (firetray.ChatStatusIcon.events['icon-changed']) {
+        delete firetray.ChatStatusIcon.events['icon-changed'];
+        firetray.ChatStatusIcon.dropPixBuf();
+        firetray.ChatStatusIcon.buildPixBuf();
+        firetray.ChatStatusIcon.timers['fade-loop'].initWithCallback(
+          { notify: firetray.ChatStatusIcon.fadeLoop },
+          FADE_OVER_SLEEP_MILLISECONDS, Ci.nsITimer.TYPE_ONE_SHOT);
+
+      } else {
+        firetray.ChatStatusIcon.timers['fade-loop'].initWithCallback(
+          { notify: firetray.ChatStatusIcon.fadeLoop },
+          FADE_OVER_SLEEP_MILLISECONDS, Ci.nsITimer.TYPE_ONE_SHOT);
+      }
+    };
+  },
+
+  fadeLoop: function() {
+    firetray.ChatStatusIcon.generators['fade'] = firetray.ChatStatusIcon.fadeGenerator();
+    firetray.ChatStatusIcon.fadeStep();
+  },
+
+  startFading: function() {
+    log.debug("startFading");
+    this.buildPixBuf();
+    this.fadeLoop();
+  },
+
+  stopFading: function() {
+    log.debug("stopFading");
+    this.events['stop-fade'] = true;
+  },
+
+  startBlinking: function() { // gtk_status_icon_set_blinking() deprecated
+    this.on = true;
+    firetray.ChatStatusIcon.timers['blink'].initWithCallback({
+      notify: function() {
+        if (firetray.ChatStatusIcon.on)
+          firetray.ChatStatusIcon.setIconVoid();
+        else
+          firetray.ChatStatusIcon.setIconImage(firetray.ChatStatusIcon.themedIconNameCurrent);
+        firetray.ChatStatusIcon.on = !firetray.ChatStatusIcon.on;
+      }
+    }, BLINK_TOGGLE_PERIOD_MILLISECONDS, Ci.nsITimer.TYPE_REPEATING_SLACK);
+  },
+
+  stopBlinking: function() {
+    log.debug("stopBlinking");
+    this.timers['blink'].cancel();
+    this.setIconImage(firetray.ChatStatusIcon.themedIconNameCurrent);
+    this.on = false;
+  },
+
+  toggleBlinkStyle: function(blinkStyle) {
+    switch (blinkStyle) {
+    case FIRETRAY_CHAT_ICON_BLINK_STYLE_NORMAL:
+      this.stopFading();
+      this.startBlinking();
+      break;
+    case FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE:
+      this.stopBlinking();
+      this.startFading();
+      break;
+    default:
+      throw new Error("Undefined chat icon blink style.");
+    }
+  },
+
+  setUrgency: function(xid, urgent) {
+    gtk.gtk_window_set_urgency_hint(firetray.Handler.gtkWindows.get(xid), urgent);
+  },
+
+  setIconTooltip: function(txt) {
+    if (!this.trayIcon) return false;
+    gtk.gtk_status_icon_set_tooltip_text(this.trayIcon, txt);
+    return true;
+  },
+
+  setIconTooltipDefault: function() {
+    this.setIconTooltip(firetray.Handler.appName+" Chat");
+  }
+
+  // TODO: onclick/activate -> chatHandler.showCurrentConversation()
+
+}; // firetray.ChatStatusIcon
diff --git a/src/modules/freebsd/FiretrayGtkIcons.jsm b/src/modules/freebsd/FiretrayGtkIcons.jsm
new file mode 100644
index 0000000..ecdd438
--- /dev/null
+++ b/src/modules/freebsd/FiretrayGtkIcons.jsm
@@ -0,0 +1,67 @@
+/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "firetray" ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://firetray/ctypes/linux/gtk.jsm");
+Cu.import("resource://firetray/commons.js");
+firetray.Handler.subscribeLibsForClosing([gtk]);
+
+if ("undefined" == typeof(firetray.StatusIcon))
+  log.error("This module MUST be imported from/after StatusIcon !");
+
+let log = firetray.Logging.getLogger("firetray.GtkIcons");
+
+
+firetray.GtkIcons = {
+  initialized: false,
+
+  GTK_THEME_ICON_PATH: null,
+
+  init: function() {
+    try {
+      if (this.initialized) return true;
+
+      this.appendSearchPath();
+      this.initialized = true;
+      return true;
+    } catch (x) {
+      log.error(x);
+      return false;
+    }
+  },
+
+  shutdown: function() {
+    // FIXME: XXX destroy icon here
+    this.initialized = false;
+  },
+
+  appendSearchPath: function() {
+    this.GTK_THEME_ICON_PATH = firetray.Utils.chromeToPath("chrome://firetray/skin/icons/linux");
+    log.debug(this.GTK_THEME_ICON_PATH);
+    let gtkIconTheme = gtk.gtk_icon_theme_get_default();
+    log.debug("gtkIconTheme="+gtkIconTheme);
+    gtk.gtk_icon_theme_append_search_path(gtkIconTheme, this.GTK_THEME_ICON_PATH);
+
+    if (log.level <= firetray.Logging.LogMod.Level.Debug) {
+      Cu.import("resource://firetray/ctypes/linux/glib.jsm");
+      Cu.import("resource://firetray/ctypes/linux/gobject.jsm");
+      firetray.Handler.subscribeLibsForClosing([glib, gobject]);
+      let path = new gobject.gchar.ptr.ptr;
+      let n_elements = new gobject.gint;
+      gtk.gtk_icon_theme_get_search_path(gtkIconTheme, path.address(), n_elements.address());
+      log.debug("n_elements="+n_elements+" path="+path);
+      let pathIt = path;
+      for (let i=0, len=n_elements.value; i<len || pathIt.isNull(); ++i) {
+        log.debug("path["+i+"]="+pathIt.contents.readString());
+        pathIt = pathIt.increment();
+      }
+      log.debug("path="+path+" pathIt="+pathIt);
+      glib.g_strfreev(path);
+    }
+  }
+
+};
diff --git a/src/modules/freebsd/FiretrayGtkStatusIcon.jsm b/src/modules/freebsd/FiretrayGtkStatusIcon.jsm
new file mode 100644
index 0000000..87ceaab
--- /dev/null
+++ b/src/modules/freebsd/FiretrayGtkStatusIcon.jsm
@@ -0,0 +1,341 @@
+/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "firetray" ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/ctypes/linux/cairo.jsm");
+Cu.import("resource://firetray/ctypes/linux/gdk.jsm");
+Cu.import("resource://firetray/ctypes/linux/gio.jsm");
+Cu.import("resource://firetray/ctypes/linux/gobject.jsm");
+Cu.import("resource://firetray/ctypes/linux/gtk.jsm");
+Cu.import("resource://firetray/ctypes/linux/pango.jsm");
+Cu.import("resource://firetray/ctypes/linux/pangocairo.jsm");
+Cu.import("resource://firetray/linux/FiretrayGtkIcons.jsm");
+Cu.import("resource://firetray/commons.js");
+firetray.Handler.subscribeLibsForClosing([cairo, gdk, gio, gobject, gtk, pango,
+  pangocairo]);
+
+let log = firetray.Logging.getLogger("firetray.GtkStatusIcon");
+
+if ("undefined" == typeof(firetray.Handler))
+  log.error("This module MUST be imported from/after FiretrayStatusIcon !");
+
+
+firetray.GtkStatusIcon = {
+  MIN_FONT_SIZE: 4,
+  FILENAME_BLANK: null,
+  GTK_THEME_ICON_PATH: null,
+
+  initialized: false,
+  callbacks: {},
+  trayIcon: null,
+  themedIconApp: null,
+  themedIconNewMail: null,
+
+  init: function() {
+    this.FILENAME_BLANK = firetray.Utils.chromeToPath(
+      "chrome://firetray/skin/icons/blank-icon.png");
+
+    firetray.GtkIcons.init();
+    this.loadThemedIcons();
+
+    this.trayIcon = gtk.gtk_status_icon_new();
+    firetray.Handler.setIconImageDefault();
+    firetray.Handler.setIconTooltipDefault();
+    this.addCallbacks();
+
+    this.initialized = true;
+    return true;
+  },
+
+  shutdown: function() {
+    log.debug("Disabling GtkStatusIcon");
+    firetray.GtkIcons.shutdown();
+    // FIXME: XXX destroy icon here
+    this.initialized = false;
+  },
+
+  loadThemedIcons: function() {
+    if (firetray.Handler.inMailApp) {
+      let newMailIconNames = firetray.StatusIcon.getNewMailIconNames();
+      if (this.themedIconNewMail) gobject.g_object_unref(this.themedIconNewMail);
+      this.themedIconNewMail = this.initThemedIcon(newMailIconNames);
+    }
+    let appIconNames = firetray.StatusIcon.getAppIconNames();
+    if (this.themedIconApp) gobject.g_object_unref(this.themedIconApp);
+    this.themedIconApp = this.initThemedIcon(appIconNames);
+  },
+
+  initThemedIcon: function(names) {
+    if (!firetray.js.isArray(names)) throw new TypeError();
+    log.debug("themedIconNames="+names);
+    let namesLen = names.length;
+    log.debug("themedIconNamesLen="+namesLen);
+    let themedIconNames = ctypes.char.ptr.array(namesLen)();
+    for (let i=0; i<namesLen; ++i)
+      themedIconNames[i] = ctypes.char.array()(names[i]);
+    log.debug("themedIconNames="+themedIconNames);
+    let themedIcon = gio.g_themed_icon_new_from_names(themedIconNames, namesLen);
+    log.debug("themedIcon="+themedIcon);
+    return themedIcon;
+  },
+
+  addCallbacks: function() {
+    Cu.import("resource://firetray/linux/FiretrayPopupMenu.jsm");
+    /* NOTE: here we do use a function handler (instead of a function
+     definition) because we need the args passed to it ! As a consequence, we
+     need to abandon 'this' in PopupMenu.popup() */
+    this.callbacks.menuPopup = gtk.GCallbackMenuPopup_t(firetray.PopupMenu.popup); // void return, no sentinel
+    gobject.g_signal_connect(this.trayIcon, "popup-menu",
+      firetray.GtkStatusIcon.callbacks.menuPopup, firetray.PopupMenu.menu);
+    this.callbacks.onScroll = gtk.GCallbackOnScroll_t(
+      firetray.GtkStatusIcon.onScroll, null, FIRETRAY_CB_SENTINEL);
+    gobject.g_signal_connect(this.trayIcon, "scroll-event",
+      firetray.GtkStatusIcon.callbacks.onScroll, null);
+
+    log.debug("showHideAllWindows: "+firetray.Handler.hasOwnProperty("showHideAllWindows"));
+    this.callbacks.iconActivate = gtk.GCallbackStatusIconActivate_t(
+      firetray.GtkStatusIcon.onClick, null, FIRETRAY_CB_SENTINEL);
+    let handlerId = gobject.g_signal_connect(firetray.GtkStatusIcon.trayIcon,
+      "activate", firetray.GtkStatusIcon.callbacks.iconActivate, null);
+    log.debug("g_connect activate="+handlerId);
+
+    let pref = firetray.Utils.prefService.getIntPref("middle_click");
+    this.attachMiddleClickCallback(pref);
+  },
+
+  attachMiddleClickCallback: function(pref) {
+    log.debug("attachMiddleClickCallback pref="+pref);
+    if (pref === FIRETRAY_MIDDLE_CLICK_ACTIVATE_LAST) {
+      this.callbacks.iconMiddleClick = gtk.GCallbackStatusIconMiddleClick_t(
+        firetray.Handler.activateLastWindowCb, null, FIRETRAY_CB_SENTINEL);
+    } else if (pref === FIRETRAY_MIDDLE_CLICK_SHOW_HIDE) {
+      this.callbacks.iconMiddleClick = gtk.GCallbackStatusIconMiddleClick_t(
+        function(widget, event, data) {firetray.Handler.showHideAllWindows(); return true;},
+        null, FIRETRAY_CB_SENTINEL);
+    } else {
+      log.error("Unknown pref value for 'middle_click': "+pref);
+      return;
+    }
+    this.callbacks.iconMiddleClickId = gobject.g_signal_connect(
+      firetray.GtkStatusIcon.trayIcon,
+      "button-press-event", firetray.GtkStatusIcon.callbacks.iconMiddleClick,
+      null);
+    log.debug("g_connect middleClick="+this.callbacks.iconMiddleClickId);
+  },
+
+  detachMiddleClickCallback: function() {
+    log.debug("detachMiddleClickCallback");
+    gobject.g_signal_handler_disconnect(
+      firetray.GtkStatusIcon.trayIcon,
+      gobject.gulong(this.callbacks.iconMiddleClickId)
+    );
+    delete this.callbacks.iconMiddleClickId;
+  },
+
+  onScroll: function(icon, event, data) {
+    let gdkEventScroll = ctypes.cast(event, gdk.GdkEventScroll.ptr);
+    let direction = gdkEventScroll.contents.direction;
+
+    firetray.StatusIcon.onScroll(direction);
+
+    let stopPropagation = false;
+    return stopPropagation;
+  },
+
+  onClick: function(gtkStatusIcon, userData) {
+    firetray.Handler.showHideAllWindows();
+    let stopPropagation = true;
+    return stopPropagation;
+  },
+
+  setIconImageFromFile: function(filename) {
+    if (!firetray.GtkStatusIcon.trayIcon)
+      log.error("Icon missing");
+    log.debug(filename);
+    gtk.gtk_status_icon_set_from_file(firetray.GtkStatusIcon.trayIcon,
+                                      filename);
+  },
+
+  setIconImageFromGIcon: function(gicon) {
+    if (!firetray.GtkStatusIcon.trayIcon || !gicon)
+      log.error("Icon missing");
+    log.debug(gicon);
+    gtk.gtk_status_icon_set_from_gicon(firetray.GtkStatusIcon.trayIcon, gicon);
+  },
+
+};                              // GtkStatusIcon
+
+firetray.StatusIcon.initImpl =
+  firetray.GtkStatusIcon.init.bind(firetray.GtkStatusIcon);
+
+firetray.StatusIcon.shutdownImpl =
+  firetray.GtkStatusIcon.shutdown.bind(firetray.GtkStatusIcon);
+
+firetray.StatusIcon.middleClickActionChanged = function() {
+  log.debug("middleClickActionChanged");
+  let pref = firetray.Utils.prefService.getIntPref("middle_click");
+  firetray.GtkStatusIcon.detachMiddleClickCallback();
+  firetray.GtkStatusIcon.attachMiddleClickCallback(pref);
+};
+
+
+firetray.Handler.loadIcons = firetray.GtkStatusIcon.loadThemedIcons;
+
+firetray.Handler.setIconImageDefault = function() {
+  log.debug("setIconImageDefault");
+  if (!firetray.GtkStatusIcon.themedIconApp)
+    throw "Default application themed icon not set";
+  let appIconType = firetray.Utils.prefService.getIntPref("app_icon_type");
+  if (appIconType === FIRETRAY_APPLICATION_ICON_TYPE_THEMED) {
+    firetray.GtkStatusIcon.setIconImageFromGIcon(
+      firetray.GtkStatusIcon.themedIconApp);
+  } else if (appIconType === FIRETRAY_APPLICATION_ICON_TYPE_CUSTOM) {
+    firetray.Handler.setIconImageCustom("app_icon_custom");
+  }
+};
+
+firetray.Handler.setIconImageNewMail = function() {
+  firetray.GtkStatusIcon.setIconImageFromGIcon(
+    firetray.GtkStatusIcon.themedIconNewMail);
+};
+
+firetray.Handler.setIconImageCustom = function(prefname) {
+  let prefCustomIconPath = firetray.Utils.prefService.getCharPref(prefname);
+  firetray.GtkStatusIcon.setIconImageFromFile(prefCustomIconPath);
+};
+
+// GTK bug: Gdk-CRITICAL **: IA__gdk_window_get_root_coords: assertion `GDK_IS_WINDOW (window)' failed
+firetray.Handler.setIconTooltip = function(toolTipStr) {
+  if (!firetray.GtkStatusIcon.trayIcon)
+    return false;
+
+  log.debug("setIconTooltip, toolTipStr="+toolTipStr);
+  try {
+    gtk.gtk_status_icon_set_tooltip_text(firetray.GtkStatusIcon.trayIcon,
+                                         toolTipStr);
+  } catch (x) {
+    log.error(x);
+    return false;
+  }
+  return true;
+};
+
+firetray.Handler.setIconText = function(text, color) { // FIXME: function too long
+  log.debug("setIconText, color="+color);
+  if (typeof(text) != "string")
+    throw new TypeError();
+
+  try {
+    // build background from image
+    let specialIcon = gdk.gdk_pixbuf_new_from_file(
+      firetray.GtkStatusIcon.FILENAME_BLANK, null); // GError **error);
+    let dest = gdk.gdk_pixbuf_copy(specialIcon);
+    let w = gdk.gdk_pixbuf_get_width(specialIcon);
+    let h = gdk.gdk_pixbuf_get_height(specialIcon);
+
+    // prepare colors/alpha
+    let colorMap = gdk.gdk_screen_get_system_colormap(gdk.gdk_screen_get_default());
+    let visual = gdk.gdk_colormap_get_visual(colorMap);
+    let visualDepth = visual.contents.depth;
+    log.debug("colorMap="+colorMap+" visual="+visual+" visualDepth="+visualDepth);
+    let fore = new gdk.GdkColor;
+    fore.pixel = fore.red = fore.green = fore.blue = 0;
+    let alpha  = new gdk.GdkColor;
+    alpha.pixel = alpha.red = alpha.green = alpha.blue = 0xFFFF;
+    if (!fore || !alpha)
+      log.warn("Undefined GdkColor fore or alpha");
+    gdk.gdk_color_parse(color, fore.address());
+    if(fore.red == alpha.red && fore.green == alpha.green && fore.blue == alpha.blue) {
+      alpha.red=0; // make sure alpha is different from fore
+    }
+    gdk.gdk_colormap_alloc_color(colorMap, fore.address(), true, true);
+    gdk.gdk_colormap_alloc_color(colorMap, alpha.address(), true, true);
+
+    // build pixmap with rectangle
+    let pm = gdk.gdk_pixmap_new(null, w, h, visualDepth);
+    let pmDrawable = ctypes.cast(pm, gdk.GdkDrawable.ptr);
+    let cr = gdk.gdk_cairo_create(pmDrawable);
+    gdk.gdk_cairo_set_source_color(cr, alpha.address());
+    cairo.cairo_rectangle(cr, 0, 0, w, h);
+    cairo.cairo_set_source_rgb(cr, 1, 1, 1);
+    cairo.cairo_fill(cr);
+
+    // build text
+    let scratch = gtk.gtk_window_new(gtk.GTK_WINDOW_TOPLEVEL);
+    let layout = gtk.gtk_widget_create_pango_layout(scratch, null);
+    gtk.gtk_widget_destroy(scratch);
+    let fnt = pango.pango_font_description_from_string("Sans 18");
+    pango.pango_font_description_set_weight(fnt,pango.PANGO_WEIGHT_SEMIBOLD);
+    pango.pango_layout_set_spacing(layout,0);
+    pango.pango_layout_set_font_description(layout, fnt);
+    log.debug("layout="+layout);
+    log.debug("text="+text);
+    pango.pango_layout_set_text(layout, text,-1);
+    let tw = new ctypes.int;
+    let th = new ctypes.int;
+    let sz;
+    let border = 4;
+    pango.pango_layout_get_pixel_size(layout, tw.address(), th.address());
+    log.debug("tw="+tw.value+" th="+th.value);
+    // fit text to the icon by decreasing font size
+    while ( tw.value > (w - border) || th.value > (h - border) ) {
+      sz = pango.pango_font_description_get_size(fnt);
+      if(sz < firetray.GtkStatusIcon.MIN_FONT_SIZE) {
+        sz = firetray.GtkStatusIcon.MIN_FONT_SIZE;
+        break;
+      }
+      sz -= pango.PANGO_SCALE;
+      pango.pango_font_description_set_size(fnt,sz);
+      pango.pango_layout_set_font_description(layout, fnt);
+      pango.pango_layout_get_pixel_size(layout, tw.address(), th.address());
+    }
+    log.debug("tw="+tw.value+" th="+th.value);
+    pango.pango_font_description_free(fnt);
+    // center text
+    let px = (w-tw.value)/2;
+    let py = (h-th.value)/2;
+
+    // draw text on pixmap
+    gdk.gdk_cairo_set_source_color(cr, fore.address());
+    cairo.cairo_move_to(cr, px, py);
+    pangocairo.pango_cairo_show_layout(cr, layout);
+    cairo.cairo_destroy(cr);
+    gobject.g_object_unref(layout);
+
+    let buf = gdk.gdk_pixbuf_get_from_drawable(null, pmDrawable, null, 0, 0, 0, 0, w, h);
+    gobject.g_object_unref(pm);
+    log.debug("alpha="+alpha);
+    let alphaRed = gobject.guint16(alpha.red);
+    let alphaRed_guchar = ctypes.cast(alphaRed, gobject.guchar);
+    let alphaGreen = gobject.guint16(alpha.green);
+    let alphaGreen_guchar = ctypes.cast(alphaGreen, gobject.guchar);
+    let alphaBlue = gobject.guint16(alpha.blue);
+    let alphaBlue_guchar = ctypes.cast(alphaBlue, gobject.guchar);
+    let bufAlpha = gdk.gdk_pixbuf_add_alpha(buf, true, alphaRed_guchar, alphaGreen_guchar, alphaBlue_guchar);
+    gobject.g_object_unref(buf);
+
+    // merge the rendered text on top
+    gdk.gdk_pixbuf_composite(bufAlpha,dest,0,0,w,h,0,0,1,1,gdk.GDK_INTERP_BILINEAR,255);
+    gobject.g_object_unref(bufAlpha);
+
+    log.debug("gtk_status_icon_set_from_pixbuf="+dest);
+    gtk.gtk_status_icon_set_from_pixbuf(firetray.GtkStatusIcon.trayIcon, dest);
+  } catch (x) {
+    log.error(x);
+    return false;
+  }
+
+  return true;
+};
+
+firetray.Handler.setIconVisibility = function(visible) {
+  if (!firetray.GtkStatusIcon.trayIcon)
+    return false;
+  gtk.gtk_status_icon_set_visible(firetray.GtkStatusIcon.trayIcon, visible);
+  return true;
+};
diff --git a/src/modules/freebsd/FiretrayPopupMenu.jsm b/src/modules/freebsd/FiretrayPopupMenu.jsm
new file mode 100644
index 0000000..21f0eec
--- /dev/null
+++ b/src/modules/freebsd/FiretrayPopupMenu.jsm
@@ -0,0 +1,262 @@
+/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+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/linux/gobject.jsm");
+Cu.import("resource://firetray/ctypes/linux/gtk.jsm");
+Cu.import("resource://firetray/commons.js");
+firetray.Handler.subscribeLibsForClosing([gobject, gtk]);
+
+let log = firetray.Logging.getLogger("firetray.PopupMenu");
+
+if ("undefined" == typeof(firetray.StatusIcon))
+  log.error("This module MUST be imported from/after StatusIcon !");
+
+
+firetray.PopupMenu = {
+  MENU_ITEM_WINDOWS_POSITION: 4,
+
+  initialized: false,
+  callbacks: {menuItemWindowActivate: {}}, // FIXME: try to store them into a ctypes array/struct.
+  menu: null,
+  menuShell: null,
+  menuSeparatorWindows: null,
+  menuItem: {tip: null, showHide: null, activateLast: null, sep: null},
+
+  init: function() {
+    this.menu = gtk.gtk_menu_new();
+    this.menuShell = ctypes.cast(this.menu, gtk.GtkMenuShell.ptr);
+    var addMenuSeparator = false;
+
+    if (firetray.Handler.inMailApp) {
+      this.addItem({itemName:"ResetIcon", iconName:"gtk-apply",
+                    action:"activate", callback: firetray.Handler.setIconImageDefault});
+      this.addItem({itemName:"NewMessage", iconName:"gtk-edit",
+                    action:"activate", callback: firetray.Handler.openMailMessage});
+      addMenuSeparator = true;
+    }
+
+    if (firetray.Handler.inBrowserApp) {
+      this.addItem({itemName:"NewWindow", iconName:"gtk-new",
+                    action:"activate", callback: firetray.Handler.openBrowserWindow});
+      addMenuSeparator = true;
+    }
+
+    var menuSeparator;
+    if (addMenuSeparator) {
+      menuSeparator = gtk.gtk_separator_menu_item_new();
+      gtk.gtk_menu_shell_append(this.menuShell, ctypes.cast(menuSeparator,
+                                                            gtk.GtkWidget.ptr));
+    }
+
+    this.addItem({itemName:"Preferences", iconName:"gtk-preferences",
+                  action:"activate", callback: firetray.Handler.openPrefWindow});
+    menuSeparator = gtk.gtk_separator_menu_item_new();
+    gtk.gtk_menu_shell_append(this.menuShell, ctypes.cast(menuSeparator,
+                                                          gtk.GtkWidget.ptr));
+
+    this.addItem({itemName:"Quit", iconName:"gtk-quit",
+                  action:"activate", callback: firetray.Handler.quitApplication});
+
+    var menuWidget = ctypes.cast(this.menu, gtk.GtkWidget.ptr);
+    gtk.gtk_widget_show_all(menuWidget);
+
+    // for hidden windows, not shown otherwise
+    this.menuSeparatorWindows = gtk.gtk_separator_menu_item_new();
+    gtk.gtk_menu_shell_prepend(
+      this.menuShell, ctypes.cast(this.menuSeparatorWindows, gtk.GtkWidget.ptr));
+    // FIXME: we better use a submenu for this: gtk_menu_new(), gtk_menu_item_set_submenu();
+
+    // for AppIndicator, not shown otherwise
+    this.prependAppIndicatorItems();
+
+    this.initialized = true;
+    return true;
+  },
+
+  shutdown: function() {
+    log.debug("Disabling PopupMenu");
+    this.initialized = false;
+  },
+
+  addItem: function(it) {
+    var menuItemLabel = firetray.Utils.strings.GetStringFromName("popupMenu.itemLabel."+it.itemName); // shouldn't need to convert to utf8 later thank to js-ctypes
+    var menuItem = gtk.gtk_image_menu_item_new_with_label(menuItemLabel);
+    var menuItemIcon = gtk.gtk_image_new_from_stock(it.iconName, gtk.GTK_ICON_SIZE_MENU);
+    gtk.gtk_image_menu_item_set_image(menuItem, menuItemIcon);
+    var menuItemWidget = ctypes.cast(menuItem, gtk.GtkWidget.ptr);
+    if (it.inFront)
+      gtk.gtk_menu_shell_prepend(this.menuShell, menuItemWidget);
+    else
+      gtk.gtk_menu_shell_append(this.menuShell, menuItemWidget);
+
+    function capitalizeFirst(str) {
+      return str.charAt(0).toUpperCase() + str.substring(1);
+    }
+
+    let cbName = "menuItem"+capitalizeFirst(it.itemName)+capitalizeFirst(it.action);
+    if (this.callbacks.hasOwnProperty(cbName))
+      log.warn("callback '"+cbName+"' already registered");
+    else
+      log.debug("cbName="+cbName);
+    this.callbacks[cbName] = gobject.GCallback_t(it.callback); // void return, no sentinel
+    gobject.g_signal_connect(menuItem, it.action,
+                             firetray.PopupMenu.callbacks[cbName], null);
+
+    return menuItem;
+  },
+
+  prependAppIndicatorItems: function() {
+    this.menuItem.sep = gtk.gtk_separator_menu_item_new();
+    gtk.gtk_menu_shell_prepend(this.menuShell, ctypes.cast(this.menuItem.sep,
+                                                           gtk.GtkWidget.ptr));
+
+    this.menuItem.activateLast = this.addItem({
+      itemName:"ActivateLast", iconName:null, action:"activate", callback:
+      firetray.Handler.showAllWindowsAndActivate, inFront: true});
+
+    this.menuItem.showHide = this.addItem({
+      itemName:"ShowHide", iconName:"gtk-go-down", action:"activate", callback:
+      firetray.Handler.showHideAllWindows, inFront: true});
+
+    this.menuItem.tip = this.createAndAddItemToMenuAt(0);
+    gtk.gtk_widget_set_sensitive(
+      ctypes.cast(this.menuItem.tip, gtk.GtkWidget.ptr), false);
+  },
+
+  popup: function(icon, button, activateTime, menu) {
+    log.debug("menu-popup");
+    log.debug("ARGS="+icon+", "+button+", "+activateTime+", "+menu);
+
+    var gtkMenuPtr = ctypes.cast(menu, gtk.GtkMenu.ptr);
+    var iconGpointer = ctypes.cast(icon, gobject.gpointer);
+    gtk.gtk_menu_popup(
+      gtkMenuPtr, null, null, gtk.gtk_status_icon_position_menu,
+      iconGpointer, button, activateTime);
+
+    let stopPropagation = false;
+    return stopPropagation;
+  },
+
+  // we'll be creating menuItems for windows (and not showing them) even if
+  // hides_single_window is false, because if hides_single_window becomes true,
+  // we'll just have to show the menuItems
+  addWindowItem: function(xid) { // on registerWindow
+    log.debug("addWindowItem");
+    var menuItemWindow = this.createAndAddItemToMenuAt(
+      this.MENU_ITEM_WINDOWS_POSITION);
+    firetray.Handler.gtkPopupMenuWindowItems.insert(xid, menuItemWindow);
+    this.setItemLabel(menuItemWindow, xid.toString()); // default to xid
+
+    let callback = gobject.GCallback_t(
+      function(){firetray.Handler.showWindow(xid);}); // void return, no sentinel
+    this.callbacks.menuItemWindowActivate[xid] = callback,
+    gobject.g_signal_connect(menuItemWindow, "activate", callback, null);
+
+    log.debug("added gtkPopupMenuWindowItems: "+firetray.Handler.gtkPopupMenuWindowItems.count);
+  },
+
+  createAndAddItemToMenuAt: function(pos) {
+    var menuItem = gtk.gtk_image_menu_item_new();
+    gtk.gtk_menu_shell_insert(this.menuShell,
+                              ctypes.cast(menuItem, gtk.GtkWidget.ptr),
+                              pos);
+    return menuItem;
+  },
+
+  removeWindowItem: function(xid) { // on unregisterWindow
+    let menuItemWindow = firetray.Handler.gtkPopupMenuWindowItems.get(xid);
+    firetray.Handler.gtkPopupMenuWindowItems.remove(xid);
+    this.removeItem(menuItemWindow);
+    log.debug("remove gtkPopupMenuWindowItems: "+firetray.Handler.gtkPopupMenuWindowItems.count);
+  },
+  removeItem: function(item) {
+    gtk.gtk_widget_destroy(ctypes.cast(item, gtk.GtkWidget.ptr));
+  },
+
+  showAllWindowItemsOnlyVisibleWindows: function() {
+    for (let xid in firetray.Handler.windows)
+      if (!firetray.Handler.windows[xid].visible)
+        this.showWindowItem(xid);
+  },
+
+  showWindowItem: function(xid) {
+    if (!this.windowItemsHandled())
+      return;
+
+    log.debug("showWindowItem");
+    let menuItemWindow = firetray.Handler.gtkPopupMenuWindowItems.get(xid);
+    this.showItem(menuItemWindow);
+    this.setItemLabel(menuItemWindow, firetray.Window.getWindowTitle(xid));
+    this.showWindowSeparator();
+  },
+
+  showItem: function(menuItem) {
+    gtk.gtk_widget_show(ctypes.cast(menuItem, gtk.GtkWidget.ptr));
+  },
+
+  setItemLabel: function(menuItem, label) {
+    log.debug("about to set title: "+label);
+    if (label)
+      gtk.gtk_menu_item_set_label(ctypes.cast(menuItem, gtk.GtkMenuItem.ptr), label);
+  },
+
+  hideAllWindowItems: function() {
+    for (let xid in firetray.Handler.windows)
+      this.hideWindowItemAndSeparator(xid);
+  },
+
+  hideWindowItemAndSeparator: function(xid) {
+    this.hideWindowItem(xid);
+    this.hideWindowSeparator();
+  },
+
+  hideWindowItemAndSeparatorMaybe: function(xid) {
+    if (!this.windowItemsHandled()) return;
+
+    this.hideWindowItem(xid);
+    if (firetray.Handler.visibleWindowsCount === firetray.Handler.windowsCount)
+      this.hideWindowSeparator();
+  },
+
+  hideWindowItem: function(xid) {
+    log.debug("hideWindowItem");
+    let menuItemWindow = firetray.Handler.gtkPopupMenuWindowItems.get(xid);
+    this.hideItem(menuItemWindow);
+  },
+
+  hideItem: function(menuItem) {
+    gtk.gtk_widget_hide(ctypes.cast(menuItem, gtk.GtkWidget.ptr));
+  },
+
+  showWindowSeparator: function() {
+    log.debug("showing menuSeparatorWindows");
+    gtk.gtk_widget_show(ctypes.cast(this.menuSeparatorWindows, gtk.GtkWidget.ptr));
+  },
+  hideWindowSeparator: function() {
+    log.debug("hiding menuSeparatorWindows");
+    gtk.gtk_widget_hide(ctypes.cast(this.menuSeparatorWindows, gtk.GtkWidget.ptr));
+  },
+
+  showHideWindowItems: function() {
+    if (this.windowItemsHandled())
+      this.showAllWindowItemsOnlyVisibleWindows();
+    else
+      this.hideAllWindowItems();
+  },
+
+  windowItemsHandled: function() {
+    return firetray.Utils.prefService.getBoolPref('hides_single_window');
+  }
+
+}; // firetray.PopupMenu
+
+firetray.Handler.showHidePopupMenuItems =
+  firetray.PopupMenu.showHideWindowItems.bind(firetray.PopupMenu);
diff --git a/src/modules/freebsd/FiretrayStatusIcon.jsm b/src/modules/freebsd/FiretrayStatusIcon.jsm
new file mode 100644
index 0000000..642d478
--- /dev/null
+++ b/src/modules/freebsd/FiretrayStatusIcon.jsm
@@ -0,0 +1,219 @@
+/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+var EXPORTED_SYMBOLS = [ "firetray" ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://firetray/ctypes/linux/gdk.jsm");
+Cu.import("resource://firetray/ctypes/linux/gio.jsm");
+Cu.import("resource://firetray/ctypes/linux/glib.jsm");
+Cu.import("resource://firetray/ctypes/linux/gobject.jsm");
+Cu.import("resource://firetray/ctypes/linux/libc.jsm");
+Cu.import("resource://firetray/ctypes/linux/x11.jsm");
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://firetray/commons.js");
+firetray.Handler.subscribeLibsForClosing([gdk, gio, glib, gobject]);
+
+let log = firetray.Logging.getLogger("firetray.StatusIcon");
+
+if ("undefined" == typeof(firetray.Handler))
+  log.error("This module MUST be imported from/after FiretrayHandler !");
+
+
+firetray.StatusIcon = {
+  initialized: false,
+  callbacks: {}, // pointers to JS functions. MUST LIVE DURING ALL THE EXECUTION
+  prefAppIconNames: null,
+  prefNewMailIconNames: null,
+  defaultAppIconName: null,
+  defaultNewMailIconName: null,
+  canAppIndicator: null,
+
+  init: function() {
+    this.defineIconNames();
+
+    // PopupMenu g_connect's some Handler functions. As these are overridden is
+    // StatusIcon implementations, PopupMenu must be initialized *after*
+    // implemenations are imported.
+    Cu.import("resource://firetray/ctypes/linux/appindicator.jsm");
+    this.canAppIndicator =
+      (appind3.available() && this.dbusNotificationWatcherReady());
+    log.info("canAppIndicator="+this.canAppIndicator);
+    /* We can't reliably detect if xembed tray icons are supported, because, for
+     instance, Unity/compiz falsely claims to have support for it through
+     _NET_SYSTEM_TRAY_Sn (compiz). So we end up using the desktop id as a
+     criteria for enabling appindicator. */
+    let desktop = this.getDesktop();
+    log.info("desktop="+JSON.stringify(desktop));
+
+    if (firetray.Utils.prefService.getBoolPref('with_appindicator') &&
+        this.canAppIndicator &&
+        (desktop.name === 'unity' ||
+         (desktop.name === 'kde' && desktop.ver > 4))) {
+      Cu.import("resource://firetray/linux/FiretrayAppIndicator.jsm");
+      /* FIXME: Ubuntu14.04/Unity: successfully closing appind3 crashes FF/TB
+       during exit, in Ubuntu's unity-menubar.patch's code.
+       https://bugs.launchpad.net/ubuntu/+source/firefox/+bug/1393256 */
+      // firetray.Handler.subscribeLibsForClosing([appind3]);
+    } else {
+      Cu.import("resource://firetray/linux/FiretrayGtkStatusIcon.jsm");
+    }
+
+    Cu.import("resource://firetray/linux/FiretrayPopupMenu.jsm");
+    if (!firetray.PopupMenu.init())
+      return false;
+
+    if (!firetray.StatusIcon.initImpl())
+      return false;
+
+    this.initialized = true;
+    return true;
+  },
+
+  shutdown: function() {
+    log.debug("Disabling StatusIcon");
+    firetray.StatusIcon.shutdownImpl();
+    firetray.PopupMenu.shutdown();
+    this.initialized = false;
+  },
+
+  defineIconNames: function() {
+    this.prefAppIconNames = (function() {
+      if (firetray.Handler.inMailApp) {
+        return "app_mail_icon_names";
+      } else if (firetray.Handler.inBrowserApp) {
+        return "app_browser_icon_names";
+      } else {
+        return "app_default_icon_names";
+      }
+    })();
+    this.defaultAppIconName = firetray.Handler.appName.toLowerCase();
+
+    this.prefNewMailIconNames = "new_mail_icon_names";
+    this.defaultNewMailIconName = "mail-unread";
+  },
+
+  getAppIconNames: function() {
+    let appIconNames = firetray.Utils.getArrayPref(
+      firetray.StatusIcon.prefAppIconNames);
+    appIconNames.push(firetray.StatusIcon.defaultAppIconName);
+    return appIconNames;
+  },
+  getNewMailIconNames: function() {
+    let newMailIconNames = firetray.Utils.getArrayPref(
+      firetray.StatusIcon.prefNewMailIconNames);
+    newMailIconNames.push(firetray.StatusIcon.defaultNewMailIconName);
+    return newMailIconNames;
+  },
+
+  loadImageCustom: function() { }, // done in setIconImageCustom
+
+  getDesktop: function() {
+    let env = Cc["@mozilla.org/process/environment;1"]
+          .createInstance(Ci.nsIEnvironment);
+    let XDG_CURRENT_DESKTOP = env.get("XDG_CURRENT_DESKTOP").toLowerCase();
+    let DESKTOP_SESSION = env.get("DESKTOP_SESSION").toLowerCase();
+
+    let desktop = {name:'unknown', ver:null};
+    if (XDG_CURRENT_DESKTOP === 'unity' || DESKTOP_SESSION === 'ubuntu') {
+      desktop.name = 'unity';
+    }
+    else if (XDG_CURRENT_DESKTOP === 'kde') { // DESKTOP_SESSION kde-plasma, plasme, kf5, ...
+      desktop.name = 'kde';
+      let KDE_SESSION_VERSION = env.get("KDE_SESSION_VERSION");
+      if (KDE_SESSION_VERSION) desktop.ver = parseInt(KDE_SESSION_VERSION, 10);
+    }
+    else if (DESKTOP_SESSION) {
+      desktop.name = DESKTOP_SESSION;
+    }
+    else if (XDG_CURRENT_DESKTOP) {
+      desktop.name = XDG_CURRENT_DESKTOP;
+    }
+
+    return desktop;
+  },
+
+  dbusNotificationWatcherReady: function() {
+    let watcherReady = false;
+
+    function error(e) {
+      if (!e.isNull()) {
+        log.error(e.contents.message);
+        glib.g_error_free(e);
+      }
+    }
+
+    let conn = new gio.GDBusConnection.ptr;
+    let err = new glib.GError.ptr(null);
+    conn = gio.g_bus_get_sync(gio.G_BUS_TYPE_SESSION, null, err.address());
+    if (error(err)) return watcherReady;
+
+    if (!conn.isNull()) {
+      let flags = gio.G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START |
+            gio.G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+            gio.G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS;
+
+      let proxy = gio.g_dbus_proxy_new_for_bus_sync(
+        gio.G_BUS_TYPE_SESSION,
+        flags,
+        null, /* GDBusInterfaceInfo */
+        appind3.NOTIFICATION_WATCHER_DBUS_ADDR,
+        appind3.NOTIFICATION_WATCHER_DBUS_OBJ,
+        appind3.NOTIFICATION_WATCHER_DBUS_IFACE,
+        null, /* GCancellable */
+        err.address());
+      if (error(err)) return watcherReady;
+
+      if (!proxy.isNull()) {
+        let owner = gio.g_dbus_proxy_get_name_owner(proxy);
+        if (!owner.isNull()) {
+          watcherReady = true;
+        }
+        gobject.g_object_unref(proxy);
+      }
+
+      gobject.g_object_unref(conn);
+    }
+
+    return watcherReady;
+  },
+
+  onScroll: function(direction) {
+    if (!firetray.Utils.prefService.getBoolPref("scroll_hides"))
+      return false;
+
+    let scroll_mode = firetray.Utils.prefService.getCharPref("scroll_mode");
+    switch(direction) {
+    case gdk.GDK_SCROLL_UP:
+      log.debug("SCROLL UP");
+      if (scroll_mode === "down_hides")
+        firetray.Handler.showAllWindows();
+      else if (scroll_mode === "up_hides")
+        firetray.Handler.hideAllWindows();
+      break;
+    case gdk.GDK_SCROLL_DOWN:
+      log.debug("SCROLL DOWN");
+      if (scroll_mode === "down_hides")
+        firetray.Handler.hideAllWindows();
+      else if (scroll_mode === "up_hides")
+        firetray.Handler.showAllWindows();
+      break;
+    default:
+      log.error("SCROLL UNKNOWN");
+    }
+
+    return true;
+  }
+
+}; // firetray.StatusIcon
+
+
+firetray.Handler.setIconTooltipDefault = function() {
+  if (!this.appName)
+    throw "application name not initialized";
+  this.setIconTooltip(this.appName);
+};
+
+firetray.Handler.setIconTooltip = function(toolTipStr) { };
diff --git a/src/modules/freebsd/FiretrayWindow.jsm b/src/modules/freebsd/FiretrayWindow.jsm
new file mode 100644
index 0000000..1a53252
--- /dev/null
+++ b/src/modules/freebsd/FiretrayWindow.jsm
@@ -0,0 +1,761 @@
+/* -*- 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/FiretrayWindow.jsm");
+Cu.import("resource://firetray/commons.js");
+firetray.Handler.subscribeLibsForClosing([gobject, gdk, gtk, libc, x11, glib]);
+
+let log = firetray.Logging.getLogger("firetray.Window");
+
+if ("undefined" == typeof(firetray.Handler))
+  log.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 = new FiretrayWindow();
+firetray.Window.signals = {'focus-in': {callback: {}, handler: {}}};
+
+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());
+
+  if (firetray.Handler.isChatEnabled()) {
+    Cu.import("resource://firetray/linux/FiretrayChat.jsm");
+    Cu.import("resource://firetray/linux/FiretrayChatStatusIcon.jsm");
+  }
+
+  this.initialized = true;
+};
+
+firetray.Window.shutdown = function() {
+  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), himself inspired by Windows docs
+ * @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);
+
+  // Tag the base window
+  let oldTitle = baseWindow.title;
+  log.debug("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); // void return, no sentinel
+    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);
+
+    if (userData.contents.outWindow.isNull())
+      throw new Error("Window not found!");
+
+    log.debug("found window: "+userData.contents.outWindow);
+  } catch (x) {
+    log.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
+ */
+firetray.Window._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.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 getRegisteredWinIdFromChromeWindow() 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.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.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.getVisibility = function(xid) {
+  let gtkWidget = ctypes.cast(firetray.Handler.gtkWindows.get(xid), gtk.GtkWidget.ptr);
+  // nsIBaseWin.visibility always true
+  return gtk.gtk_widget_get_visible(gtkWidget);
+};
+
+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);
+};
+
+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<dataSize; ++i)
+    xev.data[i] = data[i];
+
+  let rootWin = x11.XDefaultRootWindow(x11.current.Display);
+  let propagate = false;
+  let mask = ctypes.long(x11.SubstructureNotifyMask|x11.SubstructureRedirectMask);
+  // fortunately, it's OK not to cast xev. ctypes.cast to a void_t doesn't work (length pb)
+  let status = x11.XSendEvent(x11.current.Display, rootWin, propagate, mask, xev.address());
+  // always returns 1 (BadRequest as a coincidence)
+};
+
+/**
+ * raises window on top and give focus.
+ */
+firetray.Window.activate = function(xid) {
+  // broken in KDE ?
+  gtk.gtk_window_present(firetray.Handler.gtkWindows.get(xid));
+  log.debug("window raised");
+};
+
+firetray.Window.setUrgency = function(xid, urgent) {
+  log.debug("setUrgency: "+urgent);
+  gtk.gtk_window_set_urgency_hint(firetray.Handler.gtkWindows.get(xid), urgent);
+};
+
+/**
+ * YOU MUST x11.XFree() THE VARIABLE RETURNED BY THIS FUNCTION
+ * @param xwin: a x11.Window
+ * @param prop: a x11.Atom
+ */
+firetray.Window.getXWindowProperties = function(xwin, prop) {
+  // infos returned by XGetWindowProperty() - FIXME: should be freed ?
+  let actual_type = new x11.Atom;
+  let actual_format = new ctypes.int;
+  let nitems = new ctypes.unsigned_long;
+  let bytes_after = new ctypes.unsigned_long;
+  let prop_value = new ctypes.unsigned_char.ptr;
+
+  let bufSize = XATOMS_EWMH_WM_STATES.length*ctypes.unsigned_long.size;
+  let offset = 0;
+  let res = x11.XGetWindowProperty(
+    x11.current.Display, xwin, prop, offset, bufSize, 0, x11.AnyPropertyType,
+    actual_type.address(), actual_format.address(), nitems.address(),
+    bytes_after.address(), prop_value.address());
+  log.debug("XGetWindowProperty res="+res+", actual_type="+actual_type.value+", actual_format="+actual_format.value+", bytes_after="+bytes_after.value+", nitems="+nitems.value);
+
+  if (!firetray.js.strEquals(res, x11.Success)) {
+    log.error("XGetWindowProperty failed");
+    return [null, null];
+  }
+  if (firetray.js.strEquals(actual_type.value, x11.None)) {
+    log.debug("property not found");
+    return [null, null];
+  }
+
+  log.debug("prop_value="+prop_value+", size="+prop_value.constructor.size);
+  /* If the returned format is 32, the property data will be stored as an
+   array of longs (which in a 64-bit application will be 64-bit values
+   that are padded in the upper 4 bytes). [man XGetWindowProperty] */
+  if (actual_format.value !== 32) {
+    log.error("unsupported format: "+actual_format.value);
+  }
+  log.debug("format OK");
+  var props = ctypes.cast(prop_value, ctypes.unsigned_long.array(nitems.value).ptr);
+  log.debug("props="+props+", size="+props.constructor.size);
+
+  return [props, nitems];
+};
+
+/**
+ * check the state of a window by its EWMH window state. This is more
+ * accurate than the chromeWin.windowState or the GdkWindowState which are
+ * based on WM_STATE. For instance, WM_STATE becomes 'Iconic' on virtual
+ * desktop change...
+ */
+firetray.Window.getXWindowStates = function(xwin) {
+  let winStates = 0;
+
+  let [propsFound, nitems] =
+        firetray.Window.getXWindowProperties(xwin, x11.current.Atoms._NET_WM_STATE);
+  log.debug("propsFound, nitems="+propsFound+", "+nitems);
+  if (!propsFound) return 0;
+
+  let maximizedHorz = maximizedVert = false;
+  for (let i=0, len=nitems.value; i<len; ++i) {
+    log.debug("i: "+propsFound.contents[i]);
+    let currentProp = propsFound.contents[i];
+    if (firetray.js.strEquals(currentProp, x11.current.Atoms['_NET_WM_STATE_HIDDEN']))
+      winStates |= FIRETRAY_XWINDOW_HIDDEN;
+    else if (firetray.js.strEquals(currentProp, x11.current.Atoms['_NET_WM_STATE_MAXIMIZED_HORZ']))
+      maximizedHorz = true;
+    else if (firetray.js.strEquals(currentProp, x11.current.Atoms['_NET_WM_STATE_MAXIMIZED_VERT']))
+      maximizedVert = true;
+  }
+
+  if (maximizedHorz && maximizedVert)
+    winStates |= FIRETRAY_XWINDOW_MAXIMIZED;
+
+  x11.XFree(propsFound);
+
+  return winStates;
+};
+
+firetray.Window.getXWindowDesktop = function(xwin) {
+  let desktop = null;
+
+  let [propsFound, nitems] =
+        firetray.Window.getXWindowProperties(xwin, x11.current.Atoms._NET_WM_DESKTOP);
+  log.debug("DESKTOP propsFound, nitems="+propsFound+", "+nitems);
+  if (!propsFound) return null;
+
+  if (firetray.js.strEquals(nitems.value, 0))
+    log.warn("desktop number not found");
+  else if (firetray.js.strEquals(nitems.value, 1))
+    desktop = propsFound.contents[0];
+  else
+    throw new RangeError("more than one desktop found");
+
+  x11.XFree(propsFound);
+
+  return desktop;
+};
+
+firetray.Window.checkSubscribedEventMasks = function(xid) {
+  let xWindowAttributes = new x11.XWindowAttributes;
+  let status = x11.XGetWindowAttributes(x11.current.Display, xid, xWindowAttributes.address());
+  log.debug("xWindowAttributes: "+xWindowAttributes);
+  let xEventMask = xWindowAttributes.your_event_mask;
+  let xEventMaskNeeded = x11.VisibilityChangeMask|x11.StructureNotifyMask|
+        x11.FocusChangeMask|x11.PropertyChangeMask;
+  log.debug("xEventMask="+xEventMask+" xEventMaskNeeded="+xEventMaskNeeded);
+  if ((xEventMask & xEventMaskNeeded) !== xEventMaskNeeded) {
+    log.error("missing mandatory event-masks"); // change with gdk_window_set_events()
+  }
+};
+
+firetray.Window.filterWindow = function(xev, gdkEv, data) {
+  if (!xev)
+    return gdk.GDK_FILTER_CONTINUE;
+
+  let xany = ctypes.cast(xev, x11.XAnyEvent.ptr);
+  let xid = xany.contents.window;
+
+  switch (xany.contents.type) {
+
+  case x11.MapNotify:
+    log.debug("MapNotify");
+    let gdkWinStateOnMap = gdk.gdk_window_get_state(firetray.Handler.gdkWindows.get(xid));
+    log.debug("gdkWinState="+gdkWinStateOnMap+" for xid="+xid);
+    let win = firetray.Handler.windows[xid];
+    if (firetray.Handler.appStarted && !win.visible) {
+      // when app hidden at startup, then called from command line without
+      // any argument (not through FireTray that is)
+      log.warn("window not visible, correcting visibility");
+      log.debug("visibleWindowsCount="+firetray.Handler.visibleWindowsCount);
+    }
+    break;
+
+  case x11.UnmapNotify:       // for catching 'iconify'
+    log.debug("UnmapNotify");
+
+    let winStates = firetray.Window.getXWindowStates(xid);
+    let isHidden =  winStates & FIRETRAY_XWINDOW_HIDDEN;
+    log.debug("winStates="+winStates+", isHidden="+isHidden);
+    // NOTE: Gecko 8.0 provides the 'sizemodechange' event, which comes once
+    // the window is minimized. i.e. preventDefault() or returning false won't
+    // prevent the event.
+    if (isHidden) {
+      log.debug("GOT ICONIFIED");
+      firetray.Handler.onMinimize(xid);
+    }
+    break;
+
+    // default:
+    //   log.debug("xany.type="+xany.contents.type);
+    //   break;
+  }
+
+  return gdk.GDK_FILTER_CONTINUE;
+};
+
+firetray.Window.startupFilter = function(xev, gdkEv, data) {
+  if (!xev)
+    return gdk.GDK_FILTER_CONTINUE;
+
+  let xany = ctypes.cast(xev, x11.XAnyEvent.ptr);
+  let xid = xany.contents.window;
+
+  // MapRequest already taken by window manager. Not sure we could be notified
+  // *before* the window is actually mapped, in order to minimize it before
+  // it's shown.
+  if (xany.contents.type === x11.MapNotify) {
+    gdk.gdk_window_remove_filter(firetray.Handler.gdkWindows.get(xid),
+                                 firetray.Handler.windows[xid].startupFilterCb, null);
+    if (firetray.Utils.prefService.getBoolPref('start_hidden')) {
+      log.debug("start_hidden");
+      firetray.Window.startupHide(xid);
+    }
+  }
+
+  return gdk.GDK_FILTER_CONTINUE;
+};
+
+firetray.Window.showAllWindowsAndActivate = function() {
+  let visibilityRate = firetray.Handler.visibleWindowsCount/firetray.Handler.windowsCount;
+  log.debug("visibilityRate="+visibilityRate);
+  if (visibilityRate < 1)
+    firetray.Handler.showAllWindows();
+
+  for(var key in firetray.Handler.windows); // FIXME: this is not the proper way for finding the last registered window !
+  firetray.Window.activate(key);
+};
+
+firetray.Window.attachOnFocusInCallback = function(xid) {
+  log.debug("attachOnFocusInCallback xid="+xid);
+  let callback = gtk.GCallbackWidgetFocusEvent_t(
+    firetray.Window.onFocusIn, null, FIRETRAY_CB_SENTINEL);
+  this.signals['focus-in'].callback[xid] = callback;
+  let handlerId = gobject.g_signal_connect(
+    firetray.Handler.gtkWindows.get(xid), "focus-in-event", callback, null);
+  log.debug("focus-in handler="+handlerId);
+  this.signals['focus-in'].handler[xid] = handlerId;
+};
+
+firetray.Window.detachOnFocusInCallback = function(xid) {
+  log.debug("detachOnFocusInCallback xid="+xid);
+  let gtkWin = firetray.Handler.gtkWindows.get(xid);
+  gobject.g_signal_handler_disconnect(
+    gtkWin,
+    gobject.gulong(this.signals['focus-in'].handler[xid])
+  );
+  delete this.signals['focus-in'].callback[xid];
+  delete this.signals['focus-in'].handler[xid];
+};
+
+// NOTE: fluxbox issues a FocusIn event when switching workspace
+// by hotkey, which means 2 FocusIn events when switching to a moz app :(
+// (http://sourceforge.net/tracker/index.php?func=detail&aid=3190205&group_id=35398&atid=413960)
+firetray.Window.onFocusIn = function(widget, event, data) {
+  log.debug("onFocusIn");
+  let xid = firetray.Window.getXIDFromGtkWidget(widget);
+  log.debug("xid="+xid);
+
+  firetray.Window.setUrgency(xid, false);
+
+  if (firetray.Handler.isChatEnabled() && firetray.Chat.initialized) {
+    firetray.Chat.stopGetAttentionMaybe(xid);
+  }
+
+  let stopPropagation = false;
+  return stopPropagation;
+};
+
+
+///////////////////////// firetray.Handler overriding /////////////////////////
+
+/** debug facility */
+firetray.Handler.dumpWindows = function() {
+  log.debug(firetray.Handler.windowsCount);
+  for (let winId in firetray.Handler.windows) log.info(winId+"="+firetray.Handler.gtkWindows.get(winId));
+};
+
+firetray.Handler.registerWindow = function(win) {
+  log.debug("register window");
+
+  // register
+  let [baseWin, gtkWin, gdkWin, xid] = firetray.Window.getWindowsFromChromeWindow(win);
+  this.windows[xid] = {};
+  this.windows[xid].chromeWin = win;
+  this.windows[xid].baseWin = baseWin;
+  Object.defineProperties(this.windows[xid], {
+    "visible": { get: function(){return firetray.Window.getVisibility(xid);} }
+  });
+  firetray.Window.checkSubscribedEventMasks(xid);
+  try {
+    this.gtkWindows.insert(xid, gtkWin);
+    this.gdkWindows.insert(xid, gdkWin);
+    firetray.PopupMenu.addWindowItem(xid);
+  } 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.appName+".");
+  }
+  log.debug("window "+xid+" registered");
+  // NOTE: shouldn't 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
+
+    this.windows[xid].filterWindowCb = gdk.GdkFilterFunc_t(
+      firetray.Window.filterWindow, null, FIRETRAY_CB_SENTINEL);
+    gdk.gdk_window_add_filter(gdkWin, this.windows[xid].filterWindowCb, null);
+    if (!firetray.Handler.appStarted) {
+      this.windows[xid].startupFilterCb = gdk.GdkFilterFunc_t(
+        firetray.Window.startupFilter, null, FIRETRAY_CB_SENTINEL);
+      gdk.gdk_window_add_filter(gdkWin, this.windows[xid].startupFilterCb, null);
+    }
+
+    firetray.Window.attachOnFocusInCallback(xid);
+    if (firetray.Handler.isChatEnabled() && firetray.Chat.initialized) {
+      firetray.Chat.attachSelectListeners(win);
+    }
+
+  } catch (x) {
+    log.error(x);
+    firetray.Window.unregisterWindowByXID(xid);
+    return null;
+  }
+
+  log.debug("AFTER"); firetray.Handler.dumpWindows();
+  return xid;
+};
+
+firetray.Handler.unregisterWindow = function(win) {
+  log.debug("unregister window");
+  let xid = firetray.Window.getRegisteredWinIdFromChromeWindow(win);
+  return firetray.Window.unregisterWindowByXID(xid);
+};
+
+firetray.Handler.showWindow = firetray.Window.show;
+firetray.Handler.hideWindow = firetray.Window.hide;
+
+firetray.Handler.showAllWindowsAndActivate = firetray.Window.showAllWindowsAndActivate;
+firetray.Handler.activateLastWindowCb = function(gtkStatusIcon, gdkEvent, userData) {
+  log.debug("activateLastWindowCb");
+
+  let gdkEventButton = ctypes.cast(gdkEvent, gdk.GdkEventButton.ptr);
+  if (gdkEventButton.contents.button === 2 && gdkEventButton.contents.type === gdk.GDK_BUTTON_PRESS) {
+    log.debug("MIDDLE CLICK");
+
+    firetray.Window.showAllWindowsAndActivate();
+  }
+
+  let stopPropagation = false;
+  return stopPropagation;
+};
+
+/* NOTE: gtk_window_is_active() not reliable, and _NET_ACTIVE_WINDOW may not
+   always be set before 'focus-in-event' (gnome-shell/mutter 3.4.1). */
+firetray.Handler.getActiveWindow = function() {
+  let gdkActiveWin = gdk.gdk_screen_get_active_window(gdk.gdk_screen_get_default()); // inspects _NET_ACTIVE_WINDOW
+  log.debug("gdkActiveWin="+gdkActiveWin);
+  if (firetray.js.strEquals(gdkActiveWin, 'GdkWindow.ptr(ctypes.UInt64("0x0"))'))
+    return null;
+  let activeWin = firetray.Window.getXIDFromGdkWindow(gdkActiveWin);
+  log.debug("ACTIVE_WINDOW="+activeWin);
+  return activeWin;
+};
+
+firetray.Handler.windowGetAttention = function(winId) {
+  firetray.Window.setUrgency(winId, true);
+};
+
+
+/**
+ * init X11 Display and handled XAtoms.
+ * Needs to be defined and called outside x11.jsm because: 1. gdk already
+ * imports x11, 2. there is no means to get the default Display solely with
+ * Xlib without opening one... :-(
+ */
+x11.init = function() {
+  if (!firetray.js.isEmpty(this.current))
+    return true; // init only once
+
+  this.current = {};
+  try {
+    let gdkDisplay = gdk.gdk_display_get_default();
+    this.current.Display = gdk.gdk_x11_display_get_xdisplay(gdkDisplay);
+    this.current.Atoms = {};
+    XATOMS.forEach(function(atomName, index, array) {
+      this.current.Atoms[atomName] = x11.XInternAtom(this.current.Display, atomName, 0);
+      log.debug("x11.current.Atoms."+atomName+"="+this.current.Atoms[atomName]);
+    }, this);
+    return true;
+  } catch (x) {
+    log.error(x);
+    return false;
+  }
+};
+x11.init();

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mozext/firetray.git



More information about the Pkg-mozext-commits mailing list