| 1 /* |
|
| 2 * System tray icon (aka docklet) plugin for Purple |
|
| 3 * |
|
| 4 * Copyright (C) 2002-3 Robert McQueen <robot101@debian.org> |
|
| 5 * Copyright (C) 2003 Herman Bloggs <hermanator12002@yahoo.com> |
|
| 6 * Inspired by a similar plugin by: |
|
| 7 * John (J5) Palmieri <johnp@martianrock.com> |
|
| 8 * |
|
| 9 * This program is free software; you can redistribute it and/or |
|
| 10 * modify it under the terms of the GNU General Public License as |
|
| 11 * published by the Free Software Foundation; either version 2 of the |
|
| 12 * License, or (at your option) any later version. |
|
| 13 * |
|
| 14 * This program is distributed in the hope that it will be useful, but |
|
| 15 * WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
| 17 * General Public License for more details. |
|
| 18 * |
|
| 19 * You should have received a copy of the GNU General Public License |
|
| 20 * along with this program; if not, write to the Free Software |
|
| 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
| 22 * 02111-1301, USA. |
|
| 23 */ |
|
| 24 |
|
| 25 #include "internal.h" |
|
| 26 #include "pidgin.h" |
|
| 27 #include "debug.h" |
|
| 28 #include "prefs.h" |
|
| 29 #include "pidginstock.h" |
|
| 30 |
|
| 31 #include "gtkdialogs.h" |
|
| 32 |
|
| 33 #include "eggtrayicon.h" |
|
| 34 #include "gtkdocklet.h" |
|
| 35 #include <gdk/gdkkeysyms.h> |
|
| 36 |
|
| 37 #if !GTK_CHECK_VERSION(2,10,0) |
|
| 38 |
|
| 39 #define SHORT_EMBED_TIMEOUT 5000 |
|
| 40 #define LONG_EMBED_TIMEOUT 15000 |
|
| 41 |
|
| 42 /* globals */ |
|
| 43 static EggTrayIcon *docklet = NULL; |
|
| 44 static GtkWidget *image = NULL; |
|
| 45 static GtkTooltips *tooltips = NULL; |
|
| 46 static GdkPixbuf *blank_icon = NULL; |
|
| 47 static int embed_timeout = 0; |
|
| 48 static int docklet_height = 0; |
|
| 49 |
|
| 50 /* protos */ |
|
| 51 static void docklet_x11_create(gboolean); |
|
| 52 |
|
| 53 static gboolean |
|
| 54 docklet_x11_recreate_cb(gpointer data) |
|
| 55 { |
|
| 56 docklet_x11_create(TRUE); |
|
| 57 |
|
| 58 return FALSE; /* for when we're called by the glib idle handler */ |
|
| 59 } |
|
| 60 |
|
| 61 static void |
|
| 62 docklet_x11_embedded_cb(GtkWidget *widget, void *data) |
|
| 63 { |
|
| 64 purple_debug(PURPLE_DEBUG_INFO, "docklet", "X11 embedded\n"); |
|
| 65 |
|
| 66 g_source_remove(embed_timeout); |
|
| 67 embed_timeout = 0; |
|
| 68 pidgin_docklet_embedded(); |
|
| 69 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/docklet/x11/embedded", FALSE); |
|
| 70 } |
|
| 71 |
|
| 72 static void |
|
| 73 docklet_x11_destroyed_cb(GtkWidget *widget, void *data) |
|
| 74 { |
|
| 75 purple_debug(PURPLE_DEBUG_INFO, "docklet", "X11 destroyed\n"); |
|
| 76 |
|
| 77 pidgin_docklet_remove(); |
|
| 78 |
|
| 79 g_object_unref(G_OBJECT(docklet)); |
|
| 80 docklet = NULL; |
|
| 81 |
|
| 82 g_idle_add(docklet_x11_recreate_cb, NULL); |
|
| 83 } |
|
| 84 |
|
| 85 static gboolean |
|
| 86 docklet_x11_clicked_cb(GtkWidget *button, GdkEventButton *event, void *data) |
|
| 87 { |
|
| 88 if (event->type != GDK_BUTTON_PRESS) |
|
| 89 return FALSE; |
|
| 90 |
|
| 91 pidgin_docklet_clicked(event->button); |
|
| 92 return TRUE; |
|
| 93 } |
|
| 94 |
|
| 95 static gboolean |
|
| 96 docklet_x11_pressed_cb(GtkWidget *button, GdkEventKey *event) |
|
| 97 { |
|
| 98 guint state, keyval; |
|
| 99 |
|
| 100 state = event->state & gtk_accelerator_get_default_mod_mask(); |
|
| 101 keyval = event->keyval; |
|
| 102 if (state == 0 && |
|
| 103 (keyval == GDK_Return || |
|
| 104 keyval == GDK_KP_Enter || |
|
| 105 keyval == GDK_ISO_Enter || |
|
| 106 keyval == GDK_space || |
|
| 107 keyval == GDK_KP_Space)) |
|
| 108 { |
|
| 109 pidgin_docklet_clicked(1); |
|
| 110 return TRUE; |
|
| 111 } |
|
| 112 |
|
| 113 return FALSE; |
|
| 114 } |
|
| 115 |
|
| 116 static void |
|
| 117 docklet_x11_popup_cb(GtkWidget *button) |
|
| 118 { |
|
| 119 pidgin_docklet_clicked(3); |
|
| 120 } |
|
| 121 |
|
| 122 static void |
|
| 123 docklet_x11_update_icon(PurpleStatusPrimitive status, gboolean connecting, gboolean pending) |
|
| 124 { |
|
| 125 const gchar *icon_name = NULL; |
|
| 126 |
|
| 127 g_return_if_fail(image != NULL); |
|
| 128 |
|
| 129 switch (status) { |
|
| 130 case PURPLE_STATUS_OFFLINE: |
|
| 131 icon_name = PIDGIN_STOCK_TRAY_OFFLINE; |
|
| 132 break; |
|
| 133 case PURPLE_STATUS_AWAY: |
|
| 134 icon_name = PIDGIN_STOCK_TRAY_AWAY; |
|
| 135 break; |
|
| 136 case PURPLE_STATUS_UNAVAILABLE: |
|
| 137 icon_name = PIDGIN_STOCK_TRAY_BUSY; |
|
| 138 break; |
|
| 139 case PURPLE_STATUS_EXTENDED_AWAY: |
|
| 140 icon_name = PIDGIN_STOCK_TRAY_XA; |
|
| 141 break; |
|
| 142 case PURPLE_STATUS_INVISIBLE: |
|
| 143 icon_name = PIDGIN_STOCK_TRAY_INVISIBLE; |
|
| 144 break; |
|
| 145 default: |
|
| 146 icon_name = PIDGIN_STOCK_TRAY_AVAILABLE; |
|
| 147 break; |
|
| 148 } |
|
| 149 |
|
| 150 if (pending) |
|
| 151 icon_name = PIDGIN_STOCK_TRAY_PENDING; |
|
| 152 if (connecting) |
|
| 153 icon_name = PIDGIN_STOCK_TRAY_CONNECT; |
|
| 154 |
|
| 155 if(icon_name) { |
|
| 156 int icon_size; |
|
| 157 if (docklet_height < 22) |
|
| 158 icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL); |
|
| 159 else if (docklet_height < 32) |
|
| 160 icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL); |
|
| 161 else if (docklet_height < 48) |
|
| 162 icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM); |
|
| 163 else |
|
| 164 icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_LARGE); |
|
| 165 |
|
| 166 gtk_image_set_from_stock(GTK_IMAGE(image), icon_name, icon_size); |
|
| 167 } |
|
| 168 } |
|
| 169 |
|
| 170 static void |
|
| 171 docklet_x11_resize_icon(GtkWidget *widget) |
|
| 172 { |
|
| 173 if (docklet_height == MIN(widget->allocation.height, widget->allocation.width)) |
|
| 174 return; |
|
| 175 docklet_height = MIN(widget->allocation.height, widget->allocation.width); |
|
| 176 pidgin_docklet_update_icon(); |
|
| 177 } |
|
| 178 |
|
| 179 static void |
|
| 180 docklet_x11_blank_icon(void) |
|
| 181 { |
|
| 182 if (!blank_icon) { |
|
| 183 GtkIconSize size = GTK_ICON_SIZE_LARGE_TOOLBAR; |
|
| 184 gint width, height; |
|
| 185 g_object_get(G_OBJECT(image), "icon-size", &size, NULL); |
|
| 186 gtk_icon_size_lookup(size, &width, &height); |
|
| 187 blank_icon = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height); |
|
| 188 gdk_pixbuf_fill(blank_icon, 0); |
|
| 189 } |
|
| 190 |
|
| 191 gtk_image_set_from_pixbuf(GTK_IMAGE(image), blank_icon); |
|
| 192 } |
|
| 193 |
|
| 194 static void |
|
| 195 docklet_x11_set_tooltip(gchar *tooltip) |
|
| 196 { |
|
| 197 if (!tooltips) |
|
| 198 tooltips = gtk_tooltips_new(); |
|
| 199 |
|
| 200 /* image->parent is a GtkEventBox */ |
|
| 201 if (tooltip) { |
|
| 202 gtk_tooltips_enable(tooltips); |
|
| 203 gtk_tooltips_set_tip(tooltips, image->parent, tooltip, NULL); |
|
| 204 } else { |
|
| 205 gtk_tooltips_set_tip(tooltips, image->parent, "", NULL); |
|
| 206 gtk_tooltips_disable(tooltips); |
|
| 207 } |
|
| 208 } |
|
| 209 |
|
| 210 static void |
|
| 211 docklet_x11_position_menu(GtkMenu *menu, int *x, int *y, gboolean *push_in, |
|
| 212 gpointer user_data) |
|
| 213 { |
|
| 214 GtkWidget *widget = GTK_WIDGET(docklet); |
|
| 215 GtkRequisition req; |
|
| 216 gint menu_xpos, menu_ypos; |
|
| 217 |
|
| 218 gtk_widget_size_request(GTK_WIDGET(menu), &req); |
|
| 219 gdk_window_get_origin(widget->window, &menu_xpos, &menu_ypos); |
|
| 220 |
|
| 221 menu_xpos += widget->allocation.x; |
|
| 222 menu_ypos += widget->allocation.y; |
|
| 223 |
|
| 224 if (menu_ypos > gdk_screen_get_height(gtk_widget_get_screen(widget)) / 2) |
|
| 225 menu_ypos -= req.height; |
|
| 226 else |
|
| 227 menu_ypos += widget->allocation.height; |
|
| 228 |
|
| 229 *x = menu_xpos; |
|
| 230 *y = menu_ypos; |
|
| 231 |
|
| 232 *push_in = TRUE; |
|
| 233 } |
|
| 234 |
|
| 235 static void |
|
| 236 docklet_x11_destroy(void) |
|
| 237 { |
|
| 238 g_return_if_fail(docklet != NULL); |
|
| 239 |
|
| 240 if (embed_timeout) |
|
| 241 g_source_remove(embed_timeout); |
|
| 242 |
|
| 243 pidgin_docklet_remove(); |
|
| 244 |
|
| 245 g_signal_handlers_disconnect_by_func(G_OBJECT(docklet), G_CALLBACK(docklet_x11_destroyed_cb), NULL); |
|
| 246 gtk_widget_destroy(GTK_WIDGET(docklet)); |
|
| 247 |
|
| 248 g_object_unref(G_OBJECT(docklet)); |
|
| 249 docklet = NULL; |
|
| 250 |
|
| 251 if (blank_icon) |
|
| 252 g_object_unref(G_OBJECT(blank_icon)); |
|
| 253 blank_icon = NULL; |
|
| 254 |
|
| 255 image = NULL; |
|
| 256 |
|
| 257 purple_debug(PURPLE_DEBUG_INFO, "docklet", "X11 destroyed\n"); |
|
| 258 } |
|
| 259 |
|
| 260 static gboolean |
|
| 261 docklet_x11_embed_timeout_cb(gpointer data) |
|
| 262 { |
|
| 263 /* The docklet was not embedded within the timeout. |
|
| 264 * Remove it as a visibility manager, but leave the plugin |
|
| 265 * loaded so that it can embed automatically if/when a notification |
|
| 266 * area becomes available. |
|
| 267 */ |
|
| 268 purple_debug_info("docklet", "X11 failed to embed within timeout\n"); |
|
| 269 pidgin_docklet_remove(); |
|
| 270 |
|
| 271 return FALSE; |
|
| 272 } |
|
| 273 |
|
| 274 static void |
|
| 275 docklet_x11_create(gboolean recreate) |
|
| 276 { |
|
| 277 GtkWidget *box; |
|
| 278 |
|
| 279 if (docklet) { |
|
| 280 /* if this is being called when a tray icon exists, it's because |
|
| 281 something messed up. try destroying it before we proceed, |
|
| 282 although docklet_refcount may be all hosed. hopefully won't happen. */ |
|
| 283 purple_debug(PURPLE_DEBUG_WARNING, "docklet", "trying to create icon but it already exists?\n"); |
|
| 284 docklet_x11_destroy(); |
|
| 285 } |
|
| 286 |
|
| 287 docklet = egg_tray_icon_new(PIDGIN_NAME); |
|
| 288 box = gtk_event_box_new(); |
|
| 289 image = gtk_image_new(); |
|
| 290 GTK_WIDGET_SET_FLAGS (image, GTK_CAN_FOCUS); |
|
| 291 |
|
| 292 g_signal_connect(G_OBJECT(docklet), "embedded", G_CALLBACK(docklet_x11_embedded_cb), NULL); |
|
| 293 g_signal_connect(G_OBJECT(docklet), "destroy", G_CALLBACK(docklet_x11_destroyed_cb), NULL); |
|
| 294 g_signal_connect(G_OBJECT(docklet), "size-allocate", G_CALLBACK(docklet_x11_resize_icon), NULL); |
|
| 295 g_signal_connect(G_OBJECT(box), "button-press-event", G_CALLBACK(docklet_x11_clicked_cb), NULL); |
|
| 296 g_signal_connect(G_OBJECT(box), "key-press-event", G_CALLBACK(docklet_x11_pressed_cb), NULL); |
|
| 297 g_signal_connect(G_OBJECT(box), "popup-menu", G_CALLBACK(docklet_x11_popup_cb), NULL); |
|
| 298 gtk_container_add(GTK_CONTAINER(box), image); |
|
| 299 gtk_container_add(GTK_CONTAINER(docklet), box); |
|
| 300 |
|
| 301 if (!gtk_check_version(2,4,0)) |
|
| 302 g_object_set(G_OBJECT(box), "visible-window", FALSE, NULL); |
|
| 303 |
|
| 304 gtk_widget_show_all(GTK_WIDGET(docklet)); |
|
| 305 |
|
| 306 /* ref the docklet before we bandy it about the place */ |
|
| 307 g_object_ref(G_OBJECT(docklet)); |
|
| 308 |
|
| 309 /* This is a hack to avoid a race condition between the docklet getting |
|
| 310 * embedded in the notification area and the gtkblist restoring its |
|
| 311 * previous visibility state. If the docklet does not get embedded within |
|
| 312 * the timeout, it will be removed as a visibility manager until it does |
|
| 313 * get embedded. Ideally, we would only call docklet_embedded() when the |
|
| 314 * icon was actually embedded. This only happens when the docklet is first |
|
| 315 * created, not when being recreated. |
|
| 316 * |
|
| 317 * The x11 docklet tracks whether it successfully embedded in a pref and |
|
| 318 * allows for a longer timeout period if it successfully embedded the last |
|
| 319 * time it was run. This should hopefully solve problems with the buddy |
|
| 320 * list not properly starting hidden when Pidgin is started on login. |
|
| 321 */ |
|
| 322 if(!recreate) { |
|
| 323 pidgin_docklet_embedded(); |
|
| 324 if(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/docklet/x11/embedded")) { |
|
| 325 embed_timeout = g_timeout_add(LONG_EMBED_TIMEOUT, docklet_x11_embed_timeout_cb, NULL); |
|
| 326 } else { |
|
| 327 embed_timeout = g_timeout_add(SHORT_EMBED_TIMEOUT, docklet_x11_embed_timeout_cb, NULL); |
|
| 328 } |
|
| 329 } |
|
| 330 |
|
| 331 purple_debug(PURPLE_DEBUG_INFO, "docklet", "X11 created\n"); |
|
| 332 } |
|
| 333 |
|
| 334 static void |
|
| 335 docklet_x11_create_ui_op(void) |
|
| 336 { |
|
| 337 docklet_x11_create(FALSE); |
|
| 338 } |
|
| 339 |
|
| 340 static struct docklet_ui_ops ui_ops = |
|
| 341 { |
|
| 342 docklet_x11_create_ui_op, |
|
| 343 docklet_x11_destroy, |
|
| 344 docklet_x11_update_icon, |
|
| 345 docklet_x11_blank_icon, |
|
| 346 docklet_x11_set_tooltip, |
|
| 347 docklet_x11_position_menu |
|
| 348 }; |
|
| 349 |
|
| 350 void |
|
| 351 docklet_ui_init() |
|
| 352 { |
|
| 353 pidgin_docklet_set_ui_ops(&ui_ops); |
|
| 354 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/docklet/x11"); |
|
| 355 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/docklet/x11/embedded", FALSE); |
|
| 356 } |
|
| 357 |
|
| 358 #endif /* !GTK_CHECK_VERSION(2,10,0) */ |
|
| 359 |
|