Update - Macedonian Translation by Arangel Angov <arangel@linux.net.mk>
[rhythmbox.git] / widgets / eggtrayicon.c
blob18e3539f3e78f82701d9bfe9b6a61991ae3c1781
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* eggtrayicon.c
3 * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301 USA.
21 #include <config.h>
22 #include <string.h>
23 #include <libintl.h>
25 #include "eggtrayicon.h"
26 #include "rb-stock-icons.h"
27 #include "rb-file-helpers.h"
29 #include <gdkconfig.h>
30 #include <gtk/gtkimage.h>
31 #if defined (GDK_WINDOWING_X11)
32 #include <gdk/gdkx.h>
33 #include <X11/Xatom.h>
34 #elif defined (GDK_WINDOWING_WIN32)
35 #include <gdk/gdkwin32.h>
36 #endif
37 #ifdef HAVE_NOTIFY
38 #include <libnotify/notify.h>
39 #endif
41 #ifndef EGG_COMPILATION
42 #ifndef _
43 #define _(x) dgettext (GETTEXT_PACKAGE, x)
44 #define N_(x) x
45 #endif
46 #else
47 #define _(x) x
48 #define N_(x) x
49 #endif
51 #define SYSTEM_TRAY_REQUEST_DOCK 0
52 #define SYSTEM_TRAY_BEGIN_MESSAGE 1
53 #define SYSTEM_TRAY_CANCEL_MESSAGE 2
55 #define SYSTEM_TRAY_ORIENTATION_HORZ 0
56 #define SYSTEM_TRAY_ORIENTATION_VERT 1
58 enum {
59 PROP_0,
60 PROP_ORIENTATION
63 #ifdef HAVE_NOTIFY
64 struct _Notify {
65 #if (LIBNOTIFY_VERSION_MAJOR == 0 && LIBNOTIFY_VERSION_MINOR == 2)
66 NotifyHints *hints;
67 NotifyIcon *icon;
68 NotifyHandle *handle;
69 #elif (LIBNOTIFY_VERSION_MAJOR > 0 || LIBNOTIFY_VERSION_MINOR >= 3)
70 NotifyNotification *handle;
71 #endif
73 #endif
75 static GtkPlugClass *parent_class = NULL;
77 static void egg_tray_icon_init (EggTrayIcon *icon);
78 static void egg_tray_icon_class_init (EggTrayIconClass *klass);
80 static void egg_tray_icon_get_property (GObject *object,
81 guint prop_id,
82 GValue *value,
83 GParamSpec *pspec);
85 static void egg_tray_icon_realize (GtkWidget *widget);
86 static void egg_tray_icon_unrealize (GtkWidget *widget);
88 static void egg_tray_icon_add (GtkContainer *container,
89 GtkWidget *widget);
91 #ifdef GDK_WINDOWING_X11
92 static void egg_tray_icon_update_manager_window (EggTrayIcon *icon,
93 gboolean dock_if_realized);
94 static void egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon);
95 #endif
97 GType
98 egg_tray_icon_get_type (void)
100 static GType our_type = 0;
102 if (our_type == 0)
104 static const GTypeInfo our_info =
106 sizeof (EggTrayIconClass),
107 (GBaseInitFunc) NULL,
108 (GBaseFinalizeFunc) NULL,
109 (GClassInitFunc) egg_tray_icon_class_init,
110 NULL, /* class_finalize */
111 NULL, /* class_data */
112 sizeof (EggTrayIcon),
113 0, /* n_preallocs */
114 (GInstanceInitFunc) egg_tray_icon_init
117 our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0);
120 return our_type;
123 static void
124 egg_tray_icon_init (EggTrayIcon *icon)
126 icon->stamp = 1;
127 icon->orientation = GTK_ORIENTATION_HORIZONTAL;
128 #ifdef HAVE_NOTIFY
129 icon->notify = g_new0 (Notify, 1);
130 #endif
131 gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
134 static void
135 egg_tray_icon_class_init (EggTrayIconClass *klass)
137 GObjectClass *gobject_class = (GObjectClass *)klass;
138 GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
139 GtkContainerClass *container_class = (GtkContainerClass *)klass;
141 parent_class = g_type_class_peek_parent (klass);
143 gobject_class->get_property = egg_tray_icon_get_property;
145 widget_class->realize = egg_tray_icon_realize;
146 widget_class->unrealize = egg_tray_icon_unrealize;
148 container_class->add = egg_tray_icon_add;
150 g_object_class_install_property (gobject_class,
151 PROP_ORIENTATION,
152 g_param_spec_enum ("orientation",
153 _("Orientation"),
154 _("The orientation of the tray."),
155 GTK_TYPE_ORIENTATION,
156 GTK_ORIENTATION_HORIZONTAL,
157 G_PARAM_READABLE));
159 #if defined (GDK_WINDOWING_X11)
160 /* Nothing */
161 #elif defined (GDK_WINDOWING_WIN32)
162 g_warning ("Port eggtrayicon to Win32");
163 #else
164 g_warning ("Port eggtrayicon to this GTK+ backend");
165 #endif
168 static void
169 egg_tray_icon_get_property (GObject *object,
170 guint prop_id,
171 GValue *value,
172 GParamSpec *pspec)
174 EggTrayIcon *icon = EGG_TRAY_ICON (object);
176 switch (prop_id)
178 case PROP_ORIENTATION:
179 g_value_set_enum (value, icon->orientation);
180 break;
181 default:
182 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
183 break;
187 #ifdef GDK_WINDOWING_X11
189 static void
190 egg_tray_icon_get_orientation_property (EggTrayIcon *icon)
192 Display *xdisplay;
193 Atom type;
194 int format;
195 union {
196 gulong *prop;
197 guchar *prop_ch;
198 } prop = { NULL };
199 gulong nitems;
200 gulong bytes_after;
201 int error, result;
203 g_assert (icon->manager_window != None);
205 xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
207 gdk_error_trap_push ();
208 type = None;
209 result = XGetWindowProperty (xdisplay,
210 icon->manager_window,
211 icon->orientation_atom,
212 0, G_MAXLONG, FALSE,
213 XA_CARDINAL,
214 &type, &format, &nitems,
215 &bytes_after, &(prop.prop_ch));
216 error = gdk_error_trap_pop ();
218 if (error || result != Success)
219 return;
221 if (type == XA_CARDINAL)
223 GtkOrientation orientation;
225 orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
226 GTK_ORIENTATION_HORIZONTAL :
227 GTK_ORIENTATION_VERTICAL;
229 if (icon->orientation != orientation)
231 icon->orientation = orientation;
233 g_object_notify (G_OBJECT (icon), "orientation");
237 if (prop.prop)
238 XFree (prop.prop);
241 static GdkFilterReturn
242 egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
244 EggTrayIcon *icon = user_data;
245 XEvent *xev = (XEvent *)xevent;
247 if (xev->xany.type == ClientMessage &&
248 xev->xclient.message_type == icon->manager_atom &&
249 xev->xclient.data.l[1] == icon->selection_atom)
251 egg_tray_icon_update_manager_window (icon, TRUE);
253 else if (xev->xany.window == icon->manager_window)
255 if (xev->xany.type == PropertyNotify &&
256 xev->xproperty.atom == icon->orientation_atom)
258 egg_tray_icon_get_orientation_property (icon);
260 if (xev->xany.type == DestroyNotify)
262 egg_tray_icon_manager_window_destroyed (icon);
265 return GDK_FILTER_CONTINUE;
268 #endif
270 static void
271 egg_tray_icon_unrealize (GtkWidget *widget)
273 #ifdef GDK_WINDOWING_X11
274 EggTrayIcon *icon = EGG_TRAY_ICON (widget);
275 GdkWindow *root_window;
277 if (icon->manager_window != None)
279 GdkWindow *gdkwin;
281 gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
282 icon->manager_window);
284 gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
287 root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
289 gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);
291 if (GTK_WIDGET_CLASS (parent_class)->unrealize)
292 (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
293 #endif
295 #ifdef HAVE_NOTIFY
297 if (EGG_TRAY_ICON (widget)->notify->handle) {
298 #if (LIBNOTIFY_VERSION_MAJOR > 0 || LIBNOTIFY_VERSION_MINOR >= 3)
299 notify_notification_close (EGG_TRAY_ICON (widget)->notify->handle, NULL);
300 #elif (LIBNOTIFY_VERSION_MINOR == 2)
301 notify_close (EGG_TRAY_ICON (widget)->notify->handle);
302 #endif
305 g_free (EGG_TRAY_ICON (widget)->notify);
306 #endif
309 #ifdef GDK_WINDOWING_X11
311 static void
312 egg_tray_icon_send_manager_message (EggTrayIcon *icon,
313 long message,
314 Window window,
315 long data1,
316 long data2,
317 long data3)
319 XClientMessageEvent ev;
320 Display *display;
322 ev.type = ClientMessage;
323 ev.window = window;
324 ev.message_type = icon->system_tray_opcode_atom;
325 ev.format = 32;
326 ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
327 ev.data.l[1] = message;
328 ev.data.l[2] = data1;
329 ev.data.l[3] = data2;
330 ev.data.l[4] = data3;
332 display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
334 gdk_error_trap_push ();
335 XSendEvent (display,
336 icon->manager_window, False, NoEventMask, (XEvent *)&ev);
337 XSync (display, False);
338 gdk_error_trap_pop ();
341 static void
342 egg_tray_icon_send_dock_request (EggTrayIcon *icon)
344 egg_tray_icon_send_manager_message (icon,
345 SYSTEM_TRAY_REQUEST_DOCK,
346 icon->manager_window,
347 gtk_plug_get_id (GTK_PLUG (icon)),
348 0, 0);
351 static void
352 egg_tray_icon_update_manager_window (EggTrayIcon *icon,
353 gboolean dock_if_realized)
355 Display *xdisplay;
357 if (icon->manager_window != None)
358 return;
360 xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
362 XGrabServer (xdisplay);
364 icon->manager_window = XGetSelectionOwner (xdisplay,
365 icon->selection_atom);
367 if (icon->manager_window != None)
368 XSelectInput (xdisplay,
369 icon->manager_window, StructureNotifyMask|PropertyChangeMask);
371 XUngrabServer (xdisplay);
372 XFlush (xdisplay);
374 if (icon->manager_window != None)
376 GdkWindow *gdkwin;
378 gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
379 icon->manager_window);
381 gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
383 if (dock_if_realized && GTK_WIDGET_REALIZED (icon))
384 egg_tray_icon_send_dock_request (icon);
386 egg_tray_icon_get_orientation_property (icon);
390 static gboolean
391 transparent_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
393 gdk_window_clear_area (widget->window, event->area.x, event->area.y,
394 event->area.width, event->area.height);
395 return FALSE;
398 static void
399 make_transparent_again (GtkWidget *widget, GtkStyle *previous_style,
400 gpointer user_data)
402 gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
405 static void
406 make_transparent (GtkWidget *widget, gpointer user_data)
408 if (GTK_WIDGET_NO_WINDOW (widget) || GTK_WIDGET_APP_PAINTABLE (widget))
409 return;
411 gtk_widget_set_app_paintable (widget, TRUE);
412 gtk_widget_set_double_buffered (widget, FALSE);
413 gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
414 g_signal_connect (widget, "expose_event",
415 G_CALLBACK (transparent_expose_event), NULL);
416 g_signal_connect_after (widget, "style_set",
417 G_CALLBACK (make_transparent_again), NULL);
420 static void
421 egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon)
423 GdkWindow *gdkwin;
425 g_return_if_fail (icon->manager_window != None);
427 gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
428 icon->manager_window);
430 gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
432 icon->manager_window = None;
434 egg_tray_icon_update_manager_window (icon, TRUE);
437 #endif
439 gboolean
440 egg_tray_icon_have_manager (EggTrayIcon *icon)
442 GtkPlug * plug = GTK_PLUG (icon);
444 if (plug->socket_window)
445 return TRUE;
446 else
447 return FALSE;
450 static void
451 egg_tray_icon_realize (GtkWidget *widget)
453 #ifdef GDK_WINDOWING_X11
454 EggTrayIcon *icon = EGG_TRAY_ICON (widget);
455 GdkScreen *screen;
456 GdkDisplay *display;
457 Display *xdisplay;
458 char buffer[256];
459 GdkWindow *root_window;
461 if (GTK_WIDGET_CLASS (parent_class)->realize)
462 GTK_WIDGET_CLASS (parent_class)->realize (widget);
464 make_transparent (widget, NULL);
466 screen = gtk_widget_get_screen (widget);
467 display = gdk_screen_get_display (screen);
468 xdisplay = gdk_x11_display_get_xdisplay (display);
470 /* Now see if there's a manager window around */
471 g_snprintf (buffer, sizeof (buffer),
472 "_NET_SYSTEM_TRAY_S%d",
473 gdk_screen_get_number (screen));
475 icon->selection_atom = XInternAtom (xdisplay, buffer, False);
477 icon->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
479 icon->system_tray_opcode_atom = XInternAtom (xdisplay,
480 "_NET_SYSTEM_TRAY_OPCODE",
481 False);
483 icon->orientation_atom = XInternAtom (xdisplay,
484 "_NET_SYSTEM_TRAY_ORIENTATION",
485 False);
487 egg_tray_icon_update_manager_window (icon, FALSE);
488 egg_tray_icon_send_dock_request (icon);
490 root_window = gdk_screen_get_root_window (screen);
492 /* Add a root window filter so that we get changes on MANAGER */
493 gdk_window_add_filter (root_window,
494 egg_tray_icon_manager_filter, icon);
495 #endif
498 static void
499 egg_tray_icon_add (GtkContainer *container, GtkWidget *widget)
501 g_signal_connect (widget, "realize",
502 G_CALLBACK (make_transparent), NULL);
503 GTK_CONTAINER_CLASS (parent_class)->add (container, widget);
506 EggTrayIcon *
507 egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
509 g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
511 return g_object_new (EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
514 EggTrayIcon*
515 egg_tray_icon_new (const gchar *name)
517 return g_object_new (EGG_TYPE_TRAY_ICON, "title", name, NULL);
520 guint
521 egg_tray_icon_send_message (EggTrayIcon *icon,
522 gint timeout,
523 const gchar *message,
524 gint len)
526 g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
527 g_return_val_if_fail (timeout >= 0, 0);
528 g_return_val_if_fail (message != NULL, 0);
530 #ifdef HAVE_NOTIFY
531 egg_tray_icon_notify (icon, timeout, _("Notification"), NULL, message);
532 #endif
534 return 1;
537 void
538 egg_tray_icon_cancel_message (EggTrayIcon *icon,
539 guint id)
541 g_return_if_fail (EGG_IS_TRAY_ICON (icon));
543 #ifdef HAVE_NOTIFY
544 if (icon->notify->handle)
546 #if (LIBNOTIFY_VERSION_MAJOR > 0 || LIBNOTIFY_VERSION_MINOR >= 3)
547 notify_notification_close (icon->notify->handle, NULL);
548 #elif (LIBNOTIFY_VERSION_MINOR == 2)
549 notify_close (icon->notify->handle);
550 icon->notify->handle = NULL;
551 #endif
553 #endif
556 GtkOrientation
557 egg_tray_icon_get_orientation (EggTrayIcon *icon)
559 g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
561 return icon->orientation;
564 void
565 egg_tray_icon_notify (EggTrayIcon *icon,
566 guint timeout,
567 const char *primary,
568 GtkWidget *msgicon,
569 const char *secondary)
571 #ifdef HAVE_NOTIFY
572 #if (LIBNOTIFY_VERSION_MAJOR > 0 || LIBNOTIFY_VERSION_MINOR >= 3)
573 GtkRequisition size;
574 GdkPixbuf *pixbuf;
575 int x;
576 int y;
577 char *esc_primary;
578 char *esc_secondary;
580 if (!notify_is_initted ())
581 if (!notify_init ("rhythmbox"))
582 return;
584 if (icon->notify->handle != NULL)
586 notify_notification_close (icon->notify->handle, NULL);
589 if (primary == NULL)
591 primary = "";
593 if (secondary == NULL)
595 secondary = "";
598 #if (LIBNOTIFY_VERSION_MAJOR == 0 && LIBNOTIFY_VERSION_MINOR == 3 && LIBNOTIFY_VERSION_MICRO == 0)
599 esc_primary = strdup (primary);
600 #else
601 esc_primary = g_markup_escape_text (primary, strlen (primary));
602 #endif
603 esc_secondary = g_markup_escape_text (secondary, strlen (secondary));
604 icon->notify->handle = notify_notification_new (esc_primary,
605 esc_secondary,
606 NULL,
607 GTK_WIDGET (icon));
608 g_free (esc_primary);
609 g_free (esc_secondary);
611 notify_notification_set_timeout (icon->notify->handle, timeout);
613 if (msgicon)
615 pixbuf = gtk_image_get_pixbuf (GTK_IMAGE (msgicon));
617 else
619 GtkIconTheme *theme;
620 gint icon_size;
622 theme = gtk_icon_theme_get_default ();
623 gtk_icon_size_lookup (GTK_ICON_SIZE_DIALOG, &icon_size, NULL);
624 pixbuf = gtk_icon_theme_load_icon (theme,
625 "gnome-media-player",
626 icon_size,
628 NULL);
631 if (pixbuf)
633 #if (LIBNOTIFY_VERSION_MAJOR == 0 && LIBNOTIFY_VERSION_MINOR <=3 && LIBNOTIFY_VERSION_MICRO < 2)
634 notify_notification_set_icon_data_from_pixbuf (icon->notify->handle, pixbuf);
635 #else
636 notify_notification_set_icon_from_pixbuf (icon->notify->handle, pixbuf);
637 #endif
638 g_object_unref (pixbuf);
641 gdk_window_get_origin (GTK_WIDGET (icon)->window, &x, &y);
642 gtk_widget_size_request (GTK_WIDGET (icon), &size);
643 x += size.width / 2;
644 y += size.height;
645 notify_notification_set_hint_int32 (icon->notify->handle, "x", x);
646 notify_notification_set_hint_int32 (icon->notify->handle, "y", y);
648 if (! notify_notification_show (icon->notify->handle, NULL))
650 g_warning ("failed to send notification (%s)", primary);
653 return;
654 #elif (LIBNOTIFY_VERSION_MAJOR == 0 && LIBNOTIFY_VERSION_MINOR == 2)
655 gint x, y;
656 GtkRequisition size;
657 NotifyIcon *icon_notify = NULL;
658 NotifyHints *hints;
659 char *fn;
660 char *esc_primary;
661 char *esc_secondary;
663 if (!notify_is_initted ())
664 if (!notify_init ("rhythmbox"))
665 return;
667 gdk_window_get_origin (GTK_WIDGET (icon)->window, &x, &y);
668 gtk_widget_size_request (GTK_WIDGET (icon), &size);
669 x += size.width / 2;
670 y += size.height;
672 hints = notify_hints_new ();
673 notify_hints_set_int (hints, "x", x);
674 notify_hints_set_int (hints, "y", y);
676 if (msgicon)
678 GdkPixbuf *pix;
679 pix = gtk_image_get_pixbuf (GTK_IMAGE (msgicon));
680 if (pix)
682 char *tmp;
683 GError *error = NULL;
684 tmp = g_strdup_printf ("%s/.gnome2/rb-notify-icon.png", g_get_home_dir ());
685 if (gdk_pixbuf_save (pix, tmp, "png", &error, NULL))
687 icon_notify = notify_icon_new_from_uri (tmp);
689 else
691 icon_notify = NULL;
693 g_free (pix);
694 g_free (tmp);
697 else
699 fn = g_strconcat (RB_STOCK_TRAY_ICON, ".png", NULL);
700 icon_notify = notify_icon_new_from_uri (rb_file (fn));
701 g_free (fn);
704 if (icon->notify->handle)
706 notify_close (icon->notify->handle);
709 esc_primary = g_markup_escape_text (primary, strlen (primary));
710 esc_secondary = g_markup_escape_text (secondary, strlen (secondary));
711 icon->notify->hints = hints;
712 icon->notify->icon = icon_notify;
713 icon->notify->handle = notify_send_notification (NULL, "transfer",
714 NOTIFY_URGENCY_LOW,
715 primary,
716 secondary,
717 icon_notify,
718 TRUE, timeout/1000,
719 hints,
720 NULL,
722 g_free (esc_primary);
723 g_free (esc_secondary);
724 return;
725 #endif
726 #endif