| 1 /* |
|
| 2 * System tray icon (aka docklet) plugin for Purple |
|
| 3 * |
|
| 4 * Copyright (C) 2007 Anders Hasselqvist |
|
| 5 * |
|
| 6 * This program is free software; you can redistribute it and/or |
|
| 7 * modify it under the terms of the GNU General Public License as |
|
| 8 * published by the Free Software Foundation; either version 2 of the |
|
| 9 * License, or (at your option) any later version. |
|
| 10 * |
|
| 11 * This program is distributed in the hope that it will be useful, but |
|
| 12 * WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
| 14 * General Public License for more details. |
|
| 15 * |
|
| 16 * You should have received a copy of the GNU General Public License |
|
| 17 * along with this program; if not, write to the Free Software |
|
| 18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA |
|
| 19 * 02111-1307, USA. |
|
| 20 */ |
|
| 21 |
|
| 22 #include "internal.h" |
|
| 23 #include "pidgin.h" |
|
| 24 #include "debug.h" |
|
| 25 #include "prefs.h" |
|
| 26 #include "pidginstock.h" |
|
| 27 #include "gtkdocklet.h" |
|
| 28 |
|
| 29 #define SHORT_EMBED_TIMEOUT 5 |
|
| 30 #define LONG_EMBED_TIMEOUT 15 |
|
| 31 |
|
| 32 /* globals */ |
|
| 33 static GtkStatusIcon *docklet = NULL; |
|
| 34 static guint embed_timeout = 0; |
|
| 35 |
|
| 36 /* protos */ |
|
| 37 static void docklet_gtk_status_create(gboolean); |
|
| 38 |
|
| 39 static gboolean |
|
| 40 docklet_gtk_recreate_cb(gpointer data) |
|
| 41 { |
|
| 42 docklet_gtk_status_create(TRUE); |
|
| 43 |
|
| 44 return FALSE; |
|
| 45 } |
|
| 46 |
|
| 47 static gboolean |
|
| 48 docklet_gtk_embed_timeout_cb(gpointer data) |
|
| 49 { |
|
| 50 #if !GTK_CHECK_VERSION(2,12,0) |
|
| 51 if (gtk_status_icon_is_embedded(docklet)) { |
|
| 52 /* Older GTK+ (<2.12) don't implement the embedded signal, but the |
|
| 53 information is still accessable through the above function. */ |
|
| 54 purple_debug_info("docklet", "embedded\n"); |
|
| 55 |
|
| 56 pidgin_docklet_embedded(); |
|
| 57 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/docklet/gtk/embedded", TRUE); |
|
| 58 } |
|
| 59 else |
|
| 60 #endif |
|
| 61 { |
|
| 62 /* The docklet was not embedded within the timeout. |
|
| 63 * Remove it as a visibility manager, but leave the plugin |
|
| 64 * loaded so that it can embed automatically if/when a notification |
|
| 65 * area becomes available. |
|
| 66 */ |
|
| 67 purple_debug_info("docklet", "failed to embed within timeout\n"); |
|
| 68 pidgin_docklet_remove(); |
|
| 69 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/docklet/gtk/embedded", FALSE); |
|
| 70 } |
|
| 71 |
|
| 72 #if GTK_CHECK_VERSION(2,12,0) |
|
| 73 embed_timeout = 0; |
|
| 74 return FALSE; |
|
| 75 #else |
|
| 76 return TRUE; |
|
| 77 #endif |
|
| 78 } |
|
| 79 |
|
| 80 #if GTK_CHECK_VERSION(2,12,0) |
|
| 81 static gboolean |
|
| 82 docklet_gtk_embedded_cb(GtkWidget *widget, gpointer data) |
|
| 83 { |
|
| 84 if (embed_timeout) { |
|
| 85 purple_timeout_remove(embed_timeout); |
|
| 86 embed_timeout = 0; |
|
| 87 } |
|
| 88 |
|
| 89 if (gtk_status_icon_is_embedded(docklet)) { |
|
| 90 purple_debug_info("docklet", "embedded\n"); |
|
| 91 |
|
| 92 pidgin_docklet_embedded(); |
|
| 93 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/docklet/gtk/embedded", TRUE); |
|
| 94 } else { |
|
| 95 purple_debug_info("docklet", "detached\n"); |
|
| 96 |
|
| 97 pidgin_docklet_remove(); |
|
| 98 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/docklet/gtk/embedded", FALSE); |
|
| 99 } |
|
| 100 |
|
| 101 return TRUE; |
|
| 102 } |
|
| 103 #endif |
|
| 104 |
|
| 105 static void |
|
| 106 docklet_gtk_destroyed_cb(GtkWidget *widget, gpointer data) |
|
| 107 { |
|
| 108 purple_debug_info("docklet", "destroyed\n"); |
|
| 109 |
|
| 110 pidgin_docklet_remove(); |
|
| 111 |
|
| 112 g_object_unref(G_OBJECT(docklet)); |
|
| 113 docklet = NULL; |
|
| 114 |
|
| 115 g_idle_add(docklet_gtk_recreate_cb, NULL); |
|
| 116 } |
|
| 117 |
|
| 118 static void |
|
| 119 docklet_gtk_status_activated_cb(GtkStatusIcon *status_icon, gpointer user_data) |
|
| 120 { |
|
| 121 pidgin_docklet_clicked(1); |
|
| 122 } |
|
| 123 |
|
| 124 static void |
|
| 125 docklet_gtk_status_clicked_cb(GtkStatusIcon *status_icon, guint button, guint activate_time, gpointer user_data) |
|
| 126 { |
|
| 127 purple_debug_info("docklet", "The button is %u\n", button); |
|
| 128 #ifdef GDK_WINDOWING_QUARTZ |
|
| 129 /* You can only click left mouse button on MacOSX native GTK. Let that be the menu */ |
|
| 130 pidgin_docklet_clicked(3); |
|
| 131 #else |
|
| 132 pidgin_docklet_clicked(button); |
|
| 133 #endif |
|
| 134 } |
|
| 135 |
|
| 136 static void |
|
| 137 docklet_gtk_status_update_icon(PurpleStatusPrimitive status, gboolean connecting, gboolean pending) |
|
| 138 { |
|
| 139 const gchar *icon_name = NULL; |
|
| 140 |
|
| 141 switch (status) { |
|
| 142 case PURPLE_STATUS_OFFLINE: |
|
| 143 icon_name = PIDGIN_STOCK_TRAY_OFFLINE; |
|
| 144 break; |
|
| 145 case PURPLE_STATUS_AWAY: |
|
| 146 icon_name = PIDGIN_STOCK_TRAY_AWAY; |
|
| 147 break; |
|
| 148 case PURPLE_STATUS_UNAVAILABLE: |
|
| 149 icon_name = PIDGIN_STOCK_TRAY_BUSY; |
|
| 150 break; |
|
| 151 case PURPLE_STATUS_EXTENDED_AWAY: |
|
| 152 icon_name = PIDGIN_STOCK_TRAY_XA; |
|
| 153 break; |
|
| 154 case PURPLE_STATUS_INVISIBLE: |
|
| 155 icon_name = PIDGIN_STOCK_TRAY_INVISIBLE; |
|
| 156 break; |
|
| 157 default: |
|
| 158 icon_name = PIDGIN_STOCK_TRAY_AVAILABLE; |
|
| 159 break; |
|
| 160 } |
|
| 161 |
|
| 162 if (pending) |
|
| 163 icon_name = PIDGIN_STOCK_TRAY_PENDING; |
|
| 164 if (connecting) |
|
| 165 icon_name = PIDGIN_STOCK_TRAY_CONNECT; |
|
| 166 |
|
| 167 if (icon_name) { |
|
| 168 gtk_status_icon_set_from_icon_name(docklet, icon_name); |
|
| 169 } |
|
| 170 } |
|
| 171 |
|
| 172 static void |
|
| 173 docklet_gtk_status_set_tooltip(gchar *tooltip) |
|
| 174 { |
|
| 175 gtk_status_icon_set_tooltip_text(docklet, tooltip); |
|
| 176 } |
|
| 177 |
|
| 178 static void |
|
| 179 docklet_gtk_status_position_menu(GtkMenu *menu, |
|
| 180 int *x, int *y, gboolean *push_in, |
|
| 181 gpointer user_data) |
|
| 182 { |
|
| 183 gtk_status_icon_position_menu(menu, x, y, push_in, docklet); |
|
| 184 } |
|
| 185 |
|
| 186 static void |
|
| 187 docklet_gtk_status_destroy(void) |
|
| 188 { |
|
| 189 g_return_if_fail(docklet != NULL); |
|
| 190 |
|
| 191 pidgin_docklet_remove(); |
|
| 192 |
|
| 193 if (embed_timeout) { |
|
| 194 purple_timeout_remove(embed_timeout); |
|
| 195 embed_timeout = 0; |
|
| 196 } |
|
| 197 |
|
| 198 gtk_status_icon_set_visible(docklet, FALSE); |
|
| 199 g_signal_handlers_disconnect_by_func(G_OBJECT(docklet), G_CALLBACK(docklet_gtk_destroyed_cb), NULL); |
|
| 200 g_object_unref(G_OBJECT(docklet)); |
|
| 201 docklet = NULL; |
|
| 202 |
|
| 203 purple_debug_info("docklet", "GTK+ destroyed\n"); |
|
| 204 } |
|
| 205 |
|
| 206 static void |
|
| 207 docklet_gtk_status_create(gboolean recreate) |
|
| 208 { |
|
| 209 if (docklet) { |
|
| 210 /* if this is being called when a tray icon exists, it's because |
|
| 211 something messed up. try destroying it before we proceed, |
|
| 212 although docklet_refcount may be all hosed. hopefully won't happen. */ |
|
| 213 purple_debug_warning("docklet", "trying to create icon but it already exists?\n"); |
|
| 214 docklet_gtk_status_destroy(); |
|
| 215 } |
|
| 216 |
|
| 217 docklet = gtk_status_icon_new(); |
|
| 218 g_return_if_fail(docklet != NULL); |
|
| 219 |
|
| 220 g_signal_connect(G_OBJECT(docklet), "activate", G_CALLBACK(docklet_gtk_status_activated_cb), NULL); |
|
| 221 g_signal_connect(G_OBJECT(docklet), "popup-menu", G_CALLBACK(docklet_gtk_status_clicked_cb), NULL); |
|
| 222 #if GTK_CHECK_VERSION(2,12,0) |
|
| 223 g_signal_connect(G_OBJECT(docklet), "notify::embedded", G_CALLBACK(docklet_gtk_embedded_cb), NULL); |
|
| 224 #endif |
|
| 225 g_signal_connect(G_OBJECT(docklet), "destroy", G_CALLBACK(docklet_gtk_destroyed_cb), NULL); |
|
| 226 |
|
| 227 gtk_status_icon_set_visible(docklet, TRUE); |
|
| 228 |
|
| 229 /* This is a hack to avoid a race condition between the docklet getting |
|
| 230 * embedded in the notification area and the gtkblist restoring its |
|
| 231 * previous visibility state. If the docklet does not get embedded within |
|
| 232 * the timeout, it will be removed as a visibility manager until it does |
|
| 233 * get embedded. Ideally, we would only call docklet_embedded() when the |
|
| 234 * icon was actually embedded. This only happens when the docklet is first |
|
| 235 * created, not when being recreated. |
|
| 236 * |
|
| 237 * The gtk docklet tracks whether it successfully embedded in a pref and |
|
| 238 * allows for a longer timeout period if it successfully embedded the last |
|
| 239 * time it was run. This should hopefully solve problems with the buddy |
|
| 240 * list not properly starting hidden when Pidgin is started on login. |
|
| 241 */ |
|
| 242 if (!recreate) { |
|
| 243 pidgin_docklet_embedded(); |
|
| 244 #if GTK_CHECK_VERSION(2,12,0) |
|
| 245 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/docklet/gtk/embedded")) { |
|
| 246 embed_timeout = purple_timeout_add_seconds(LONG_EMBED_TIMEOUT, docklet_gtk_embed_timeout_cb, NULL); |
|
| 247 } else { |
|
| 248 embed_timeout = purple_timeout_add_seconds(SHORT_EMBED_TIMEOUT, docklet_gtk_embed_timeout_cb, NULL); |
|
| 249 } |
|
| 250 #else |
|
| 251 embed_timeout = purple_timeout_add_seconds(SHORT_EMBED_TIMEOUT, docklet_gtk_embed_timeout_cb, NULL); |
|
| 252 #endif |
|
| 253 } |
|
| 254 |
|
| 255 purple_debug_info("docklet", "GTK+ created\n"); |
|
| 256 } |
|
| 257 |
|
| 258 static void |
|
| 259 docklet_gtk_status_create_ui_op(void) |
|
| 260 { |
|
| 261 docklet_gtk_status_create(FALSE); |
|
| 262 } |
|
| 263 |
|
| 264 static struct docklet_ui_ops ui_ops = |
|
| 265 { |
|
| 266 docklet_gtk_status_create_ui_op, |
|
| 267 docklet_gtk_status_destroy, |
|
| 268 docklet_gtk_status_update_icon, |
|
| 269 NULL, |
|
| 270 docklet_gtk_status_set_tooltip, |
|
| 271 docklet_gtk_status_position_menu |
|
| 272 }; |
|
| 273 |
|
| 274 void |
|
| 275 docklet_ui_init(void) |
|
| 276 { |
|
| 277 pidgin_docklet_set_ui_ops(&ui_ops); |
|
| 278 |
|
| 279 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/docklet/gtk"); |
|
| 280 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/docklet/x11/embedded")) { |
|
| 281 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/docklet/gtk/embedded", TRUE); |
|
| 282 purple_prefs_remove(PIDGIN_PREFS_ROOT "/docklet/x11/embedded"); |
|
| 283 } else { |
|
| 284 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/docklet/gtk/embedded", FALSE); |
|
| 285 } |
|
| 286 |
|
| 287 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), |
|
| 288 DATADIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S "pidgin" G_DIR_SEPARATOR_S "tray"); |
|
| 289 } |
|
| 290 |
|