/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* eggtrayicon.c * Copyright (C) 2002 Anders Carlsson * * 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 #include #include #include "eggtrayicon.h" #include #if defined (GDK_WINDOWING_X11) #include #include #elif defined (GDK_WINDOWING_WIN32) #include #endif #ifndef EGG_COMPILATION #ifndef _ #define _(x) dgettext (GETTEXT_PACKAGE, x) #define N_(x) x #endif #else #define _(x) x #define N_(x) x #endif #define SYSTEM_TRAY_REQUEST_DOCK 0 #define SYSTEM_TRAY_BEGIN_MESSAGE 1 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 #define SYSTEM_TRAY_ORIENTATION_HORZ 0 #define SYSTEM_TRAY_ORIENTATION_VERT 1 enum { PROP_0, PROP_ORIENTATION }; 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_get_property(GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void egg_tray_icon_realize(GtkWidget * widget); static void egg_tray_icon_unrealize(GtkWidget * widget); static void egg_tray_icon_add(GtkContainer * container, GtkWidget * widget); #ifdef GDK_WINDOWING_X11 static void egg_tray_icon_update_manager_window(EggTrayIcon * icon, gboolean dock_if_realized); static void egg_tray_icon_manager_window_destroyed(EggTrayIcon * icon); #endif 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; icon->orientation = GTK_ORIENTATION_HORIZONTAL; gtk_widget_add_events(GTK_WIDGET(icon), GDK_PROPERTY_CHANGE_MASK); } static void egg_tray_icon_class_init(EggTrayIconClass * klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GtkContainerClass *container_class = (GtkContainerClass *) klass; parent_class = g_type_class_peek_parent(klass); gobject_class->get_property = egg_tray_icon_get_property; widget_class->realize = egg_tray_icon_realize; widget_class->unrealize = egg_tray_icon_unrealize; container_class->add = egg_tray_icon_add; g_object_class_install_property(gobject_class, PROP_ORIENTATION, g_param_spec_enum("orientation", _("Orientation"), _ ("The orientation of the tray."), GTK_TYPE_ORIENTATION, GTK_ORIENTATION_HORIZONTAL, G_PARAM_READABLE)); #if defined (GDK_WINDOWING_X11) /* Nothing */ #elif defined (GDK_WINDOWING_WIN32) g_warning("Port eggtrayicon to Win32"); #else g_warning("Port eggtrayicon to this GTK+ backend"); #endif } static void egg_tray_icon_get_property(GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { EggTrayIcon *icon = EGG_TRAY_ICON(object); switch (prop_id) { case PROP_ORIENTATION: g_value_set_enum(value, icon->orientation); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } #ifdef GDK_WINDOWING_X11 static void egg_tray_icon_get_orientation_property(EggTrayIcon * icon) { Display *xdisplay; Atom type; int format; union { gulong *prop; guchar *prop_ch; } prop = { NULL}; gulong nitems; gulong bytes_after; int error, result; g_assert(icon->manager_window != None); xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(GTK_WIDGET(icon))); gdk_error_trap_push(); type = None; result = XGetWindowProperty(xdisplay, icon->manager_window, icon->orientation_atom, 0, G_MAXLONG, FALSE, XA_CARDINAL, &type, &format, &nitems, &bytes_after, &(prop.prop_ch)); error = gdk_error_trap_pop(); if (error || result != Success) return; if (type == XA_CARDINAL) { GtkOrientation orientation; orientation = (prop.prop[0] == SYSTEM_TRAY_ORIENTATION_HORZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL; if (icon->orientation != orientation) { icon->orientation = orientation; g_object_notify(G_OBJECT(icon), "orientation"); } } if (prop.prop) XFree(prop.prop); } 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, TRUE); } else if (xev->xany.window == icon->manager_window) { if (xev->xany.type == PropertyNotify && xev->xproperty.atom == icon->orientation_atom) { egg_tray_icon_get_orientation_property(icon); } if (xev->xany.type == DestroyNotify) { egg_tray_icon_manager_window_destroyed(icon); } } return GDK_FILTER_CONTINUE; } #endif static void egg_tray_icon_unrealize(GtkWidget * widget) { #ifdef GDK_WINDOWING_X11 EggTrayIcon *icon = EGG_TRAY_ICON(widget); GdkWindow *root_window; if (icon->manager_window != None) { GdkWindow *gdkwin; gdkwin = gdk_window_lookup_for_display(gtk_widget_get_display(widget), icon->manager_window); gdk_window_remove_filter(gdkwin, egg_tray_icon_manager_filter, icon); } root_window = gdk_screen_get_root_window(gtk_widget_get_screen(widget)); gdk_window_remove_filter(root_window, egg_tray_icon_manager_filter, icon); if (GTK_WIDGET_CLASS(parent_class)->unrealize) (*GTK_WIDGET_CLASS(parent_class)->unrealize) (widget); #endif } #ifdef GDK_WINDOWING_X11 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; display = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(GTK_WIDGET(icon))); 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, gboolean dock_if_realized) { Display *xdisplay; if (icon->manager_window != None) return; xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(GTK_WIDGET(icon))); XGrabServer(xdisplay); icon->manager_window = XGetSelectionOwner(xdisplay, icon->selection_atom); if (icon->manager_window != None) XSelectInput(xdisplay, icon->manager_window, StructureNotifyMask | PropertyChangeMask); XUngrabServer(xdisplay); XFlush(xdisplay); if (icon->manager_window != None) { GdkWindow *gdkwin; gdkwin = gdk_window_lookup_for_display(gtk_widget_get_display (GTK_WIDGET(icon)), icon->manager_window); gdk_window_add_filter(gdkwin, egg_tray_icon_manager_filter, icon); if (dock_if_realized && GTK_WIDGET_REALIZED(icon)) egg_tray_icon_send_dock_request(icon); egg_tray_icon_get_orientation_property(icon); } } static void egg_tray_icon_manager_window_destroyed(EggTrayIcon * icon) { GdkWindow *gdkwin; g_return_if_fail(icon->manager_window != None); gdkwin = gdk_window_lookup_for_display(gtk_widget_get_display (GTK_WIDGET(icon)), icon->manager_window); gdk_window_remove_filter(gdkwin, egg_tray_icon_manager_filter, icon); icon->manager_window = None; egg_tray_icon_update_manager_window(icon, TRUE); } #endif static gboolean transparent_expose_event(GtkWidget * widget, GdkEventExpose * event, gpointer user_data) { gdk_window_clear_area(widget->window, event->area.x, event->area.y, event->area.width, event->area.height); return FALSE; } static void make_transparent_again(GtkWidget * widget, GtkStyle * previous_style, gpointer user_data) { gdk_window_set_back_pixmap(widget->window, NULL, TRUE); } static void make_transparent(GtkWidget * widget, gpointer user_data) { if (GTK_WIDGET_NO_WINDOW(widget) || GTK_WIDGET_APP_PAINTABLE(widget)) return; gtk_widget_set_app_paintable(widget, TRUE); gtk_widget_set_double_buffered(widget, FALSE); gdk_window_set_back_pixmap(widget->window, NULL, TRUE); g_signal_connect(widget, "expose_event", G_CALLBACK(transparent_expose_event), NULL); g_signal_connect_after(widget, "style_set", G_CALLBACK(make_transparent_again), NULL); } static void egg_tray_icon_realize(GtkWidget * widget) { #ifdef GDK_WINDOWING_X11 EggTrayIcon *icon = EGG_TRAY_ICON(widget); GdkScreen *screen; GdkDisplay *display; Display *xdisplay; char buffer[256]; GdkWindow *root_window; if (GTK_WIDGET_CLASS(parent_class)->realize) GTK_WIDGET_CLASS(parent_class)->realize(widget); make_transparent(widget, NULL); screen = gtk_widget_get_screen(widget); display = gdk_screen_get_display(screen); xdisplay = gdk_x11_display_get_xdisplay(display); /* Now see if there's a manager window around */ g_snprintf(buffer, sizeof(buffer), "_NET_SYSTEM_TRAY_S%d", gdk_screen_get_number(screen)); icon->selection_atom = XInternAtom(xdisplay, buffer, False); icon->manager_atom = XInternAtom(xdisplay, "MANAGER", False); icon->system_tray_opcode_atom = XInternAtom(xdisplay, "_NET_SYSTEM_TRAY_OPCODE", False); icon->orientation_atom = XInternAtom(xdisplay, "_NET_SYSTEM_TRAY_ORIENTATION", False); egg_tray_icon_update_manager_window(icon, FALSE); egg_tray_icon_send_dock_request(icon); root_window = gdk_screen_get_root_window(screen); /* Add a root window filter so that we get changes on MANAGER */ gdk_window_add_filter(root_window, egg_tray_icon_manager_filter, icon); #endif } static void egg_tray_icon_add(GtkContainer * container, GtkWidget * widget) { g_signal_connect(widget, "realize", G_CALLBACK(make_transparent), NULL); GTK_CONTAINER_CLASS(parent_class)->add(container, widget); } EggTrayIcon *egg_tray_icon_new_for_screen(GdkScreen * screen, const char *name) { g_return_val_if_fail(GDK_IS_SCREEN(screen), NULL); return g_object_new(EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL); } EggTrayIcon *egg_tray_icon_new(const gchar * name) { return g_object_new(EGG_TYPE_TRAY_ICON, "title", name, NULL); } 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); #ifdef GDK_WINDOWING_X11 if (icon->manager_window == None) return 0; #endif if (len < 0) len = strlen(message); stamp = icon->stamp++; #ifdef GDK_WINDOWING_X11 /* Get ready to send the message */ egg_tray_icon_send_manager_message(icon, SYSTEM_TRAY_BEGIN_MESSAGE, icon->manager_window, timeout, len, stamp); /* Now to send the actual message */ gdk_error_trap_push(); while (len > 0) { XClientMessageEvent ev; Display *xdisplay; xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display (GTK_WIDGET(icon))); ev.type = ClientMessage; ev.window = icon->manager_window; 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(); #endif 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); #ifdef GDK_WINDOWING_X11 egg_tray_icon_send_manager_message(icon, SYSTEM_TRAY_CANCEL_MESSAGE, (Window) gtk_plug_get_id(GTK_PLUG(icon)), id, 0, 0); #endif } GtkOrientation egg_tray_icon_get_orientation(EggTrayIcon * icon) { g_return_val_if_fail(EGG_IS_TRAY_ICON(icon), GTK_ORIENTATION_HORIZONTAL); return icon->orientation; }