mail-notification

Fork of Jean-Yves Lefort's mail-notification, a tray icon to notify of new mail
git clone https://code.djc.id.au/git/mail-notification/

src/eggtrayicon.c (14293B) - raw

      1 /* eggtrayicon.c
      2  * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
      3  *
      4  * Mail Notification
      5  * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be>
      6  *
      7  * This program is free software; you can redistribute it and/or modify
      8  * it under the terms of the GNU General Public License as published by
      9  * the Free Software Foundation; either version 3 of the License, or
     10  * (at your option) any later version.
     11  *
     12  * This program is distributed in the hope that it will be useful,
     13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15  * GNU General Public License for more details.
     16  *
     17  * You should have received a copy of the GNU General Public License along
     18  * with this program; if not, write to the Free Software Foundation, Inc.,
     19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     20  */
     21 
     22 #include <string.h>
     23 #include <glib/gi18n.h>
     24 #include <libintl.h>
     25 
     26 #include "eggtrayicon.h"
     27 
     28 #include <gdkconfig.h>
     29 #if defined (GDK_WINDOWING_X11)
     30 #include <gdk/gdkx.h>
     31 #include <X11/Xatom.h>
     32 #elif defined (GDK_WINDOWING_WIN32)
     33 #include <gdk/gdkwin32.h>
     34 #endif
     35 
     36 #ifndef EGG_COMPILATION
     37 #ifndef _
     38 #define _(x) dgettext (GETTEXT_PACKAGE, x)
     39 #define N_(x) x
     40 #endif
     41 #else
     42 #define _(x) x
     43 #define N_(x) x
     44 #endif
     45 
     46 #define SYSTEM_TRAY_REQUEST_DOCK    0
     47 #define SYSTEM_TRAY_BEGIN_MESSAGE   1
     48 #define SYSTEM_TRAY_CANCEL_MESSAGE  2
     49 
     50 #define SYSTEM_TRAY_ORIENTATION_HORZ 0
     51 #define SYSTEM_TRAY_ORIENTATION_VERT 1
     52 
     53 enum {
     54   PROP_0,
     55   PROP_ORIENTATION
     56 };
     57 
     58 static GtkPlugClass *parent_class = NULL;
     59 
     60 static void egg_tray_icon_init (EggTrayIcon *icon);
     61 static void egg_tray_icon_class_init (EggTrayIconClass *klass);
     62 
     63 static void egg_tray_icon_get_property (GObject    *object,
     64 					guint       prop_id,
     65 					GValue     *value,
     66 					GParamSpec *pspec);
     67 
     68 static void egg_tray_icon_add (GtkContainer *container, GtkWidget *widget);
     69 
     70 static void egg_tray_icon_realize   (GtkWidget *widget);
     71 static void egg_tray_icon_unrealize (GtkWidget *widget);
     72 
     73 #ifdef GDK_WINDOWING_X11
     74 static void egg_tray_icon_update_manager_window    (EggTrayIcon *icon,
     75 						    gboolean     dock_if_realized);
     76 static void egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon);
     77 #endif
     78 
     79 GType
     80 egg_tray_icon_get_type (void)
     81 {
     82   static GType our_type = 0;
     83 
     84   if (our_type == 0)
     85     {
     86       static const GTypeInfo our_info =
     87       {
     88 	sizeof (EggTrayIconClass),
     89 	(GBaseInitFunc) NULL,
     90 	(GBaseFinalizeFunc) NULL,
     91 	(GClassInitFunc) egg_tray_icon_class_init,
     92 	NULL, /* class_finalize */
     93 	NULL, /* class_data */
     94 	sizeof (EggTrayIcon),
     95 	0,    /* n_preallocs */
     96 	(GInstanceInitFunc) egg_tray_icon_init
     97       };
     98 
     99       our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0);
    100     }
    101 
    102   return our_type;
    103 }
    104 
    105 static void
    106 egg_tray_icon_init (EggTrayIcon *icon)
    107 {
    108   icon->stamp = 1;
    109   icon->orientation = GTK_ORIENTATION_HORIZONTAL;
    110 
    111   gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
    112 }
    113 
    114 static void
    115 egg_tray_icon_class_init (EggTrayIconClass *klass)
    116 {
    117   GObjectClass *gobject_class = (GObjectClass *)klass;
    118   GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
    119   GtkContainerClass *container_class = (GtkContainerClass *)klass;
    120 
    121   parent_class = g_type_class_peek_parent (klass);
    122 
    123   gobject_class->get_property = egg_tray_icon_get_property;
    124 
    125   widget_class->realize   = egg_tray_icon_realize;
    126   widget_class->unrealize = egg_tray_icon_unrealize;
    127 
    128   container_class->add = egg_tray_icon_add;
    129 
    130   g_object_class_install_property (gobject_class,
    131 				   PROP_ORIENTATION,
    132 				   g_param_spec_enum ("orientation",
    133 						      _("Orientation"),
    134 						      _("The orientation of the tray."),
    135 						      GTK_TYPE_ORIENTATION,
    136 						      GTK_ORIENTATION_HORIZONTAL,
    137 						      G_PARAM_READABLE));
    138 
    139 #if defined (GDK_WINDOWING_X11)
    140   /* Nothing */
    141 #elif defined (GDK_WINDOWING_WIN32)
    142   g_warning ("Port eggtrayicon to Win32");
    143 #else
    144   g_warning ("Port eggtrayicon to this GTK+ backend");
    145 #endif
    146 }
    147 
    148 static void
    149 egg_tray_icon_get_property (GObject    *object,
    150 			    guint       prop_id,
    151 			    GValue     *value,
    152 			    GParamSpec *pspec)
    153 {
    154   EggTrayIcon *icon = EGG_TRAY_ICON (object);
    155 
    156   switch (prop_id)
    157     {
    158     case PROP_ORIENTATION:
    159       g_value_set_enum (value, icon->orientation);
    160       break;
    161     default:
    162       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    163       break;
    164     }
    165 }
    166 
    167 #ifdef GDK_WINDOWING_X11
    168 
    169 static void
    170 egg_tray_icon_get_orientation_property (EggTrayIcon *icon)
    171 {
    172   Display *xdisplay;
    173   Atom type;
    174   int format;
    175   union {
    176 	gulong *prop;
    177 	guchar *prop_ch;
    178   } prop = { NULL };
    179   gulong nitems;
    180   gulong bytes_after;
    181   int error, result;
    182 
    183   g_assert (icon->manager_window != None);
    184 
    185   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
    186 
    187   gdk_error_trap_push ();
    188   type = None;
    189   result = XGetWindowProperty (xdisplay,
    190 			       icon->manager_window,
    191 			       icon->orientation_atom,
    192 			       0, G_MAXLONG, FALSE,
    193 			       XA_CARDINAL,
    194 			       &type, &format, &nitems,
    195 			       &bytes_after, &(prop.prop_ch));
    196   error = gdk_error_trap_pop ();
    197 
    198   if (error || result != Success)
    199     return;
    200 
    201   if (type == XA_CARDINAL)
    202     {
    203       GtkOrientation orientation;
    204 
    205       orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
    206 					GTK_ORIENTATION_HORIZONTAL :
    207 					GTK_ORIENTATION_VERTICAL;
    208 
    209       if (icon->orientation != orientation)
    210 	{
    211 	  icon->orientation = orientation;
    212 
    213 	  g_object_notify (G_OBJECT (icon), "orientation");
    214 	}
    215     }
    216 
    217   if (prop.prop)
    218     XFree (prop.prop);
    219 }
    220 
    221 static GdkFilterReturn
    222 egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
    223 {
    224   EggTrayIcon *icon = user_data;
    225   XEvent *xev = (XEvent *)xevent;
    226 
    227   if (xev->xany.type == ClientMessage &&
    228       xev->xclient.message_type == icon->manager_atom &&
    229       xev->xclient.data.l[1] == icon->selection_atom)
    230     {
    231       egg_tray_icon_update_manager_window (icon, TRUE);
    232     }
    233   else if (xev->xany.window == icon->manager_window)
    234     {
    235       if (xev->xany.type == PropertyNotify &&
    236 	  xev->xproperty.atom == icon->orientation_atom)
    237 	{
    238 	  egg_tray_icon_get_orientation_property (icon);
    239 	}
    240       if (xev->xany.type == DestroyNotify)
    241 	{
    242 	  egg_tray_icon_manager_window_destroyed (icon);
    243 	}
    244     }
    245   return GDK_FILTER_CONTINUE;
    246 }
    247 
    248 #endif
    249 
    250 static void
    251 egg_tray_icon_unrealize (GtkWidget *widget)
    252 {
    253 #ifdef GDK_WINDOWING_X11
    254   EggTrayIcon *icon = EGG_TRAY_ICON (widget);
    255   GdkWindow *root_window;
    256 
    257   if (icon->manager_window != None)
    258     {
    259       GdkWindow *gdkwin;
    260 
    261       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
    262                                               icon->manager_window);
    263 
    264       gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
    265     }
    266 
    267   root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
    268 
    269   gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);
    270 
    271   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
    272     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
    273 #endif
    274 }
    275 
    276 #ifdef GDK_WINDOWING_X11
    277 
    278 static void
    279 egg_tray_icon_send_manager_message (EggTrayIcon *icon,
    280 				    long         message,
    281 				    Window       window,
    282 				    long         data1,
    283 				    long         data2,
    284 				    long         data3)
    285 {
    286   XClientMessageEvent ev;
    287   Display *display;
    288 
    289   ev.type = ClientMessage;
    290   ev.window = window;
    291   ev.message_type = icon->system_tray_opcode_atom;
    292   ev.format = 32;
    293   ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
    294   ev.data.l[1] = message;
    295   ev.data.l[2] = data1;
    296   ev.data.l[3] = data2;
    297   ev.data.l[4] = data3;
    298 
    299   display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
    300 
    301   gdk_error_trap_push ();
    302   XSendEvent (display,
    303 	      icon->manager_window, False, NoEventMask, (XEvent *)&ev);
    304   XSync (display, False);
    305   gdk_error_trap_pop ();
    306 }
    307 
    308 static void
    309 egg_tray_icon_send_dock_request (EggTrayIcon *icon)
    310 {
    311   egg_tray_icon_send_manager_message (icon,
    312 				      SYSTEM_TRAY_REQUEST_DOCK,
    313 				      icon->manager_window,
    314 				      gtk_plug_get_id (GTK_PLUG (icon)),
    315 				      0, 0);
    316 }
    317 
    318 static void
    319 egg_tray_icon_update_manager_window (EggTrayIcon *icon,
    320 				     gboolean     dock_if_realized)
    321 {
    322   Display *xdisplay;
    323 
    324   if (icon->manager_window != None)
    325     return;
    326 
    327   xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
    328 
    329   XGrabServer (xdisplay);
    330 
    331   icon->manager_window = XGetSelectionOwner (xdisplay,
    332 					     icon->selection_atom);
    333 
    334   if (icon->manager_window != None)
    335     XSelectInput (xdisplay,
    336 		  icon->manager_window, StructureNotifyMask|PropertyChangeMask);
    337 
    338   XUngrabServer (xdisplay);
    339   XFlush (xdisplay);
    340 
    341   if (icon->manager_window != None)
    342     {
    343       GdkWindow *gdkwin;
    344 
    345       gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
    346 					      icon->manager_window);
    347 
    348       gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
    349 
    350       if (dock_if_realized && GTK_WIDGET_REALIZED (icon))
    351 	egg_tray_icon_send_dock_request (icon);
    352 
    353       egg_tray_icon_get_orientation_property (icon);
    354     }
    355 }
    356 
    357 static gboolean
    358 transparent_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
    359 {
    360   gdk_window_clear_area (widget->window, event->area.x, event->area.y,
    361 			 event->area.width, event->area.height);
    362   return FALSE;
    363 }
    364 
    365 static void
    366 make_transparent_again (GtkWidget *widget, GtkStyle *previous_style,
    367 			gpointer user_data)
    368 {
    369   gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
    370 }
    371 
    372 static void
    373 make_transparent (GtkWidget *widget, gpointer user_data)
    374 {
    375   if (GTK_WIDGET_NO_WINDOW (widget) || GTK_WIDGET_APP_PAINTABLE (widget))
    376     return;
    377 
    378   gtk_widget_set_app_paintable (widget, TRUE);
    379   gtk_widget_set_double_buffered (widget, FALSE);
    380   gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
    381   g_signal_connect (widget, "expose_event",
    382 		    G_CALLBACK (transparent_expose_event), NULL);
    383   g_signal_connect_after (widget, "style_set",
    384 			  G_CALLBACK (make_transparent_again), NULL);
    385 }
    386 
    387 static void
    388 egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon)
    389 {
    390   GdkWindow *gdkwin;
    391 
    392   g_return_if_fail (icon->manager_window != None);
    393 
    394   gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
    395 					  icon->manager_window);
    396 
    397   gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
    398 
    399   icon->manager_window = None;
    400 
    401   egg_tray_icon_update_manager_window (icon, TRUE);
    402 }
    403 
    404 #endif
    405 
    406 static void
    407 egg_tray_icon_realize (GtkWidget *widget)
    408 {
    409 #ifdef GDK_WINDOWING_X11
    410   EggTrayIcon *icon = EGG_TRAY_ICON (widget);
    411   GdkScreen *screen;
    412   GdkDisplay *display;
    413   Display *xdisplay;
    414   char buffer[256];
    415   GdkWindow *root_window;
    416 
    417   if (GTK_WIDGET_CLASS (parent_class)->realize)
    418     GTK_WIDGET_CLASS (parent_class)->realize (widget);
    419 
    420   make_transparent (widget, NULL);
    421 
    422   screen = gtk_widget_get_screen (widget);
    423   display = gdk_screen_get_display (screen);
    424   xdisplay = gdk_x11_display_get_xdisplay (display);
    425 
    426   /* Now see if there's a manager window around */
    427   g_snprintf (buffer, sizeof (buffer),
    428 	      "_NET_SYSTEM_TRAY_S%d",
    429 	      gdk_screen_get_number (screen));
    430 
    431   icon->selection_atom = XInternAtom (xdisplay, buffer, False);
    432 
    433   icon->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
    434 
    435   icon->system_tray_opcode_atom = XInternAtom (xdisplay,
    436 						   "_NET_SYSTEM_TRAY_OPCODE",
    437 						   False);
    438 
    439   icon->orientation_atom = XInternAtom (xdisplay,
    440 					"_NET_SYSTEM_TRAY_ORIENTATION",
    441 					False);
    442 
    443   egg_tray_icon_update_manager_window (icon, FALSE);
    444   egg_tray_icon_send_dock_request (icon);
    445 
    446   root_window = gdk_screen_get_root_window (screen);
    447 
    448   /* Add a root window filter so that we get changes on MANAGER */
    449   gdk_window_add_filter (root_window,
    450 			 egg_tray_icon_manager_filter, icon);
    451 #endif
    452 }
    453 
    454 static void
    455 egg_tray_icon_add (GtkContainer *container, GtkWidget *widget)
    456 {
    457   g_signal_connect (widget, "realize",
    458 		    G_CALLBACK (make_transparent), NULL);
    459   GTK_CONTAINER_CLASS (parent_class)->add (container, widget);
    460 }
    461 
    462 EggTrayIcon *
    463 egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
    464 {
    465   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
    466 
    467   return g_object_new (EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
    468 }
    469 
    470 EggTrayIcon*
    471 egg_tray_icon_new (const gchar *name)
    472 {
    473   return g_object_new (EGG_TYPE_TRAY_ICON, "title", name, NULL);
    474 }
    475 
    476 guint
    477 egg_tray_icon_send_message (EggTrayIcon *icon,
    478 			    gint         timeout,
    479 			    const gchar *message,
    480 			    gint         len)
    481 {
    482   guint stamp;
    483 
    484   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
    485   g_return_val_if_fail (timeout >= 0, 0);
    486   g_return_val_if_fail (message != NULL, 0);
    487 
    488 #ifdef GDK_WINDOWING_X11
    489   if (icon->manager_window == None)
    490     return 0;
    491 #endif
    492 
    493   if (len < 0)
    494     len = strlen (message);
    495 
    496   stamp = icon->stamp++;
    497 
    498 #ifdef GDK_WINDOWING_X11
    499   /* Get ready to send the message */
    500   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
    501 				      (Window)gtk_plug_get_id (GTK_PLUG (icon)),
    502 				      timeout, len, stamp);
    503 
    504   /* Now to send the actual message */
    505   gdk_error_trap_push ();
    506   while (len > 0)
    507     {
    508       XClientMessageEvent ev;
    509       Display *xdisplay;
    510 
    511       xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
    512 
    513       ev.type = ClientMessage;
    514       ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
    515       ev.format = 8;
    516       ev.message_type = XInternAtom (xdisplay,
    517 				     "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
    518       if (len > 20)
    519 	{
    520 	  memcpy (&ev.data, message, 20);
    521 	  len -= 20;
    522 	  message += 20;
    523 	}
    524       else
    525 	{
    526 	  memcpy (&ev.data, message, len);
    527 	  len = 0;
    528 	}
    529 
    530       XSendEvent (xdisplay,
    531 		  icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
    532       XSync (xdisplay, False);
    533     }
    534   gdk_error_trap_pop ();
    535 #endif
    536 
    537   return stamp;
    538 }
    539 
    540 void
    541 egg_tray_icon_cancel_message (EggTrayIcon *icon,
    542 			      guint        id)
    543 {
    544   g_return_if_fail (EGG_IS_TRAY_ICON (icon));
    545   g_return_if_fail (id > 0);
    546 #ifdef GDK_WINDOWING_X11
    547   egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
    548 				      (Window)gtk_plug_get_id (GTK_PLUG (icon)),
    549 				      id, 0, 0);
    550 #endif
    551 }
    552 
    553 GtkOrientation
    554 egg_tray_icon_get_orientation (EggTrayIcon *icon)
    555 {
    556   g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
    557 
    558   return icon->orientation;
    559 }