1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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.
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)
33 #include <X11/Xatom.h>
34 #elif defined (GDK_WINDOWING_WIN32)
35 #include <gdk/gdkwin32.h>
38 #include <libnotify/notify.h>
41 #ifndef EGG_COMPILATION
43 #define _(x) dgettext (GETTEXT_PACKAGE, x)
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
65 #if (LIBNOTIFY_VERSION_MAJOR == 0 && LIBNOTIFY_VERSION_MINOR == 2)
69 #elif (LIBNOTIFY_VERSION_MAJOR > 0 || LIBNOTIFY_VERSION_MINOR >= 3)
70 NotifyNotification
*handle
;
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
,
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
,
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
);
98 egg_tray_icon_get_type (void)
100 static GType 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
),
114 (GInstanceInitFunc
) egg_tray_icon_init
117 our_type
= g_type_register_static (GTK_TYPE_PLUG
, "EggTrayIcon", &our_info
, 0);
124 egg_tray_icon_init (EggTrayIcon
*icon
)
127 icon
->orientation
= GTK_ORIENTATION_HORIZONTAL
;
129 icon
->notify
= g_new0 (Notify
, 1);
131 gtk_widget_add_events (GTK_WIDGET (icon
), GDK_PROPERTY_CHANGE_MASK
);
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
,
152 g_param_spec_enum ("orientation",
154 _("The orientation of the tray."),
155 GTK_TYPE_ORIENTATION
,
156 GTK_ORIENTATION_HORIZONTAL
,
159 #if defined (GDK_WINDOWING_X11)
161 #elif defined (GDK_WINDOWING_WIN32)
162 g_warning ("Port eggtrayicon to Win32");
164 g_warning ("Port eggtrayicon to this GTK+ backend");
169 egg_tray_icon_get_property (GObject
*object
,
174 EggTrayIcon
*icon
= EGG_TRAY_ICON (object
);
178 case PROP_ORIENTATION
:
179 g_value_set_enum (value
, icon
->orientation
);
182 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
187 #ifdef GDK_WINDOWING_X11
190 egg_tray_icon_get_orientation_property (EggTrayIcon
*icon
)
203 g_assert (icon
->manager_window
!= None
);
205 xdisplay
= GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon
)));
207 gdk_error_trap_push ();
209 result
= XGetWindowProperty (xdisplay
,
210 icon
->manager_window
,
211 icon
->orientation_atom
,
214 &type
, &format
, &nitems
,
215 &bytes_after
, &(prop
.prop_ch
));
216 error
= gdk_error_trap_pop ();
218 if (error
|| result
!= Success
)
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");
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
;
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
)
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
);
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
);
305 g_free (EGG_TRAY_ICON (widget
)->notify
);
309 #ifdef GDK_WINDOWING_X11
312 egg_tray_icon_send_manager_message (EggTrayIcon
*icon
,
319 XClientMessageEvent ev
;
322 ev
.type
= ClientMessage
;
324 ev
.message_type
= icon
->system_tray_opcode_atom
;
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 ();
336 icon
->manager_window
, False
, NoEventMask
, (XEvent
*)&ev
);
337 XSync (display
, False
);
338 gdk_error_trap_pop ();
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
)),
352 egg_tray_icon_update_manager_window (EggTrayIcon
*icon
,
353 gboolean dock_if_realized
)
357 if (icon
->manager_window
!= None
)
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
);
374 if (icon
->manager_window
!= None
)
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
);
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
);
399 make_transparent_again (GtkWidget
*widget
, GtkStyle
*previous_style
,
402 gdk_window_set_back_pixmap (widget
->window
, NULL
, TRUE
);
406 make_transparent (GtkWidget
*widget
, gpointer user_data
)
408 if (GTK_WIDGET_NO_WINDOW (widget
) || GTK_WIDGET_APP_PAINTABLE (widget
))
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
);
421 egg_tray_icon_manager_window_destroyed (EggTrayIcon
*icon
)
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
);
440 egg_tray_icon_have_manager (EggTrayIcon
*icon
)
442 GtkPlug
* plug
= GTK_PLUG (icon
);
444 if (plug
->socket_window
)
451 egg_tray_icon_realize (GtkWidget
*widget
)
453 #ifdef GDK_WINDOWING_X11
454 EggTrayIcon
*icon
= EGG_TRAY_ICON (widget
);
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",
483 icon
->orientation_atom
= XInternAtom (xdisplay
,
484 "_NET_SYSTEM_TRAY_ORIENTATION",
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
);
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
);
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
);
515 egg_tray_icon_new (const gchar
*name
)
517 return g_object_new (EGG_TYPE_TRAY_ICON
, "title", name
, NULL
);
521 egg_tray_icon_send_message (EggTrayIcon
*icon
,
523 const gchar
*message
,
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);
531 egg_tray_icon_notify (icon
, timeout
, _("Notification"), NULL
, message
);
538 egg_tray_icon_cancel_message (EggTrayIcon
*icon
,
541 g_return_if_fail (EGG_IS_TRAY_ICON (icon
));
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
;
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
;
565 egg_tray_icon_notify (EggTrayIcon
*icon
,
569 const char *secondary
)
572 #if (LIBNOTIFY_VERSION_MAJOR > 0 || LIBNOTIFY_VERSION_MINOR >= 3)
580 if (!notify_is_initted ())
581 if (!notify_init ("rhythmbox"))
584 if (icon
->notify
->handle
!= NULL
)
586 notify_notification_close (icon
->notify
->handle
, NULL
);
593 if (secondary
== NULL
)
598 #if (LIBNOTIFY_VERSION_MAJOR == 0 && LIBNOTIFY_VERSION_MINOR == 3 && LIBNOTIFY_VERSION_MICRO == 0)
599 esc_primary
= strdup (primary
);
601 esc_primary
= g_markup_escape_text (primary
, strlen (primary
));
603 esc_secondary
= g_markup_escape_text (secondary
, strlen (secondary
));
604 icon
->notify
->handle
= notify_notification_new (esc_primary
,
608 g_free (esc_primary
);
609 g_free (esc_secondary
);
611 notify_notification_set_timeout (icon
->notify
->handle
, timeout
);
615 pixbuf
= gtk_image_get_pixbuf (GTK_IMAGE (msgicon
));
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",
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
);
636 notify_notification_set_icon_from_pixbuf (icon
->notify
->handle
, pixbuf
);
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
);
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
);
654 #elif (LIBNOTIFY_VERSION_MAJOR == 0 && LIBNOTIFY_VERSION_MINOR == 2)
657 NotifyIcon
*icon_notify
= NULL
;
663 if (!notify_is_initted ())
664 if (!notify_init ("rhythmbox"))
667 gdk_window_get_origin (GTK_WIDGET (icon
)->window
, &x
, &y
);
668 gtk_widget_size_request (GTK_WIDGET (icon
), &size
);
672 hints
= notify_hints_new ();
673 notify_hints_set_int (hints
, "x", x
);
674 notify_hints_set_int (hints
, "y", y
);
679 pix
= gtk_image_get_pixbuf (GTK_IMAGE (msgicon
));
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
);
699 fn
= g_strconcat (RB_STOCK_TRAY_ICON
, ".png", NULL
);
700 icon_notify
= notify_icon_new_from_uri (rb_file (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",
722 g_free (esc_primary
);
723 g_free (esc_secondary
);