From c95a97954405c34d49c3ebdd0ffd664a858f98c0 Mon Sep 17 00:00:00 2001
From: Yann Leboulanger <asterix@lagaule.org>
Date: Wed, 9 Jun 2004 14:22:27 +0000
Subject: [PATCH] icon in systray (it represents state and changes when we
 recieve a message not poped up)

---
 Makefile                         |  13 +-
 common/i18n.py                   |   0
 plugins/gtkgui/Makefile          |  26 +++
 plugins/gtkgui/eggtrayicon.c     | 344 +++++++++++++++++++++++++++++++
 plugins/gtkgui/eggtrayicon.h     |  76 +++++++
 plugins/gtkgui/gtkgui.glade      |   7 +-
 plugins/gtkgui/gtkgui.py         |  38 +++-
 plugins/gtkgui/trayicon.defs     |  58 ++++++
 plugins/gtkgui/trayicon.override |  28 +++
 plugins/gtkgui/trayiconmodule.c  |  25 +++
 10 files changed, 607 insertions(+), 8 deletions(-)
 mode change 100755 => 100644 common/i18n.py
 create mode 100644 plugins/gtkgui/Makefile
 create mode 100644 plugins/gtkgui/eggtrayicon.c
 create mode 100644 plugins/gtkgui/eggtrayicon.h
 create mode 100644 plugins/gtkgui/trayicon.defs
 create mode 100644 plugins/gtkgui/trayicon.override
 create mode 100644 plugins/gtkgui/trayiconmodule.c

diff --git a/Makefile b/Makefile
index 6f079def80..d1bbe307b8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,13 @@
+MODULES = common plugins/gtkgui
+
 all:
-	python setup.py build_ext -i
-	mv idle.so common/
 	msgfmt Messages/fr/LC_MESSAGES/gajim.po -o Messages/fr/LC_MESSAGES/gajim.mo
+	for dir in ${MODULES}; do \
+	  (cd $$dir; make all); \
+	done
+
+clean:
+	find -name *.pyc -exec rm {} \;
+	for dir in ${MODULES}; do \
+	  (cd $$dir; make clean); \
+	done
diff --git a/common/i18n.py b/common/i18n.py
old mode 100755
new mode 100644
diff --git a/plugins/gtkgui/Makefile b/plugins/gtkgui/Makefile
new file mode 100644
index 0000000000..0bf06f78f9
--- /dev/null
+++ b/plugins/gtkgui/Makefile
@@ -0,0 +1,26 @@
+# Set the C flags to include the GTK+ and Python libraries
+CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0` -I/usr/include/python2.3/ -I.
+LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0`
+
+# Build the shared object
+#trayicon.so: trayicon.o eggtrayicon.o trayiconmodule.o
+all: trayicon.o eggtrayicon.o trayiconmodule.o
+	$(CC) $(LDFLAGS) -shared $^ -o trayicon.so
+
+
+# The path to the GTK+ python types
+DEFS=`pkg-config --variable=defsdir pygtk-2.0`
+
+# Generate the C wrapper from the defs and our override file
+trayicon.c: trayicon.defs trayicon.override
+	pygtk-codegen-2.0 --prefix trayicon \
+	--register $(DEFS)/gdk-types.defs \
+	--register $(DEFS)/gtk-types.defs \
+	--override trayicon.override \
+	trayicon.defs > $@
+
+# A rule to clean the generated files
+clean:
+	rm -f trayicon.so *.o trayicon.c *~ 
+
+.PHONY: clean
diff --git a/plugins/gtkgui/eggtrayicon.c b/plugins/gtkgui/eggtrayicon.c
new file mode 100644
index 0000000000..65e4cdda09
--- /dev/null
+++ b/plugins/gtkgui/eggtrayicon.c
@@ -0,0 +1,344 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* eggtrayicon.c
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <gdk/gdkx.h>
+#include "eggtrayicon.h"
+
+#define SYSTEM_TRAY_REQUEST_DOCK    0
+#define SYSTEM_TRAY_BEGIN_MESSAGE   1
+#define SYSTEM_TRAY_CANCEL_MESSAGE  2
+         
+static GtkPlugClass *parent_class = NULL;
+
+static void egg_tray_icon_init (EggTrayIcon *icon);
+static void egg_tray_icon_class_init (EggTrayIconClass *klass);
+
+static void egg_tray_icon_update_manager_window (EggTrayIcon *icon);
+
+GType
+egg_tray_icon_get_type (void)
+{
+  static GType our_type = 0;
+
+  if (our_type == 0)
+    {
+      static const GTypeInfo our_info =
+      {
+	sizeof (EggTrayIconClass),
+	(GBaseInitFunc) NULL,
+	(GBaseFinalizeFunc) NULL,
+	(GClassInitFunc) egg_tray_icon_class_init,
+	NULL, /* class_finalize */
+	NULL, /* class_data */
+	sizeof (EggTrayIcon),
+	0,    /* n_preallocs */
+	(GInstanceInitFunc) egg_tray_icon_init
+      };
+
+      our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0);
+    }
+
+  return our_type;
+}
+
+static void
+egg_tray_icon_init (EggTrayIcon *icon)
+{
+  icon->stamp = 1;
+  
+  gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
+}
+
+static void
+egg_tray_icon_class_init (EggTrayIconClass *klass)
+{
+  parent_class = g_type_class_peek_parent (klass);
+}
+
+static GdkFilterReturn
+egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
+{
+  EggTrayIcon *icon = user_data;
+  XEvent *xev = (XEvent *)xevent;
+
+  if (xev->xany.type == ClientMessage &&
+      xev->xclient.message_type == icon->manager_atom &&
+      xev->xclient.data.l[1] == icon->selection_atom)
+    {
+      egg_tray_icon_update_manager_window (icon);
+    }
+  else if (xev->xany.window == icon->manager_window)
+    {
+      if (xev->xany.type == DestroyNotify)
+	{
+	  egg_tray_icon_update_manager_window (icon);
+	}
+    }
+  
+  return GDK_FILTER_CONTINUE;
+}
+
+static void
+egg_tray_icon_send_manager_message (EggTrayIcon *icon,
+				    long         message,
+				    Window       window,
+				    long         data1,
+				    long         data2,
+				    long         data3)
+{
+  XClientMessageEvent ev;
+  Display *display;
+  
+  ev.type = ClientMessage;
+  ev.window = window;
+  ev.message_type = icon->system_tray_opcode_atom;
+  ev.format = 32;
+  ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
+  ev.data.l[1] = message;
+  ev.data.l[2] = data1;
+  ev.data.l[3] = data2;
+  ev.data.l[4] = data3;
+
+#if HAVE_GTK_MULTIHEAD
+  display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+#else
+  display = gdk_display;
+#endif
+  
+  gdk_error_trap_push ();
+  XSendEvent (display,
+	      icon->manager_window, False, NoEventMask, (XEvent *)&ev);
+  XSync (display, False);
+  gdk_error_trap_pop ();
+}
+
+static void
+egg_tray_icon_send_dock_request (EggTrayIcon *icon)
+{
+  egg_tray_icon_send_manager_message (icon,
+				      SYSTEM_TRAY_REQUEST_DOCK,
+				      icon->manager_window,
+				      gtk_plug_get_id (GTK_PLUG (icon)),
+				      0, 0);
+}
+
+static void
+egg_tray_icon_update_manager_window (EggTrayIcon *icon)
+{
+  Display *xdisplay;
+  
+#if HAVE_GTK_MULTIHEAD
+  xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+#else
+  xdisplay = gdk_display;
+#endif
+  
+  if (icon->manager_window != None)
+    {
+      GdkWindow *gdkwin;
+
+#if HAVE_GTK_MULTIHEAD
+      gdkwin = gdk_window_lookup_for_display (display,
+					      icon->manager_window);
+#else
+      gdkwin = gdk_window_lookup (icon->manager_window);
+#endif
+      
+      gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
+    }
+  
+  XGrabServer (xdisplay);
+  
+  icon->manager_window = XGetSelectionOwner (xdisplay,
+					     icon->selection_atom);
+
+  if (icon->manager_window != None)
+    XSelectInput (xdisplay,
+		  icon->manager_window, StructureNotifyMask);
+
+  XUngrabServer (xdisplay);
+  XFlush (xdisplay);
+  
+  if (icon->manager_window != None)
+    {
+      GdkWindow *gdkwin;
+
+#if HAVE_GTK_MULTIHEAD
+      gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
+					      icon->manager_window);
+#else
+      gdkwin = gdk_window_lookup (icon->manager_window);
+#endif
+      
+      gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
+
+      /* Send a request that we'd like to dock */
+      egg_tray_icon_send_dock_request (icon);
+    }
+}
+
+EggTrayIcon *
+egg_tray_icon_new_for_xscreen (Screen *xscreen, const char *name);
+
+EggTrayIcon *
+egg_tray_icon_new_for_xscreen (Screen *xscreen, const char *name)
+{
+  EggTrayIcon *icon;
+  char buffer[256];
+  GdkWindow *root_window;
+
+  g_return_val_if_fail (xscreen != NULL, NULL);
+  
+  icon = g_object_new (EGG_TYPE_TRAY_ICON, NULL);
+  gtk_window_set_title (GTK_WINDOW (icon), name);
+
+#if HAVE_GTK_MULTIHEAD
+  gtk_plug_construct_for_display (GTK_PLUG (icon),
+				  gdk_screen_get_display (screen), 0);
+#else
+  gtk_plug_construct (GTK_PLUG (icon), 0);
+#endif
+  
+  gtk_widget_realize (GTK_WIDGET (icon));
+
+  /* Now see if there's a manager window around */
+  g_snprintf (buffer, sizeof (buffer),
+	      "_NET_SYSTEM_TRAY_S%d",
+	      XScreenNumberOfScreen (xscreen));
+  
+  icon->selection_atom = XInternAtom (DisplayOfScreen (xscreen),
+				      buffer, False);
+  
+  icon->manager_atom = XInternAtom (DisplayOfScreen (xscreen),
+				    "MANAGER", False);
+  
+  icon->system_tray_opcode_atom = XInternAtom (DisplayOfScreen (xscreen),
+					       "_NET_SYSTEM_TRAY_OPCODE", False);
+
+  egg_tray_icon_update_manager_window (icon);
+
+#if HAVE_GTK_MULTIHEAD
+  root_window = gdk_screen_get_root_window (screen);
+#else
+  root_window = gdk_window_lookup (gdk_x11_get_default_root_xwindow ());
+#endif
+  
+  /* Add a root window filter so that we get changes on MANAGER */
+  gdk_window_add_filter (root_window,
+			 egg_tray_icon_manager_filter, icon);
+		      
+  return icon;
+}
+
+#if HAVE_GTK_MULTIHEAD
+EggTrayIcon *
+egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
+{
+  EggTrayIcon *icon;
+  char buffer[256];
+
+  g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+
+  return egg_tray_icon_new_for_xscreen (GDK_SCREEN_XSCREEN (screen), name);
+}
+#endif
+
+EggTrayIcon*
+egg_tray_icon_new (const gchar *name)
+{
+  return egg_tray_icon_new_for_xscreen (DefaultScreenOfDisplay (gdk_display), name);
+}
+
+guint
+egg_tray_icon_send_message (EggTrayIcon *icon,
+			    gint         timeout,
+			    const gchar *message,
+			    gint         len)
+{
+  guint stamp;
+  
+  g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
+  g_return_val_if_fail (timeout >= 0, 0);
+  g_return_val_if_fail (message != NULL, 0);
+		     
+  if (icon->manager_window == None)
+    return 0;
+
+  if (len < 0)
+    len = strlen (message);
+
+  stamp = icon->stamp++;
+  
+  /* Get ready to send the message */
+  egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
+				      (Window)gtk_plug_get_id (GTK_PLUG (icon)),
+				      timeout, len, stamp);
+
+  /* Now to send the actual message */
+  gdk_error_trap_push ();
+  while (len > 0)
+    {
+      XClientMessageEvent ev;
+      Display *xdisplay;
+
+#if HAVE_GTK_MULTIHEAD
+      xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+#else
+      xdisplay = gdk_display;
+#endif
+      
+      ev.type = ClientMessage;
+      ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
+      ev.format = 8;
+      ev.message_type = XInternAtom (xdisplay,
+				     "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
+      if (len > 20)
+	{
+	  memcpy (&ev.data, message, 20);
+	  len -= 20;
+	  message += 20;
+	}
+      else
+	{
+	  memcpy (&ev.data, message, len);
+	  len = 0;
+	}
+
+      XSendEvent (xdisplay,
+		  icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
+      XSync (xdisplay, False);
+    }
+  gdk_error_trap_pop ();
+
+  return stamp;
+}
+
+void
+egg_tray_icon_cancel_message (EggTrayIcon *icon,
+			      guint        id)
+{
+  g_return_if_fail (EGG_IS_TRAY_ICON (icon));
+  g_return_if_fail (id > 0);
+  
+  egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
+				      (Window)gtk_plug_get_id (GTK_PLUG (icon)),
+				      id, 0, 0);
+}
diff --git a/plugins/gtkgui/eggtrayicon.h b/plugins/gtkgui/eggtrayicon.h
new file mode 100644
index 0000000000..724bc3da8b
--- /dev/null
+++ b/plugins/gtkgui/eggtrayicon.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* eggtrayicon.h
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_TRAY_ICON_H__
+#define __EGG_TRAY_ICON_H__
+
+#include <gtk/gtkplug.h>
+#include <gdk/gdkx.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_TRAY_ICON		(egg_tray_icon_get_type ())
+#define EGG_TRAY_ICON(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TRAY_ICON, EggTrayIcon))
+#define EGG_TRAY_ICON_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_TRAY_ICON, EggTrayIconClass))
+#define EGG_IS_TRAY_ICON(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TRAY_ICON))
+#define EGG_IS_TRAY_ICON_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_TRAY_ICON))
+#define EGG_TRAY_ICON_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TRAY_ICON, EggTrayIconClass))
+	
+typedef struct _EggTrayIcon	  EggTrayIcon;
+typedef struct _EggTrayIconClass  EggTrayIconClass;
+
+struct _EggTrayIcon
+{
+  GtkPlug parent_instance;
+
+  guint stamp;
+  
+  Atom selection_atom;
+  Atom manager_atom;
+  Atom system_tray_opcode_atom;
+  Window manager_window;
+};
+
+struct _EggTrayIconClass
+{
+  GtkPlugClass parent_class;
+};
+
+GType        egg_tray_icon_get_type       (void);
+
+#if EGG_TRAY_ENABLE_MULTIHEAD
+EggTrayIcon *egg_tray_icon_new_for_screen (GdkScreen   *screen,
+					   const gchar *name);
+#endif
+
+EggTrayIcon *egg_tray_icon_new            (const gchar *name);
+
+guint        egg_tray_icon_send_message   (EggTrayIcon *icon,
+					   gint         timeout,
+					   const char  *message,
+					   gint         len);
+void         egg_tray_icon_cancel_message (EggTrayIcon *icon,
+					   guint        id);
+
+
+					    
+G_END_DECLS
+
+#endif /* __EGG_TRAY_ICON_H__ */
diff --git a/plugins/gtkgui/gtkgui.glade b/plugins/gtkgui/gtkgui.glade
index 50c98277a9..0688ab11d8 100644
--- a/plugins/gtkgui/gtkgui.glade
+++ b/plugins/gtkgui/gtkgui.glade
@@ -2,7 +2,6 @@
 <!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
 
 <glade-interface>
-<requires lib="gnome"/>
 
 <widget class="GtkWindow" id="Gajim">
   <property name="visible">True</property>
@@ -10,8 +9,8 @@
   <property name="type">GTK_WINDOW_TOPLEVEL</property>
   <property name="window_position">GTK_WIN_POS_NONE</property>
   <property name="modal">False</property>
-  <property name="default_width">100</property>
-  <property name="default_height">300</property>
+  <property name="default_width">150</property>
+  <property name="default_height">400</property>
   <property name="resizable">True</property>
   <property name="destroy_with_parent">False</property>
   <property name="decorated">True</property>
@@ -4062,7 +4061,7 @@ when NOT onlie</property>
 		  <property name="can_focus">True</property>
 		  <property name="editable">True</property>
 		  <property name="overwrite">False</property>
-		  <property name="accepts_tab">True</property>
+		  <property name="accepts_tab">False</property>
 		  <property name="justification">GTK_JUSTIFY_LEFT</property>
 		  <property name="wrap_mode">GTK_WRAP_WORD</property>
 		  <property name="cursor_visible">True</property>
diff --git a/plugins/gtkgui/gtkgui.py b/plugins/gtkgui/gtkgui.py
index bc175ec210..ac0ab7e485 100644
--- a/plugins/gtkgui/gtkgui.py
+++ b/plugins/gtkgui/gtkgui.py
@@ -21,6 +21,7 @@ import pygtk
 pygtk.require('2.0')
 import gtk
 from gtk import TRUE, FALSE
+import trayicon
 import gtk.glade,gobject
 import os,string,time,Queue
 import common.optparser,common.sleepy
@@ -102,6 +103,7 @@ class message_Window:
 			if self.plugin.roster.pixbufs.has_key(self.user.show):
 				self.plugin.roster.tree.get_model().set_value(i, 0, \
 					self.plugin.roster.pixbufs[self.user.show])
+		self.plugin.set_systray()
 
 	def on_msg_key_press_event(self, widget, event):
 		"""When a key is pressed :
@@ -839,13 +841,15 @@ class roster_Window:
 	def set_optionmenu(self):
 		#table to change index in plugin.connected to index in optionmenu
 		table = {0:6, 1:0, 2:1, 3:2, 4:3, 5:4}
-		mini = min(self.plugin.connected.values())
+		maxi = max(self.plugin.connected.values())
 		optionmenu = self.xml.get_widget('optionmenu')
 		#temporarily block signal in order not to send status that we show
 		#in the optionmenu
 		optionmenu.handler_block(self.id_signal_optionmenu)
-		optionmenu.set_history(table[mini])
+		optionmenu.set_history(table[maxi])
 		optionmenu.handler_unblock(self.id_signal_optionmenu)
+		statuss = ['offline', 'online', 'away', 'xa', 'dnd', 'invisible']
+		self.plugin.set_systray(statuss[maxi])
 
 	def on_status_changed(self, account, status):
 		"""the core tells us that our status has changed"""
@@ -891,6 +895,7 @@ class roster_Window:
 				self.plugin.queues[account][jid] = Queue.Queue(50)
 				for i in self.get_user_iter(jid, account):
 					model.set_value(i, 0, self.pixbufs['message'])
+				self.plugin.set_systray('message')
 			tim = time.strftime("[%H:%M:%S]")
 			self.plugin.queues[account][jid].put((msg, tim))
 			if path:
@@ -1402,6 +1407,27 @@ class plugin:
 				self.sleeper_state[account] = 3
 		return 1
 
+	def set_systray_img(self, status):
+		if not self.roster.pixbufs.has_key(status):
+			return
+		pix = self.roster.pixbufs[status]
+		if isinstance(pix, gtk.gdk.PixbufAnimation):
+			self.img_tray.set_from_animation(pix)
+		else:
+			self.img_tray.set_from_pixbuf(pix)
+
+	def set_systray(self, status=None):
+		if not status:
+			self.nb_msg -= 1
+			if self.nb_msg == 0:
+				self.set_systray_img(self.status)
+		elif status == 'message':
+			self.nb_msg += 1
+			self.set_systray_img('message')
+		else:
+			self.status = status
+			self.set_systray_img(status)
+
 	def __init__(self, quIN, quOUT):
 		gtk.threads_init()
 		gtk.threads_enter()
@@ -1420,6 +1446,7 @@ class plugin:
 		self.config = self.wait('CONFIG')
 		self.send('ASK_CONFIG', None, ('GtkGui', 'accounts'))
 		self.accounts = self.wait('CONFIG')
+		self.nb_msg = 0
 		self.windows = {'logs':{}}
 		self.queues = {}
 		self.connected = {}
@@ -1440,6 +1467,13 @@ class plugin:
 		gtk.timeout_add(100, self.read_queue)
 		gtk.timeout_add(1000, self.read_sleepy)
 		self.sleeper = None
+		t = trayicon.TrayIcon("Gajim")
+		tip = gtk.Tooltips()
+		tip.set_tip(t, 'Gajim')
+		self.img_tray = gtk.Image()
+		t.add(self.img_tray)
+		t.show_all()
+		self.set_systray('offline')
 		gtk.main()
 		gtk.threads_leave()
 
diff --git a/plugins/gtkgui/trayicon.defs b/plugins/gtkgui/trayicon.defs
new file mode 100644
index 0000000000..f61b1a2e73
--- /dev/null
+++ b/plugins/gtkgui/trayicon.defs
@@ -0,0 +1,58 @@
+;; -*- scheme -*-
+; object definitions ...
+(define-object TrayIcon
+  (in-module "Egg")
+  (parent "GtkPlug")
+  (c-name "EggTrayIcon")
+  (gtype-id "EGG_TYPE_TRAY_ICON")
+)
+
+;; Enumerations and flags ...
+
+
+;; From eggtrayicon.h
+
+(define-function egg_tray_icon_get_type
+  (c-name "egg_tray_icon_get_type")
+  (return-type "GType")
+)
+
+;;(define-function egg_tray_icon_new_for_screen
+;;  (c-name "egg_tray_icon_new_for_screen")
+;;  (return-type "EggTrayIcon*")
+;;  (parameters
+;;    '("GdkScreen*" "screen")
+;;    '("const-gchar*" "name")
+;;  )
+;;)
+
+(define-function egg_tray_icon_new
+  (c-name "egg_tray_icon_new")
+  (is-constructor-of "EggTrayIcon")
+  (return-type "EggTrayIcon*")
+  (parameters
+    '("const-gchar*" "name")
+  )
+)
+
+(define-method send_message
+  (of-object "EggTrayIcon")
+  (c-name "egg_tray_icon_send_message")
+  (return-type "guint")
+  (parameters
+    '("gint" "timeout")
+    '("const-char*" "message")
+    '("gint" "len")
+  )
+)
+
+(define-method cancel_message
+  (of-object "EggTrayIcon")
+  (c-name "egg_tray_icon_cancel_message")
+  (return-type "none")
+  (parameters
+    '("guint" "id")
+  )
+)
+
+
diff --git a/plugins/gtkgui/trayicon.override b/plugins/gtkgui/trayicon.override
new file mode 100644
index 0000000000..979f523dcb
--- /dev/null
+++ b/plugins/gtkgui/trayicon.override
@@ -0,0 +1,28 @@
+/* -*- Mode: C; c-basic-offset: 4 -*- */
+%%
+headers
+#include <Python.h>
+
+#include "pygobject.h"
+#include "eggtrayicon.h"
+%%
+modulename trayicon
+%%
+import gtk.Plug as PyGtkPlug_Type
+%%
+ignore-glob
+  *_get_type
+%%
+override egg_tray_icon_send_message kwargs
+static PyObject*
+_wrap_egg_tray_icon_send_message(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = {"timeout", "message", NULL};
+    int timeout, len, ret;
+    char *message;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "is#:TrayIcon.send_message", kwlist, &timeout, &message, &len))
+        return NULL;
+    ret = egg_tray_icon_send_message(EGG_TRAY_ICON(self->obj), timeout, message, len);
+    return PyInt_FromLong(ret);
+}
diff --git a/plugins/gtkgui/trayiconmodule.c b/plugins/gtkgui/trayiconmodule.c
new file mode 100644
index 0000000000..c2959a4854
--- /dev/null
+++ b/plugins/gtkgui/trayiconmodule.c
@@ -0,0 +1,25 @@
+/* -*- Mode: C; c-basic-offset: 4 -*- */
+
+/* include this first, before NO_IMPORT_PYGOBJECT is defined */
+#include <pygobject.h>
+
+void trayicon_register_classes (PyObject *d);
+
+extern PyMethodDef trayicon_functions[];
+
+DL_EXPORT(void)
+inittrayicon(void)
+{
+    PyObject *m, *d;
+	
+    init_pygobject ();
+
+    m = Py_InitModule ("trayicon", trayicon_functions);
+    d = PyModule_GetDict (m);
+	
+    trayicon_register_classes (d);
+
+    if (PyErr_Occurred ()) {
+	Py_FatalError ("can't initialise module trayicon :(");
+    }
+}
-- 
GitLab