2 * System tray icon (aka docklet) plugin for Purple
4 * Copyright (C) 2007 Anders Hasselqvist
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.
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.
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
26 #include "pidginstock.h"
27 #include "gtkdocklet.h"
29 #define SHORT_EMBED_TIMEOUT 5
30 #define LONG_EMBED_TIMEOUT 15
33 static GtkStatusIcon
*docklet
= NULL
;
34 static guint embed_timeout
= 0;
37 static void docklet_gtk_status_create(gboolean
);
40 docklet_gtk_recreate_cb(gpointer data
)
42 docklet_gtk_status_create(TRUE
);
48 docklet_gtk_embed_timeout_cb(gpointer data
)
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");
56 pidgin_docklet_embedded();
57 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/docklet/gtk/embedded", TRUE
);
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.
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
);
72 #if GTK_CHECK_VERSION(2,12,0)
80 #if GTK_CHECK_VERSION(2,12,0)
82 docklet_gtk_embedded_cb(GtkWidget
*widget
, gpointer data
)
85 purple_timeout_remove(embed_timeout
);
89 if (gtk_status_icon_is_embedded(docklet
)) {
90 purple_debug_info("docklet", "embedded\n");
92 pidgin_docklet_embedded();
93 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/docklet/gtk/embedded", TRUE
);
95 purple_debug_info("docklet", "detached\n");
97 pidgin_docklet_remove();
98 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/docklet/gtk/embedded", FALSE
);
106 docklet_gtk_destroyed_cb(GtkWidget
*widget
, gpointer data
)
108 purple_debug_info("docklet", "destroyed\n");
110 pidgin_docklet_remove();
112 g_object_unref(G_OBJECT(docklet
));
115 g_idle_add(docklet_gtk_recreate_cb
, NULL
);
119 docklet_gtk_status_activated_cb(GtkStatusIcon
*status_icon
, gpointer user_data
)
121 pidgin_docklet_clicked(1);
125 docklet_gtk_status_clicked_cb(GtkStatusIcon
*status_icon
, guint button
, guint activate_time
, gpointer user_data
)
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);
132 pidgin_docklet_clicked(button
);
137 docklet_gtk_status_update_icon(PurpleStatusPrimitive status
, gboolean connecting
, gboolean pending
)
139 const gchar
*icon_name
= NULL
;
142 case PURPLE_STATUS_OFFLINE
:
143 icon_name
= PIDGIN_STOCK_TRAY_OFFLINE
;
145 case PURPLE_STATUS_AWAY
:
146 icon_name
= PIDGIN_STOCK_TRAY_AWAY
;
148 case PURPLE_STATUS_UNAVAILABLE
:
149 icon_name
= PIDGIN_STOCK_TRAY_BUSY
;
151 case PURPLE_STATUS_EXTENDED_AWAY
:
152 icon_name
= PIDGIN_STOCK_TRAY_XA
;
154 case PURPLE_STATUS_INVISIBLE
:
155 icon_name
= PIDGIN_STOCK_TRAY_INVISIBLE
;
158 icon_name
= PIDGIN_STOCK_TRAY_AVAILABLE
;
163 icon_name
= PIDGIN_STOCK_TRAY_PENDING
;
165 icon_name
= PIDGIN_STOCK_TRAY_CONNECT
;
168 gtk_status_icon_set_from_icon_name(docklet
, icon_name
);
171 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/docklet/blink")) {
172 gtk_status_icon_set_blinking(docklet
, (pending
&& !connecting
));
173 } else if (gtk_status_icon_get_blinking(docklet
)) {
174 gtk_status_icon_set_blinking(docklet
, FALSE
);
179 docklet_gtk_status_set_tooltip(gchar
*tooltip
)
181 gtk_status_icon_set_tooltip(docklet
, tooltip
);
185 docklet_gtk_status_position_menu(GtkMenu
*menu
,
186 int *x
, int *y
, gboolean
*push_in
,
189 gtk_status_icon_position_menu(menu
, x
, y
, push_in
, docklet
);
193 docklet_gtk_status_destroy(void)
195 g_return_if_fail(docklet
!= NULL
);
197 pidgin_docklet_remove();
200 purple_timeout_remove(embed_timeout
);
204 gtk_status_icon_set_visible(docklet
, FALSE
);
205 g_signal_handlers_disconnect_by_func(G_OBJECT(docklet
), G_CALLBACK(docklet_gtk_destroyed_cb
), NULL
);
206 g_object_unref(G_OBJECT(docklet
));
209 purple_debug_info("docklet", "GTK+ destroyed\n");
213 docklet_gtk_status_create(gboolean recreate
)
216 /* if this is being called when a tray icon exists, it's because
217 something messed up. try destroying it before we proceed,
218 although docklet_refcount may be all hosed. hopefully won't happen. */
219 purple_debug_warning("docklet", "trying to create icon but it already exists?\n");
220 docklet_gtk_status_destroy();
223 docklet
= gtk_status_icon_new();
224 g_return_if_fail(docklet
!= NULL
);
226 g_signal_connect(G_OBJECT(docklet
), "activate", G_CALLBACK(docklet_gtk_status_activated_cb
), NULL
);
227 g_signal_connect(G_OBJECT(docklet
), "popup-menu", G_CALLBACK(docklet_gtk_status_clicked_cb
), NULL
);
228 #if GTK_CHECK_VERSION(2,12,0)
229 g_signal_connect(G_OBJECT(docklet
), "notify::embedded", G_CALLBACK(docklet_gtk_embedded_cb
), NULL
);
231 g_signal_connect(G_OBJECT(docklet
), "destroy", G_CALLBACK(docklet_gtk_destroyed_cb
), NULL
);
233 gtk_status_icon_set_visible(docklet
, TRUE
);
235 /* This is a hack to avoid a race condition between the docklet getting
236 * embedded in the notification area and the gtkblist restoring its
237 * previous visibility state. If the docklet does not get embedded within
238 * the timeout, it will be removed as a visibility manager until it does
239 * get embedded. Ideally, we would only call docklet_embedded() when the
240 * icon was actually embedded. This only happens when the docklet is first
241 * created, not when being recreated.
243 * The gtk docklet tracks whether it successfully embedded in a pref and
244 * allows for a longer timeout period if it successfully embedded the last
245 * time it was run. This should hopefully solve problems with the buddy
246 * list not properly starting hidden when Pidgin is started on login.
249 pidgin_docklet_embedded();
250 #if GTK_CHECK_VERSION(2,12,0)
251 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/docklet/gtk/embedded")) {
252 embed_timeout
= purple_timeout_add_seconds(LONG_EMBED_TIMEOUT
, docklet_gtk_embed_timeout_cb
, NULL
);
254 embed_timeout
= purple_timeout_add_seconds(SHORT_EMBED_TIMEOUT
, docklet_gtk_embed_timeout_cb
, NULL
);
257 embed_timeout
= purple_timeout_add_seconds(SHORT_EMBED_TIMEOUT
, docklet_gtk_embed_timeout_cb
, NULL
);
261 purple_debug_info("docklet", "GTK+ created\n");
265 docklet_gtk_status_create_ui_op(void)
267 docklet_gtk_status_create(FALSE
);
270 static struct docklet_ui_ops ui_ops
=
272 docklet_gtk_status_create_ui_op
,
273 docklet_gtk_status_destroy
,
274 docklet_gtk_status_update_icon
,
276 docklet_gtk_status_set_tooltip
,
277 docklet_gtk_status_position_menu
281 docklet_ui_init(void)
283 pidgin_docklet_set_ui_ops(&ui_ops
);
285 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/docklet/gtk");
286 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/docklet/x11/embedded")) {
287 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/docklet/gtk/embedded", TRUE
);
288 purple_prefs_remove(PIDGIN_PREFS_ROOT
"/docklet/x11/embedded");
290 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/docklet/gtk/embedded", FALSE
);
293 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(),
294 DATADIR G_DIR_SEPARATOR_S
"pixmaps" G_DIR_SEPARATOR_S
"pidgin" G_DIR_SEPARATOR_S
"tray");