From: <sea...@us...> - 2006-10-05 23:24:10
|
Revision: 17433 http://svn.sourceforge.net/gaim/?rev=17433&view=rev Author: seanegan Date: 2006-10-05 16:24:00 -0700 (Thu, 05 Oct 2006) Log Message: ----------- Depluginize the docklet. Modified Paths: -------------- trunk/gtk/Makefile.am trunk/gtk/gtkmain.c trunk/gtk/plugins/docklet/Makefile.am trunk/gtk/plugins/docklet/docklet.c trunk/libgaim/util.c Added Paths: ----------- trunk/gtk/docklet-x11.c trunk/gtk/docklet.h trunk/gtk/eggtrayicon.c trunk/gtk/eggtrayicon.h trunk/gtk/gtkdocklet-win32.c trunk/gtk/gtkdocklet-x11.c trunk/gtk/gtkdocklet.c trunk/gtk/gtkdocklet.h trunk/gtk/win32/MinimizeToTray.c trunk/gtk/win32/MinimizeToTray.h Removed Paths: ------------- trunk/gtk/plugins/docklet/docklet-win32.c trunk/gtk/plugins/docklet/docklet-x11.c trunk/gtk/plugins/docklet/docklet.h trunk/gtk/plugins/docklet/eggtrayicon.c trunk/gtk/plugins/docklet/eggtrayicon.h Modified: trunk/gtk/Makefile.am =================================================================== --- trunk/gtk/Makefile.am 2006-10-04 20:31:44 UTC (rev 17432) +++ trunk/gtk/Makefile.am 2006-10-05 23:24:00 UTC (rev 17433) @@ -59,6 +59,7 @@ bin_PROGRAMS = gaim gaim_SOURCES = \ + eggtrayicon.c \ gaimcombobox.c \ gaimstock.c \ gtkaccount.c \ @@ -73,6 +74,8 @@ gtkdebug.c \ gtkdialogs.c \ gtkdnd-hints.c \ + gtkdocklet.c \ + gtkdocklet-x11.c \ gtkeventloop.c \ gtkexpander.c \ gtkft.c \ @@ -100,6 +103,7 @@ gtkwhiteboard.c gaim_headers = \ + eggtrayicon.h \ gtkaccount.h \ gtkblist.h \ gtkcelllayout.h \ @@ -116,6 +120,7 @@ gtkdebug.h \ gtkdialogs.h \ gtkdnd-hints.h \ + gtkdocklet.h \ gtkeventloop.h \ gtkexpander.h \ gtkft.h \ Copied: trunk/gtk/docklet-x11.c (from rev 17432, trunk/gtk/plugins/docklet/docklet-x11.c) =================================================================== --- trunk/gtk/docklet-x11.c (rev 0) +++ trunk/gtk/docklet-x11.c 2006-10-05 23:24:00 UTC (rev 17433) @@ -0,0 +1,305 @@ +/* + * System tray icon (aka docklet) plugin for Gaim + * + * Copyright (C) 2002-3 Robert McQueen <rob...@de...> + * Copyright (C) 2003 Herman Bloggs <her...@ya...> + * Inspired by a similar plugin by: + * John (J5) Palmieri <jo...@ma...> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "internal.h" +#include "gtkgaim.h" +#include "debug.h" +#include "gaimstock.h" + +#include "gaim.h" +#include "gtkdialogs.h" + +#include "eggtrayicon.h" +#include "docklet.h" + +#define EMBED_TIMEOUT 5000 + +/* globals */ +static EggTrayIcon *docklet = NULL; +static GtkWidget *image = NULL; +static GtkTooltips *tooltips = NULL; +static GdkPixbuf *blank_icon = NULL; +static int embed_timeout = 0; + +/* protos */ +static void docklet_x11_create(void); + +static gboolean +docklet_x11_create_cb() +{ + docklet_x11_create(); + + return FALSE; /* for when we're called by the glib idle handler */ +} + +static void +docklet_x11_embedded_cb(GtkWidget *widget, void *data) +{ + gaim_debug(GAIM_DEBUG_INFO, "tray icon", "embedded\n"); + + g_source_remove(embed_timeout); + embed_timeout = 0; + docklet_embedded(); +} + +static void +docklet_x11_destroyed_cb(GtkWidget *widget, void *data) +{ + gaim_debug(GAIM_DEBUG_INFO, "tray icon", "destroyed\n"); + + docklet_remove(); + + g_object_unref(G_OBJECT(docklet)); + docklet = NULL; + + g_idle_add(docklet_x11_create_cb, &handle); +} + +static void +docklet_x11_clicked_cb(GtkWidget *button, GdkEventButton *event, void *data) +{ + if (event->type != GDK_BUTTON_PRESS) + return; + + docklet_clicked(event->button); +} + +static void +docklet_x11_update_icon(DockletStatus icon) +{ + const gchar *icon_name = NULL; + + g_return_if_fail(image != NULL); + + switch (icon) { + case DOCKLET_STATUS_OFFLINE: + icon_name = GAIM_STOCK_ICON_OFFLINE; + break; + case DOCKLET_STATUS_CONNECTING: + icon_name = GAIM_STOCK_ICON_CONNECT; + break; + case DOCKLET_STATUS_ONLINE: + icon_name = GAIM_STOCK_ICON_ONLINE; + break; + case DOCKLET_STATUS_ONLINE_PENDING: + icon_name = GAIM_STOCK_ICON_ONLINE_MSG; + break; + case DOCKLET_STATUS_AWAY: + icon_name = GAIM_STOCK_ICON_AWAY; + break; + case DOCKLET_STATUS_AWAY_PENDING: + icon_name = GAIM_STOCK_ICON_AWAY_MSG; + break; + } + + if(icon_name) + gtk_image_set_from_stock(GTK_IMAGE(image), icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR); + +#if 0 + GdkPixbuf *p; + GdkBitmap *mask = NULL; + + p = gtk_widget_render_icon(GTK_WIDGET(image), icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR, NULL); + + if (p && (gdk_pixbuf_get_colorspace(p) == GDK_COLORSPACE_RGB) && (gdk_pixbuf_get_bits_per_sample(p) == 8) + && (gdk_pixbuf_get_has_alpha(p)) && (gdk_pixbuf_get_n_channels(p) == 4)) { + int len = gdk_pixbuf_get_width(p) * gdk_pixbuf_get_height(p); + guchar *data = gdk_pixbuf_get_pixels(p); + guchar *bitmap = g_malloc((len / 8) + 1); + int i; + + for (i = 0; i < len; i++) + if (data[i*4 + 3] > 55) + bitmap[i/8] |= 1 << i % 8; + else + bitmap[i/8] &= ~(1 << i % 8); + + mask = gdk_bitmap_create_from_data(GDK_DRAWABLE(GTK_WIDGET(image)->window), bitmap, gdk_pixbuf_get_width(p), gdk_pixbuf_get_height(p)); + g_free(bitmap); + } + + if (mask) + gdk_window_shape_combine_mask(image->window, mask, 0, 0); + + g_object_unref(G_OBJECT(p)); +#endif +} + +static void +docklet_x11_blank_icon() +{ + if (!blank_icon) { + gint width, height; + + gtk_icon_size_lookup(GTK_ICON_SIZE_LARGE_TOOLBAR, &width, &height); + blank_icon = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height); + gdk_pixbuf_fill(blank_icon, 0); + } + + gtk_image_set_from_pixbuf(GTK_IMAGE(image), blank_icon); +} + +static void +docklet_x11_set_tooltip(gchar *tooltip) +{ + if (!tooltips) + tooltips = gtk_tooltips_new(); + + /* image->parent is a GtkEventBox */ + if (tooltip) { + gtk_tooltips_enable(tooltips); + gtk_tooltips_set_tip(tooltips, image->parent, tooltip, NULL); + } else { + gtk_tooltips_set_tip(tooltips, image->parent, "", NULL); + gtk_tooltips_disable(tooltips); + } +} + +#if GTK_CHECK_VERSION(2,2,0) +static void +docklet_x11_position_menu(GtkMenu *menu, int *x, int *y, gboolean *push_in, + gpointer user_data) +{ + GtkWidget *widget = GTK_WIDGET(docklet); + GtkRequisition req; + gint menu_xpos, menu_ypos; + + gtk_widget_size_request(GTK_WIDGET(menu), &req); + gdk_window_get_origin(widget->window, &menu_xpos, &menu_ypos); + + menu_xpos += widget->allocation.x; + menu_ypos += widget->allocation.y; + + if (menu_ypos > gdk_screen_get_height(gtk_widget_get_screen(widget)) / 2) + menu_ypos -= req.height; + else + menu_ypos += widget->allocation.height; + + *x = menu_xpos; + *y = menu_ypos; + + *push_in = TRUE; +} +#endif + +static void +docklet_x11_destroy() +{ + g_return_if_fail(docklet != NULL); + + docklet_remove(); + + g_signal_handlers_disconnect_by_func(G_OBJECT(docklet), G_CALLBACK(docklet_x11_destroyed_cb), NULL); + gtk_widget_destroy(GTK_WIDGET(docklet)); + + g_object_unref(G_OBJECT(docklet)); + docklet = NULL; + + if (blank_icon) + g_object_unref(G_OBJECT(blank_icon)); + blank_icon = NULL; + + image = NULL; + + gaim_debug(GAIM_DEBUG_INFO, "tray icon", "destroyed\n"); +} + +static gboolean +docklet_x11_embed_timeout_cb() +{ + /* The docklet was not embedded within the timeout. + * Remove it as a visibility manager, but leave the plugin + * loaded so that it can embed automatically if/when a notification + * area becomes available. + */ + gaim_debug_info("tray icon", "failed to embed within timeout\n"); + docklet_remove(); + + return FALSE; +} + +static void +docklet_x11_create() +{ + GtkWidget *box; + + if (docklet) { + /* if this is being called when a tray icon exists, it's because + something messed up. try destroying it before we proceed, + although docklet_refcount may be all hosed. hopefully won't happen. */ + gaim_debug(GAIM_DEBUG_WARNING, "tray icon", "trying to create icon but it already exists?\n"); + docklet_x11_destroy(); + } + + docklet = egg_tray_icon_new("Gaim"); + box = gtk_event_box_new(); + image = gtk_image_new(); + + g_signal_connect(G_OBJECT(docklet), "embedded", G_CALLBACK(docklet_x11_embedded_cb), NULL); + g_signal_connect(G_OBJECT(docklet), "destroy", G_CALLBACK(docklet_x11_destroyed_cb), NULL); + g_signal_connect(G_OBJECT(box), "button-press-event", G_CALLBACK(docklet_x11_clicked_cb), NULL); + + gtk_container_add(GTK_CONTAINER(box), image); + gtk_container_add(GTK_CONTAINER(docklet), box); + + if (!gtk_check_version(2,4,0)) + g_object_set(G_OBJECT(box), "visible-window", FALSE, NULL); + + gtk_widget_show_all(GTK_WIDGET(docklet)); + + /* ref the docklet before we bandy it about the place */ + g_object_ref(G_OBJECT(docklet)); + + /* This is a hack to avoid a race condition between the docklet getting + * embedded in the notification area and the gtkblist restoring its + * previous visibility state. If the docklet does not get embedded within + * the timeout, it will be removed as a visibility manager until it does + * get embedded. Ideally, we would only call docklet_embedded() when the + * icon was actually embedded. + */ + docklet_embedded(); + embed_timeout = g_timeout_add(EMBED_TIMEOUT, docklet_x11_embed_timeout_cb, NULL); + + gaim_debug(GAIM_DEBUG_INFO, "tray icon", "created\n"); +} + +static struct docklet_ui_ops ui_ops = +{ + docklet_x11_create, + docklet_x11_destroy, + docklet_x11_update_icon, + docklet_x11_blank_icon, + docklet_x11_set_tooltip, +#if GTK_CHECK_VERSION(2,2,0) + docklet_x11_position_menu +#else + NULL +#endif +}; + +void +docklet_ui_init() +{ + docklet_set_ui_ops(&ui_ops); +} Copied: trunk/gtk/docklet.h (from rev 17432, trunk/gtk/plugins/docklet/docklet.h) =================================================================== --- trunk/gtk/docklet.h (rev 0) +++ trunk/gtk/docklet.h 2006-10-05 23:24:00 UTC (rev 17433) @@ -0,0 +1,61 @@ +/* + * System tray icon (aka docklet) plugin for Gaim + * + * Copyright (C) 2002-3 Robert McQueen <rob...@de...> + * Copyright (C) 2003 Herman Bloggs <her...@ya...> + * Inspired by a similar plugin by: + * John (J5) Palmieri <jo...@ma...> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _DOCKLET_H_ +#define _DOCKLET_H_ + +typedef enum +{ + DOCKLET_STATUS_OFFLINE, + DOCKLET_STATUS_ONLINE, + DOCKLET_STATUS_ONLINE_PENDING, + DOCKLET_STATUS_AWAY, + DOCKLET_STATUS_AWAY_PENDING, + DOCKLET_STATUS_CONNECTING +} DockletStatus; + +struct docklet_ui_ops +{ + void (*create)(void); + void (*destroy)(void); + void (*update_icon)(DockletStatus); + void (*blank_icon)(void); + void (*set_tooltip)(gchar *); + GtkMenuPositionFunc position_menu; +}; + +/* useful for setting idle callbacks that will be cleaned up */ +extern GaimPlugin *handle; + +/* functions in docklet.c */ +void docklet_clicked(int); +void docklet_embedded(void); +void docklet_remove(void); +void docklet_set_ui_ops(struct docklet_ui_ops *); +void docklet_unload(void); + +/* function in docklet-{x11,win32}.c */ +void docklet_ui_init(void); + +#endif /* _DOCKLET_H_ */ Copied: trunk/gtk/eggtrayicon.c (from rev 17432, trunk/gtk/plugins/docklet/eggtrayicon.c) =================================================================== --- trunk/gtk/eggtrayicon.c (rev 0) +++ trunk/gtk/eggtrayicon.c 2006-10-05 23:24:00 UTC (rev 17433) @@ -0,0 +1,572 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* eggtrayicon.c + * Copyright (C) 2002 Anders Carlsson <and...@gn...> + * + * 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 <config.h> +#include <string.h> + +#include "eggtrayicon.h" + +#include <gdk/gdkx.h> +#include <X11/Xatom.h> + +#define _(x) x +#define N_(x) x + +#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); + +static void egg_tray_icon_update_manager_window (EggTrayIcon *icon, + gboolean dock_if_realized); +static void egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon); + +GType +egg_tray_icon_get_type (void) +{ + static GType our_type = 0; + + if (our_type == 0) + { + our_type = g_type_from_name("EggTrayIcon"); + + 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, + NULL /* value_table */ + }; + + our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0); + } + else if (parent_class == NULL) + { + /* we're reheating the old class from a previous instance - engage ugly hack =( */ + egg_tray_icon_class_init((EggTrayIconClass *)g_type_class_peek(our_type)); + } + } + + 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)); +} + +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; + } +} + +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_return_if_fail(icon->manager_window != None); + +#if GTK_CHECK_VERSION(2,1,0) + xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); +#else + xdisplay = gdk_display; +#endif + + 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; +} + +static void +egg_tray_icon_unrealize (GtkWidget *widget) +{ + EggTrayIcon *icon = EGG_TRAY_ICON (widget); + GdkWindow *root_window; + + if (icon->manager_window != None) + { + GdkWindow *gdkwin; + +#if GTK_CHECK_VERSION(2,1,0) + gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget), + icon->manager_window); +#else + gdkwin = gdk_window_lookup (icon->manager_window); +#endif + + gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon); + } + +#if GTK_CHECK_VERSION(2,1,0) + root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget)); +#else + root_window = gdk_window_lookup (gdk_x11_get_default_root_xwindow ()); +#endif + + 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); +} + +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 GTK_CHECK_VERSION(2,1,0) + 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, + gboolean dock_if_realized) +{ + Display *xdisplay; + + if (icon->manager_window != None) + return; + +#if GTK_CHECK_VERSION(2,1,0) + xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); +#else + xdisplay = gdk_display; +#endif + + 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; + +#if GTK_CHECK_VERSION(2,1,0) + 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); + + 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); + +#if GTK_CHECK_VERSION(2,1,0) + 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_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon); + + icon->manager_window = None; + + egg_tray_icon_update_manager_window (icon, TRUE); +} + +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) +{ + EggTrayIcon *icon = EGG_TRAY_ICON (widget); + gint screen; + 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); + +#if GTK_CHECK_VERSION(2,1,0) + screen = gdk_screen_get_number (gtk_widget_get_screen (widget)); + xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (widget)); +#else + screen = XScreenNumberOfScreen (DefaultScreenOfDisplay (gdk_display)); + xdisplay = gdk_display; +#endif + + /* Now see if there's a manager window around */ + g_snprintf (buffer, sizeof (buffer), + "_NET_SYSTEM_TRAY_S%d", + 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); + +#if GTK_CHECK_VERSION(2,1,0) + root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget)); +#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); +} + +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); +} + +#if GTK_CHECK_VERSION(2,1,0) +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); +} +#endif + +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); + + 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 GTK_CHECK_VERSION(2,1,0) + 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); +} + +GtkOrientation +egg_tray_icon_get_orientation (EggTrayIcon *icon) +{ + g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL); + + return icon->orientation; +} Copied: trunk/gtk/eggtrayicon.h (from rev 17432, trunk/gtk/plugins/docklet/eggtrayicon.h) =================================================================== --- trunk/gtk/eggtrayicon.h (rev 0) +++ trunk/gtk/eggtrayicon.h 2006-10-05 23:24:00 UTC (rev 17433) @@ -0,0 +1,80 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* eggtrayicon.h + * Copyright (C) 2002 Anders Carlsson <and...@gn...> + * + * 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 <gtk/gtkversion.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; + Atom orientation_atom; + Window manager_window; + + GtkOrientation orientation; +}; + +struct _EggTrayIconClass +{ + GtkPlugClass parent_class; +}; + +GType egg_tray_icon_get_type (void); + +#if GTK_CHECK_VERSION(2,1,0) +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); + +GtkOrientation egg_tray_icon_get_orientation (EggTrayIcon *icon); + +G_END_DECLS + +#endif /* __EGG_TRAY_ICON_H__ */ Copied: trunk/gtk/gtkdocklet-win32.c (from rev 17432, trunk/gtk/plugins/docklet/docklet-win32.c) =================================================================== --- trunk/gtk/gtkdocklet-win32.c (rev 0) +++ trunk/gtk/gtkdocklet-win32.c 2006-10-05 23:24:00 UTC (rev 17433) @@ -0,0 +1,263 @@ +/* + * System tray icon (aka docklet) plugin for Gaim + * + * Copyright (C) 2002-3 Robert McQueen <rob...@de...> + * Copyright (C) 2003 Herman Bloggs <her...@ya...> + * Inspired by a similar plugin by: + * John (J5) Palmieri <jo...@ma...> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <windows.h> +#include <gdk/gdkwin32.h> +#include <gdk/gdk.h> + +#include "internal.h" +#include "gtkblist.h" +#include "gtkprefs.h" +#include "debug.h" + +#include "gaim.h" +#include "gtkdialogs.h" + +#include "resource.h" +#include "MinimizeToTray.h" +#include "gtkwin32dep.h" +#include "docklet.h" + +/* + * DEFINES, MACROS & DATA TYPES + */ +#define WM_TRAYMESSAGE WM_USER /* User defined WM Message */ + +/* + * LOCALS + */ +static HWND systray_hwnd=0; +static HICON sysicon_disconn=0; +static HICON sysicon_conn=0; +static HICON sysicon_away=0; +static HICON sysicon_pend=0; +static HICON sysicon_awypend=0; +static HICON sysicon_blank=0; +static NOTIFYICONDATA wgaim_nid; + + +static LRESULT CALLBACK systray_mainmsg_handler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + static UINT taskbarRestartMsg; /* static here means value is kept across multiple calls to this func */ + + switch(msg) { + case WM_CREATE: + gaim_debug(GAIM_DEBUG_INFO, "tray icon", "WM_CREATE\n"); + taskbarRestartMsg = RegisterWindowMessage("TaskbarCreated"); + break; + + case WM_TIMER: + gaim_debug(GAIM_DEBUG_INFO, "tray icon", "WM_TIMER\n"); + break; + + case WM_DESTROY: + gaim_debug(GAIM_DEBUG_INFO, "tray icon", "WM_DESTROY\n"); + break; + + case WM_TRAYMESSAGE: + { + int type = 0; + + /* We'll use Double Click - Single click over on linux */ + if( lparam == WM_LBUTTONDBLCLK ) + type = 1; + else if( lparam == WM_MBUTTONUP ) + type = 2; + else if( lparam == WM_RBUTTONUP ) + type = 3; + else + break; + + docklet_clicked(type); + break; + } + default: + if (msg == taskbarRestartMsg) { + /* explorer crashed and left us hanging... + This will put the systray icon back in it's place, when it restarts */ + Shell_NotifyIcon(NIM_ADD,&wgaim_nid); + } + break; + }/* end switch */ + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +/* Create hidden window to process systray messages */ +static HWND systray_create_hiddenwin() { + WNDCLASSEX wcex; + TCHAR wname[32]; + + strcpy(wname, "GaimWin"); + + wcex.cbSize = sizeof(WNDCLASSEX); + + wcex.style = 0; + wcex.lpfnWndProc = (WNDPROC)systray_mainmsg_handler; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = gtkwgaim_hinstance(); + wcex.hIcon = NULL; + wcex.hCursor = NULL, + wcex.hbrBackground = NULL; + wcex.lpszMenuName = NULL; + wcex.lpszClassName = wname; + wcex.hIconSm = NULL; + + RegisterClassEx(&wcex); + + /* Create the window */ + return (CreateWindow(wname, "", 0, 0, 0, 0, 0, GetDesktopWindow(), NULL, gtkwgaim_hinstance(), 0)); +} + +static void systray_init_icon(HWND hWnd, HICON icon) { + ZeroMemory(&wgaim_nid,sizeof(wgaim_nid)); + wgaim_nid.cbSize=sizeof(NOTIFYICONDATA); + wgaim_nid.hWnd=hWnd; + wgaim_nid.uID=0; + wgaim_nid.uFlags=NIF_ICON | NIF_MESSAGE | NIF_TIP; + wgaim_nid.uCallbackMessage=WM_TRAYMESSAGE; + wgaim_nid.hIcon=icon; + lstrcpy(wgaim_nid.szTip, "Gaim"); + Shell_NotifyIcon(NIM_ADD,&wgaim_nid); + docklet_embedded(); +} + +static void systray_change_icon(HICON icon) { + wgaim_nid.hIcon = icon; + Shell_NotifyIcon(NIM_MODIFY,&wgaim_nid); +} + +static void systray_remove_nid(void) { + Shell_NotifyIcon(NIM_DELETE,&wgaim_nid); +} + +static void wgaim_tray_update_icon(DockletStatus icon) { + switch (icon) { + case DOCKLET_STATUS_OFFLINE: + systray_change_icon(sysicon_disconn); + break; + case DOCKLET_STATUS_CONNECTING: + break; + case DOCKLET_STATUS_ONLINE: + systray_change_icon(sysicon_conn); + break; + case DOCKLET_STATUS_ONLINE_PENDING: + systray_change_icon(sysicon_pend); + break; + case DOCKLET_STATUS_AWAY: + systray_change_icon(sysicon_away); + break; + case DOCKLET_STATUS_AWAY_PENDING: + systray_change_icon(sysicon_awypend); + break; + } +} + +static void wgaim_tray_blank_icon() { + systray_change_icon(sysicon_blank); +} + +static void wgaim_tray_set_tooltip(gchar *tooltip) { + if (tooltip) { + char *locenc = NULL; + locenc = g_locale_from_utf8(tooltip, -1, NULL, NULL, NULL); + lstrcpyn(wgaim_nid.szTip, locenc, sizeof(wgaim_nid.szTip)/sizeof(TCHAR)); + g_free(locenc); + } else { + lstrcpy(wgaim_nid.szTip, "Gaim"); + } + Shell_NotifyIcon(NIM_MODIFY, &wgaim_nid); +} + +void wgaim_tray_minimize(GaimGtkBuddyList *gtkblist) { + MinimizeWndToTray(GDK_WINDOW_HWND(gtkblist->window->window)); +} + +void wgaim_tray_maximize(GaimGtkBuddyList *gtkblist) { + RestoreWndFromTray(GDK_WINDOW_HWND(gtkblist->window->window)); +} + + +static void wgaim_tray_create() { + OSVERSIONINFO osinfo; + /* dummy window to process systray messages */ + systray_hwnd = systray_create_hiddenwin(); + + osinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osinfo); + + /* Load icons, and init systray notify icon + * NOTE: Windows < XP only supports displaying 4-bit images in the Systray, + * 2K and ME will use the highest color depth that the desktop will support, + * but will scale it back to 4-bits for display. + * That is why we use custom 4-bit icons for pre XP Windowses */ + if ((osinfo.dwMajorVersion == 5 && osinfo.dwMinorVersion > 0) || + (osinfo.dwMajorVersion >= 6)) + { + sysicon_disconn = (HICON)LoadImage(gtkwgaim_hinstance(), MAKEINTRESOURCE(GAIM_OFFLINE_TRAY_ICON), IMAGE_ICON, 16, 16, 0); + sysicon_conn = (HICON)LoadImage(gtkwgaim_hinstance(), MAKEINTRESOURCE(GAIM_TRAY_ICON), IMAGE_ICON, 16, 16, 0); + sysicon_away = (HICON)LoadImage(gtkwgaim_hinstance(), MAKEINTRESOURCE(GAIM_AWAY_TRAY_ICON), IMAGE_ICON, 16, 16, 0); + sysicon_pend = (HICON)LoadImage(gtkwgaim_hinstance(), MAKEINTRESOURCE(GAIM_PEND_TRAY_ICON), IMAGE_ICON, 16, 16, 0); + sysicon_awypend = (HICON)LoadImage(gtkwgaim_hinstance(), MAKEINTRESOURCE(GAIM_AWAYPEND_TRAY_ICON), IMAGE_ICON, 16, 16, 0); + } else { + sysicon_disconn = (HICON)LoadImage(gtkwgaim_hinstance(), MAKEINTRESOURCE(GAIM_OFFLINE_TRAY_ICON_4BIT), IMAGE_ICON, 16, 16, 0); + sysicon_conn = (HICON)LoadImage(gtkwgaim_hinstance(), MAKEINTRESOURCE(GAIM_TRAY_ICON_4BIT), IMAGE_ICON, 16, 16, 0); + sysicon_away = (HICON)LoadImage(gtkwgaim_hinstance(), MAKEINTRESOURCE(GAIM_AWAY_TRAY_ICON_4BIT), IMAGE_ICON, 16, 16, 0); + sysicon_pend = (HICON)LoadImage(gtkwgaim_hinstance(), MAKEINTRESOURCE(GAIM_PEND_TRAY_ICON_4BIT), IMAGE_ICON, 16, 16, 0); + sysicon_awypend = (HICON)LoadImage(gtkwgaim_hinstance(), MAKEINTRESOURCE(GAIM_AWAYPEND_TRAY_ICON_4BIT), IMAGE_ICON, 16, 16, 0); + } + sysicon_blank = (HICON)LoadImage(gtkwgaim_hinstance(), MAKEINTRESOURCE(GAIM_BLANK_TRAY_ICON), IMAGE_ICON, 16, 16, 0); + + /* Create icon in systray */ + systray_init_icon(systray_hwnd, sysicon_disconn); + + gaim_signal_connect(gaim_gtk_blist_get_handle(), "gtkblist-hiding", + &handle, GAIM_CALLBACK(wgaim_tray_minimize), NULL); + gaim_signal_connect(gaim_gtk_blist_get_handle(), "gtkblist-unhiding", + &handle, GAIM_CALLBACK(wgaim_tray_maximize), NULL); + + gaim_debug(GAIM_DEBUG_INFO, "tray icon", "created\n"); +} + +static void wgaim_tray_destroy() { + gaim_signals_disconnect_by_handle(&handle); + systray_remove_nid(); + DestroyWindow(systray_hwnd); + docklet_remove(); +} + +static struct docklet_ui_ops wgaim_tray_ops = +{ + wgaim_tray_create, + wgaim_tray_destroy, + wgaim_tray_update_icon, + wgaim_tray_blank_icon, + wgaim_tray_set_tooltip, + NULL +}; + +/* Used by docklet's plugin load func */ +void docklet_ui_init() { + docklet_set_ui_ops(&wgaim_tray_ops); +} Copied: trunk/gtk/gtkdocklet-x11.c (from rev 17432, trunk/gtk/plugins/docklet/docklet-x11.c) =================================================================== --- trunk/gtk/gtkdocklet-x11.c (rev 0) +++ trunk/gtk/gtkdocklet-x11.c 2006-10-05 23:24:00 UTC (rev 17433) @@ -0,0 +1,305 @@ +/* + * System tray icon (aka docklet) plugin for Gaim + * + * Copyright (C) 2002-3 Robert McQueen <rob...@de...> + * Copyright (C) 2003 Herman Bloggs <her...@ya...> + * Inspired by a similar plugin by: + * John (J5) Palmieri <jo...@ma...> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "internal.h" +#include "gtkgaim.h" +#include "debug.h" +#include "gaimstock.h" + +#include "gaim.h" +#include "gtkdialogs.h" + +#include "eggtrayicon.h" +#include "gtkdocklet.h" + +#define EMBED_TIMEOUT 5000 + +/* globals */ +static EggTrayIcon *docklet = NULL; +static GtkWidget *image = NULL; +static GtkTooltips *tooltips = NULL; +static GdkPixbuf *blank_icon = NULL; +static int embed_timeout = 0; + +/* protos */ +static void docklet_x11_create(void); + +static gboolean +docklet_x11_create_cb() +{ + docklet_x11_create(); + + return FALSE; /* for when we're called by the glib idle handler */ +} + +static void +docklet_x11_embedded_cb(GtkWidget *widget, void *data) +{ + gaim_debug(GAIM_DEBUG_INFO, "tray icon", "embedded\n"); + + g_source_remove(embed_timeout); + embed_timeout = 0; + gaim_gtk_docklet_embedded(); +} + +static void +docklet_x11_destroyed_cb(GtkWidget *widget, void *data) +{ + gaim_debug(GAIM_DEBUG_INFO, "tray icon", "destroyed\n"); + + gaim_gtk_docklet_remove(); + + g_object_unref(G_OBJECT(docklet)); + docklet = NULL; + + g_idle_add(docklet_x11_create_cb, NULL); +} + +static void +docklet_x11_clicked_cb(GtkWidget *button, GdkEventButton *event, void *data) +{ + if (event->type != GDK_BUTTON_PRESS) + return; + + gaim_gtk_docklet_clicked(event->button); +} + +static void +docklet_x11_update_icon(DockletStatus icon) +{ + const gchar *icon_name = NULL; + + g_return_if_fail(image != NULL); + + switch (icon) { + case DOCKLET_STATUS_OFFLINE: + icon_name = GAIM_STOCK_ICON_OFFLINE; + break; + case DOCKLET_STATUS_CONNECTING: + icon_name = GAIM_STOCK_ICON_CONNECT; + break; + case DOCKLET_STATUS_ONLINE: + icon_name = GAIM_STOCK_ICON_ONLINE; + break; + case DOCKLET_STATUS_ONLINE_PENDING: + icon_name = GAIM_STOCK_ICON_ONLINE_MSG; + break; + case DOCKLET_STATUS_AWAY: + icon_name = GAIM_STOCK_ICON_AWAY; + break; + case DOCKLET_STATUS_AWAY_PENDING: + icon_name = GAIM_STOCK_ICON_AWAY_MSG; + break; + } + + if(icon_name) + gtk_image_set_from_stock(GTK_IMAGE(image), icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR); + +#if 0 + GdkPixbuf *p; + GdkBitmap *mask = NULL; + + p = gtk_widget_render_icon(GTK_WIDGET(image), icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR, NULL); + + if (p && (gdk_pixbuf_get_colorspace(p) == GDK_COLORSPACE_RGB) && (gdk_pixbuf_get_bits_per_sample(p) == 8) + && (gdk_pixbuf_get_has_alpha(p)) && (gdk_pixbuf_get_n_channels(p) == 4)) { + int len = gdk_pixbuf_get_width(p) * gdk_pixbuf_get_height(p); + guchar *data = gdk_pixbuf_get_pixels(p); + guchar *bitmap = g_malloc((len / 8) + 1); + int i; + + for (i = 0; i < len; i++) + if (data[i*4 + 3] > 55) + bitmap[i/8] |= 1 << i % 8; + else + bitmap[i/8] &= ~(1 << i % 8); + + mask = gdk_bitmap_create_from_data(GDK_DRAWABLE(GTK_WIDGET(image)->window), bitmap, gdk_pixbuf_get_width(p), gdk_pixbuf_get_height(p)); + g_free(bitmap); + } + + if (mask) + gdk_window_shape_combine_mask(image->window, mask, 0, 0); + + g_object_unref(G_OBJECT(p)); +#endif +} + +static void +docklet_x11_blank_icon() +{ + if (!blank_icon) { + gint width, height; + + gtk_icon_size_lookup(GTK_ICON_SIZE_LARGE_TOOLBAR, &width, &height); + blank_icon = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height); + gdk_pixbuf_fill(blank_icon, 0); + } + + gtk_image_set_from_pixbuf(GTK_IMAGE(image), blank_icon); +} + +static void +docklet_x11_set_tooltip(gchar *tooltip) +{ + if (!tooltips) + tooltips = gtk_tooltips_new(); + + /* image->parent is a GtkEventBox */ + if (tooltip) { + gtk_tooltips_enable(tooltips); + gtk_tooltips_set_tip(tooltips, image->parent, tooltip, NULL); + } else { + gtk_tooltips_set_tip(tooltips, image->parent, "", NULL); + gtk_tooltips_disable(tooltips); + } +} + +#if GTK_CHECK_VERSION(2,2,0) +static void +docklet_x11_position_menu(GtkMenu *menu, int *x, int *y, gboolean *push_in, + gpointer user_data) +{ + GtkWidget *widget = GTK_WIDGET(docklet); + GtkRequisition req; + gint menu_xpos, menu_ypos; + + gtk_widget_size_request(GTK_WIDGET(menu), &req); + gdk_window_get_origin(widget->window, &menu_xpos, &menu_ypos); + + menu_xpos += widget->allocation.x; + menu_ypos += widget->allocation.y; + + if (menu_ypos > gdk_screen_get_height(gtk_widget_get_screen(widget)) / 2) + menu_ypos -= req.height; + else + menu_ypos += widget->allocation.height; + + *x = menu_xpos; + *y = menu_ypos; + + *push_in = TRUE; +} +#endif + +static void +docklet_x11_destroy() +{ + g_return_if_fail(docklet != NULL); + + gaim_gtk_docklet_remove(); + + g_signal_handlers_disconnect_by_func(G_OBJECT(docklet), G_CALLBACK(docklet_x11_destroyed_cb), NULL); + gtk_widget_destroy(GTK_WIDGET(docklet)); + + g_object_unref(G_OBJECT(docklet)); + docklet = NULL; + + if (blank_icon) + g_object_unref(G_OBJECT(blank_icon)); + blank_icon = NULL; + + image = NULL; + + gaim_debug(GAIM_DEBUG_INFO, "tray icon", "destroyed\n"); +} + +static gboolean +docklet_x11_embed_timeout_cb() +{ + /* The docklet was not embedded within the timeout. + * Remove it as a visibility manager, but leave the plugin + * loaded so that it can embed automatically if/when a notification + * area becomes available. + */ + gaim_debug_info("tray icon", "failed to embed within timeout\n"); + gaim_gtk_docklet_remove(); + + return FALSE; +} + +static void +docklet_x11_create() +{ + GtkWidget *box; + + if (docklet) { + /* if this is being called when a tray icon exists, it's because + something messed up. try destroying it before we proceed, + although docklet_refcount may be all hosed. hopefully won't happen. */ + gaim_debug(GAIM_DEBUG_WARNING, "tray icon", "trying to create icon but it already exists?\n"); + docklet_x11_destroy(); + } + + docklet = egg_tray_icon_new("Gaim"); + box = gtk_event_box_new(); + image = gtk_image_new(); + + g_signal_connect(G_OBJECT(docklet), "embedded", G_CALLBACK(docklet_x11_embedded_cb), NULL); + g_signal_connect(G_OBJECT(docklet), "destroy", G_CALLBACK(docklet_x11_destroyed_cb), NULL); + g_signal_connect(G_OBJECT(box), "button-press-event", G_CALLBACK(docklet_x11_clicked_cb), NULL); + + gtk_container_add(GTK_CONTAINER(box), image); + gtk_container_add(GTK_CONTAINER(docklet), box); + + if (!gtk_check_version(2,4,0)) + g_object_set(G_OBJECT(box), "visible-window", FALSE, NULL); + + gtk_widget_show_all(GTK_WIDGET(docklet)); + + /* ref the docklet before we bandy it about the place */ + g_object_ref(G_OBJECT(docklet)); + + /* This is a hack to avoid a race condition between the docklet getting + * embedded in the notification area and the gtkblist restoring its + * previous visibility state. If the docklet does not get embedded within + * the timeout, it will be removed as a visibility manager until it does + * get embedded. Ideally, we would only call docklet_embedded() when the + * icon was actually embedded. + */ + gaim_gtk_docklet_embedded(); + embed_timeout = g_timeout_add(EMBED_TIMEOUT, docklet_x11_embed_timeout_cb, NULL); + + gaim_debug(GAIM_DEBUG_INFO, "tray icon", "created\n"); +} + +static struct docklet_ui_ops ui_ops = +{ + docklet_x11_create, + docklet_x11_destroy, + docklet_x11_update_icon, + docklet_x11_blank_icon, + docklet_x11_set_tooltip, +#if GTK_CHECK_VERSION(2,2,0) + docklet_x11_position_menu +#else + NULL +#endif +}; + +void +docklet_ui_init() +{ + gaim_gtk_docklet_set_ui_ops(&ui_ops); +} Copied: trunk/gtk/gtkdocklet.c (from rev 17432, trunk/gtk/plugins/docklet/docklet.c) =================================================================== --- trunk/gtk/gtkdocklet.c (rev 0) +++ trunk/gtk/gtkdocklet.c 2006-10-05 23:24:00 UTC (rev 17433) @@ -0,0 +1,694 @@ +/* + * System tray icon (aka docklet) plugin for Gaim + * + * Copyright (C) 2002-3 Robert McQueen <rob...@de...> + * Copyright (C) 2003 Herman Bloggs <her...@ya...> + * Inspired by a similar plugin by: + * John (J5) Palmieri <jo...@ma...> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ +#include "internal.h" +#include "gtkgaim.h" + +#include "core.h" +#include "conversation.h" +#include "debug.h" +#include "prefs.h" +#include "signals.h" +#include "sound.h" +#include "version.h" + +#include "gtkaccount.h" +#include "gtkblist.h" +#include "gtkconv.h" +#include "gtkft.h" +#include "gtkplugin.h" +#include "gtkprefs.h" +#include "gtksavedstatuses.h" +#include "gtksound.h" +#include "gtkutils.h" +#include "gaimstock.h" +#include "gtkdocklet.h" + +#include "gaim.h" +#include "gtkdialogs.h" + +#ifndef DOCKLET_TOOLTIP_LINE_LIMIT +#define DOCKLET_TOOLTIP_LINE_LIMIT 5 +#endif + +/* globals */ +static struct docklet_ui_ops *ui_ops = NULL; +static DockletStatus status = DOCKLET_STATUS_OFFLINE; +static gboolean enable_join_chat = FALSE; +static guint docklet_blinking_timer = 0; +static gboolean visibility_manager = FALSE; + +/************************************************************************** + * docklet status and utility functions + **************************************************************************/ +static gboolean +docklet_blink_icon() +{ + static gboolean blinked = FALSE; + gboolean ret = FALSE; /* by default, don't keep blinking */ + + blinked = !blinked; + + switch (status) { + case DOCKLET_STATUS_ONLINE_PENDING: + case DOCKLET_STATUS_AWAY_PENDING: + if (blinked) { + if (ui_ops && ui_ops->blank_icon) + ui_ops->blank_icon(); + } else { + if (ui_ops && ui_ops->update_icon) + ui_ops->update_icon(status); + } + ret = TRUE; /* keep blinking */ + break; + default: + docklet_blinking_timer = 0; + blinked = FALSE; + break; + } + + return ret; +} + +static GList * +get_pending_list(guint max) +{ + const char *im = gaim_prefs_get_string("/plugins/gtk/docklet/blink_im"); + const char *chat = gaim_prefs_get_string("/plugins/gtk/docklet/blink_chat"); + GList *l_im = NULL; + GList *l_chat = NULL; + + if (im != NULL && strcmp(im, "always") == 0) { + l_im = gaim_gtk_conversations_find_unseen_list(GAIM_CONV_TYPE_IM, + GAIM_UNSEEN_TEXT, + FALSE, max); + } else if (im != NULL && strcmp(im, "hidden") == 0) { + l_im = gaim_gtk_conversations_find_unseen_list(GAIM_CONV_TYPE_IM, + GAIM_UNSEEN_TEXT, + TRUE, max); + } + + if (chat != NULL && strcmp(chat, "always") == 0) { + l_chat = gaim_gtk_conversations_find_unseen_list(GAIM_CONV_TYPE_CHAT, + GAIM_UNSEEN_TEXT, + FALSE, max); + } else if (chat != NULL && strcmp(chat, "nick") == 0) { + l_chat = gaim_gtk_conversations_find_unseen_list(GAIM_CONV_TYPE_CHAT, + GAIM_UNSEEN_NICK, + FALSE, max); + } + + if (l_im != NULL && l_chat != NULL) + return g_list_concat(l_im, l_chat); + else if (l_im != NULL) + return l_im; + else + return l_chat; +} + +static gboolean +docklet_update_status() +{ + GList *convs; + GList *l; + int count; + DockletStatus newstatus = DOCKLET_STATUS_OFFLINE; + gboolean pending = FALSE; + + /* determine if any ims have unseen messages */ + convs = get_pending_list(DOCKLET_TOOLTIP_LINE_LIMIT); + + if (convs != NULL) { + pending = TRUE; + + /* set tooltip if messages are pending */ + if (ui_ops->set_tooltip) { + GString *tooltip_text = g_string_new(""); + for (l = convs, count = 0 ; l != NULL ; l = l->next, count++) { + if (GAIM_IS_GTK_CONVERSATION(l->data)) { + GaimGtkConversation *gtkconv = GAIM_GTK_CONVERSATION((GaimConversation *)l->data); + if (count == DOCKLET_TOOLTIP_LINE_LIMIT - 1) + g_string_append(tooltip_text, _("Right-click for more unread messages...\n")); + else + g_string_append_printf(tooltip_text, + ngettext("%d unread message from %s\n", "%d unread messages from %s\n", gtkconv->unseen_count), + gtkconv->unseen_count, + gtk_label_get_text(GTK_LABEL(gtkconv->tab_label))); + } + } + + /* get rid of the last newline */ + if (tooltip_text->len > 0) + tooltip_text = g_string_truncate(tooltip_text, tooltip_text->len - 1); + + ui_ops->set_tooltip(tooltip_text->str); + + g_string_free(tooltip_text, TRUE); + } + + g_list_free(convs); + + } else if (ui_ops->set_tooltip) { + ui_ops->set_tooltip(NULL); + } + + /* iterate through all accounts and determine which + * status to show in the tray icon based on the following + * ranks (highest encountered rank will be used): + * + * 1) OFFLINE + * 2) ONLINE + * 3) ONLINE_PENDING + * 4) AWAY + * 5) AWAY_PENDING + * 6) CONNECTING + */ + for(l = gaim_accounts_get_all(); l != NULL; l = l->next) { + DockletStatus tmpstatus = DOCKLET_STATUS_OFFLINE; + + GaimAccount *account = (GaimAccount*)l->data; + GaimStatus *account_status; + + if (!gaim_account_get_enabled(account, GAIM_GTK_UI)) + continue; + + if (gaim_account_is_disconnected(account)) + continue; + + account_status = gaim_account_get_active_status(account); + + if (gaim_account_is_connecting(account)) { + tmpstatus = DOCKLET_STATUS_CONNECTING; + } else if (gaim_status_is_online(account_status)) { + if (!gaim_status_is_available(account_status)) { + if (pending) + tmpstatus = DOCKLET_STATUS_AWAY_PENDING; + else + tmpstatus = DOCKLET_STATUS_AWAY; + } + else { + if (pending) + tmpstatus = DOCKLET_STATUS_ONLINE_PENDING; + else + tmpstatus = DOCKLET_STATUS_ONLINE; + } + } + + if (tmpstatus > newstatus) + newstatus = tmpstatus; + } + + /* update the icon if we changed status */ + if (status != newstatus) { + status = newstatus; + + if (ui_ops && ui_ops->update_icon) + ui_ops->update_icon(status); + + /* and schedule the blinker function if messages are pending */ + if ((status == DOCKLET_STATUS_ONLINE_PENDING + || status == DOCKLET_STATUS_AWAY_PENDING) + && docklet_blinking_timer == 0) { + docklet_blinking_timer = g_timeout_add(500, docklet_blink_icon, NULL); + } + } + + return FALSE; /* for when we're called by the glib idle handler */ +} + +static gboolean +online_account_supports_chat() +{ + GList *c = NULL; + c = gaim_connections_get_all(); + + while(c != NULL) { + GaimConnection *gc = c->data; + GaimPluginProtocolInfo *prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + if (prpl_info != NULL && prpl_info->chat_info != NULL) + return TRUE; + c = c->next; + } + + return FALSE; +} + +/************************************************************************** + * callbacks and signal handlers + **************************************************************************/ +static void +gaim_quit_cb() +{ + /* TODO: confirm quit while pending */ +} + +static void +docklet_update_status_cb(void *data) +{ + docklet_update_status(); +} + +static void +docklet_prefs_cb(const char *name, GaimPrefType type, + gconstpointer val, gpointer data) +{ + docklet_update_status(); +} + +static void +docklet_conv_updated_cb(GaimConversation *conv, GaimConvUpdateType type) +{ + if (type == GAIM_CONV_UPDATE_UNSEEN) + docklet_update_status(); +} + +static void +docklet_signed_on_cb(GaimConnection *gc) +{ + if (!enable_join_chat) { + if (GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL) + enable_join_chat = TRUE; + } + docklet_update_status(); +} + +static void +docklet_signed_off_cb(GaimConnection *gc) +{ + if (enable_join_chat) { + if (GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL) + enable_join_chat = online_account_supports_chat(); + } + docklet_update_status(); +} + +/************************************************************************** + * docklet pop-up menu + **************************************************************************/ +static void +docklet_toggle_mute(GtkWidget *toggle, void *data) +{ + gaim_prefs_set_bool("/gaim/gtk/sound/mute", GTK_CHECK_MENU_ITEM(toggle)->active); +} + +static void +docklet_toggle_blist(GtkWidget *toggle, void *data) +{ + gaim_blist_set_visible(GTK_CHECK_MENU_ITEM(toggle)->active); +} + +#ifdef _WIN32 +/* This is a workaround for a bug in windows GTK+. Clicking outside of the + menu does not get rid of it, so instead we get rid of it as soon as the + pointer leaves the menu. */ +static gboolean +hide_docklet_menu(gpointer data) +{ + if (data != NULL) { + gtk_menu_popdown(GTK_MENU(data)); + } + return FALSE; +} + +static gboolean +docklet_menu_leave_enter(GtkWidget *menu, GdkEventCrossing *event, void *data) +{ + static guint hide_docklet_timer = 0; + if (event->type == GDK_LEAVE_NOTIFY && event->detail == GDK_NOTIFY_ANCESTOR) { + gaim_debug(GAIM_DEBUG_INFO, "docklet", "menu leave-notify-event\n"); + /* Add some slop so that the menu doesn't annoyingly disappear when mousing around */ + if (hide_docklet_timer == 0) { + hide_docklet_timer = gaim_timeout_add(500, + hide_docklet_menu, menu); + } + } else if (event->type == GDK_ENTER_NOTIFY && event->detail == GDK_NOTIFY_ANCESTOR) { + gaim_debug(GAIM_DEBUG_INFO, "docklet", "menu enter-notify-event\n"); + if (hide_docklet_timer != 0) { + /* Cancel the hiding if we reenter */ + + gaim_timeout_remove(hide_docklet_timer); + hide_docklet_timer = 0; + } + } + return FALSE; +} +#endif + +static void +show_custom_status_editor_cb(GtkMenuItem *menuitem, gpointer user_data) +{ + GaimSavedStatus *saved_status; + saved_status = gaim_savedstatus_get_current(); + gaim_gtk_status_editor_show(FALSE, + gaim_savedstatus_is_transient(saved_status) ? saved_status : NULL); +} + +static void +activate_status_primitive_cb(GtkMenuItem *menuitem, gpointer user_data) +{ + GaimStatusPrimitive primitive; + GaimSavedStatus *saved_status; + + primitive = GPOINTER_TO_INT(user_data); + + /* Try to lookup an already existing transient saved status */ + saved_status = gaim_savedstatus_find_transient_by_type_and_message(primitive, NULL); + + /* Create a new transient saved status if we weren't able to find one */ + if (saved_status == NULL) + saved_status = gaim_savedstatus_new(NULL, primitive); + + /* Set the status for each account */ + gaim_savedstatus_activate(saved_status); +} + +static void +activate_saved_status_cb(GtkMenuItem *menuitem, gpointer user_data) +{ + time_t creation_time; + GaimSavedStatus *saved_status; + + creation_time = GPOINTER_TO_INT(user_data); + saved_status = gaim_savedstatus_find_by_creation_time(creation_time); + if (saved_status != NULL) + gaim_savedstatus_activate(saved_status); +} + +static GtkWidget * +new_menu_item_with_gaim_icon(GtkWidget *menu, const char *str, GaimStatusPrimitive primitive, GtkSignalFunc sf, gpointer data, guint accel_key, guint accel_mods, char *mod) +{ + GtkWidget *menuitem; + GdkPixbuf *pixbuf; + GtkWidget *image; + + menuitem = gtk_image_menu_item_new_with_mnemonic(str); + + if (menu) + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + if (sf) + g_signal_connect(G_OBJECT(menuitem), "activate", sf, data); + + pixbuf = gaim_gtk_create... [truncated message content] |