4093
|
1 /* System tray icon (aka docklet) plugin for Gaim
|
3510
|
2 * Copyright (C) 2002 Robert McQueen <robot101@debian.org>
|
|
3 * Inspired by a similar plugin by:
|
|
4 * John (J5) Palmieri <johnp@martianrock.com>
|
|
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 /* todo (in order of importance):
|
4093
|
23 - don't crash when the plugin gets unloaded (may be a libegg bug,
|
|
24 see #101467 in gnome bugzilla)
|
|
25 - handle and update tooltips to show your current accounts ?
|
3510
|
26 - dernyi's account status menu in the right click
|
|
27 - store icons in gtk2 stock icon thing (needs doing for the whole prog)
|
3554
|
28 - optional pop up notices when GNOME2's system-tray-applet supports it
|
|
29 - support blinking the icon when messages are pending */
|
3510
|
30
|
|
31 /* includes */
|
|
32 #include <gtk/gtk.h>
|
|
33 #include "gaim.h"
|
|
34 #include "eggtrayicon.h"
|
|
35
|
3867
|
36 #ifndef GAIM_PLUGINS
|
|
37 #define GAIM_PLUGINS
|
|
38 #endif
|
|
39
|
3510
|
40 /* types */
|
|
41 enum docklet_status {
|
4157
|
42 offline,
|
|
43 offline_connecting,
|
3510
|
44 online,
|
4157
|
45 online_connecting,
|
|
46 online_pending,
|
3510
|
47 away,
|
4157
|
48 away_pending
|
3510
|
49 };
|
|
50
|
|
51 /* functions */
|
4093
|
52 static gboolean docklet_create();
|
4157
|
53 static gboolean docklet_update_status();
|
4093
|
54 void gaim_plugin_remove();
|
3510
|
55
|
|
56 /* globals */
|
3513
|
57 static EggTrayIcon *docklet = NULL;
|
3510
|
58 static GtkWidget *icon;
|
|
59 static enum docklet_status status;
|
|
60
|
3554
|
61 static void docklet_toggle_mute(GtkWidget *toggle, void *data) {
|
|
62 mute_sounds = GTK_CHECK_MENU_ITEM(toggle)->active;
|
3510
|
63 }
|
|
64
|
3554
|
65 static void docklet_toggle_queue(GtkWidget *widget, void *data) {
|
|
66 away_options ^= OPT_AWAY_QUEUE_UNREAD;
|
|
67 save_prefs();
|
3510
|
68 }
|
3570
|
69
|
|
70 /* static void docklet_toggle_blist_show(GtkWidget *widget, void *data) {
|
|
71 blist_options ^= OPT_BLIST_APP_BUDDY_SHOW;
|
|
72 save_prefs();
|
|
73 } */
|
|
74
|
3554
|
75 static void docklet_flush_queue() {
|
3570
|
76 if (unread_message_queue) {
|
|
77 purge_away_queue(unread_message_queue);
|
|
78 unread_message_queue = NULL;
|
|
79 }
|
3510
|
80 }
|
|
81
|
|
82 static void docklet_menu(GdkEventButton *event) {
|
3513
|
83 static GtkWidget *menu = NULL;
|
3512
|
84 GtkWidget *entry;
|
3510
|
85
|
|
86 if (menu) {
|
|
87 gtk_widget_destroy(menu);
|
|
88 }
|
|
89
|
|
90 menu = gtk_menu_new();
|
|
91
|
4157
|
92 switch (status) {
|
|
93 case offline:
|
|
94 case offline_connecting:
|
|
95 break;
|
|
96 case online:
|
|
97 case online_connecting:
|
|
98 case online_pending: {
|
3510
|
99 GtkWidget *docklet_awaymenu;
|
|
100 GSList *awy = NULL;
|
|
101 struct away_message *a = NULL;
|
|
102
|
|
103 docklet_awaymenu = gtk_menu_new();
|
|
104 awy = away_messages;
|
|
105
|
|
106 while (awy) {
|
|
107 a = (struct away_message *)awy->data;
|
|
108
|
|
109 entry = gtk_menu_item_new_with_label(a->name);
|
3554
|
110 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(do_away_message), a);
|
3510
|
111 gtk_menu_append(GTK_MENU(docklet_awaymenu), entry);
|
|
112
|
|
113 awy = g_slist_next(awy);
|
|
114 }
|
|
115
|
4157
|
116 if (away_messages)
|
|
117 gaim_separator(docklet_awaymenu);
|
3510
|
118
|
|
119 entry = gtk_menu_item_new_with_label(_("New..."));
|
3554
|
120 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(create_away_mess), NULL);
|
3510
|
121 gtk_menu_append(GTK_MENU(docklet_awaymenu), entry);
|
|
122
|
|
123 entry = gtk_menu_item_new_with_label(_("Away"));
|
3512
|
124 gtk_menu_item_set_submenu(GTK_MENU_ITEM(entry), docklet_awaymenu);
|
3510
|
125 gtk_menu_append(GTK_MENU(menu), entry);
|
4157
|
126 } break;
|
|
127 case away:
|
|
128 case away_pending:
|
3510
|
129 entry = gtk_menu_item_new_with_label(_("Back"));
|
3554
|
130 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(do_im_back), NULL);
|
3510
|
131 gtk_menu_append(GTK_MENU(menu), entry);
|
4157
|
132 break;
|
3510
|
133 }
|
|
134
|
4157
|
135 switch (status) {
|
|
136 case offline:
|
|
137 case offline_connecting:
|
|
138 entry = gtk_menu_item_new_with_label(_("Auto-login"));
|
|
139 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(auto_login), NULL);
|
|
140 gtk_menu_append(GTK_MENU(menu), entry);
|
|
141 break;
|
|
142 default:
|
|
143 entry = gtk_menu_item_new_with_label(_("Signoff"));
|
|
144 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(signoff_all), NULL);
|
|
145 gtk_menu_append(GTK_MENU(menu), entry);
|
|
146 break;
|
|
147 }
|
|
148
|
|
149 gaim_separator(menu);
|
3510
|
150
|
3517
|
151 entry = gtk_check_menu_item_new_with_label(_("Mute Sounds"));
|
|
152 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry), mute_sounds);
|
3554
|
153 g_signal_connect(G_OBJECT(entry), "toggled", G_CALLBACK(docklet_toggle_mute), NULL);
|
3517
|
154 gtk_menu_append(GTK_MENU(menu), entry);
|
|
155
|
4157
|
156 gaim_new_item_from_pixbuf(menu, _("Accounts..."), "accounts-menu.png", G_CALLBACK(account_editor), NULL, 0, 0, 0);
|
|
157 gaim_new_item_from_stock(menu, _("Preferences..."), GTK_STOCK_PREFERENCES, G_CALLBACK(show_prefs), NULL, 0, 0, 0);
|
3510
|
158
|
4157
|
159 gaim_separator(menu);
|
3510
|
160
|
4157
|
161 gaim_new_item_from_pixbuf(menu, _("About Gaim..."), "about_menu.png", G_CALLBACK(show_about), NULL, 0, 0, 0);
|
|
162 gaim_new_item_from_stock(menu, _("Quit"), GTK_STOCK_QUIT, G_CALLBACK(do_quit), NULL, 0, 0, 0);
|
3510
|
163
|
|
164 gtk_widget_show_all(menu);
|
|
165 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
|
|
166 }
|
|
167
|
|
168 static void docklet_clicked(GtkWidget *button, GdkEventButton *event, void *data) {
|
3939
|
169 if (event->type != GDK_BUTTON_PRESS)
|
|
170 return;
|
|
171
|
3510
|
172 switch (event->button) {
|
|
173 case 1:
|
3517
|
174 if (unread_message_queue) {
|
3570
|
175 docklet_flush_queue();
|
3517
|
176 docklet_update_status();
|
3570
|
177 } else {
|
3517
|
178 docklet_toggle();
|
3554
|
179 }
|
3510
|
180 break;
|
|
181 case 2:
|
|
182 break;
|
|
183 case 3:
|
|
184 docklet_menu(event);
|
|
185 break;
|
|
186 }
|
|
187 }
|
|
188
|
|
189 static void docklet_update_icon() {
|
3939
|
190 gchar *filename = NULL;
|
3510
|
191 GdkPixbuf *unscaled;
|
|
192
|
|
193 switch (status) {
|
4157
|
194 case offline:
|
|
195 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "offline.png", NULL);
|
|
196 break;
|
|
197 case offline_connecting:
|
|
198 case online_connecting:
|
|
199 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "connect.png", NULL);
|
|
200 break;
|
3510
|
201 case online:
|
|
202 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "online.png", NULL);
|
|
203 break;
|
4157
|
204 case online_pending:
|
|
205 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "msgunread.png", NULL);
|
|
206 break;
|
3510
|
207 case away:
|
|
208 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "away.png", NULL);
|
|
209 break;
|
|
210 case away_pending:
|
|
211 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "msgpend.png", NULL);
|
|
212 break;
|
|
213 }
|
|
214
|
|
215 unscaled = gdk_pixbuf_new_from_file(filename, NULL);
|
|
216
|
|
217 if (unscaled) {
|
|
218 GdkPixbuf *scaled;
|
|
219
|
|
220 scaled = gdk_pixbuf_scale_simple(unscaled, 24, 24, GDK_INTERP_BILINEAR);
|
|
221 gtk_image_set_from_pixbuf(GTK_IMAGE(icon), scaled);
|
|
222 g_object_unref(unscaled);
|
|
223 g_object_unref(scaled);
|
|
224
|
4093
|
225 debug_printf("Tray Icon: updated icon to %s\n",filename);
|
3510
|
226 } else {
|
4093
|
227 debug_printf("Tray Icon: failed to load icon from %s\n",filename);
|
3510
|
228 }
|
|
229
|
|
230 g_free(filename);
|
|
231 }
|
|
232
|
4157
|
233 static gboolean docklet_update_status() {
|
3510
|
234 enum docklet_status oldstatus;
|
|
235
|
|
236 oldstatus = status;
|
|
237
|
|
238 if (connections) {
|
3517
|
239 if (unread_message_queue) {
|
4157
|
240 status = online_pending;
|
3517
|
241 } else if (awaymessage) {
|
3510
|
242 if (message_queue) {
|
|
243 status = away_pending;
|
|
244 } else {
|
|
245 status = away;
|
|
246 }
|
3554
|
247 } else if (connecting_count) {
|
4157
|
248 status = online_connecting;
|
3510
|
249 } else {
|
|
250 status = online;
|
|
251 }
|
|
252 } else {
|
3517
|
253 if (connecting_count) {
|
4157
|
254 status = offline_connecting;
|
3517
|
255 } else {
|
|
256 status = offline;
|
|
257 }
|
3510
|
258 }
|
|
259
|
|
260 if (status != oldstatus) {
|
|
261 docklet_update_icon();
|
|
262 }
|
4157
|
263
|
|
264 return FALSE; /* for when we're called by the glib idle handler */
|
3510
|
265 }
|
|
266
|
3554
|
267 static void docklet_embedded(GtkWidget *widget, void *data) {
|
4093
|
268 debug_printf("Tray Icon: embedded\n");
|
3570
|
269 docklet_add();
|
3554
|
270 }
|
|
271
|
|
272 static void docklet_destroyed(GtkWidget *widget, void *data) {
|
4093
|
273 debug_printf("Tray Icon: destroyed\n");
|
|
274
|
|
275 docklet_remove();
|
|
276
|
3570
|
277 docklet_flush_queue();
|
4093
|
278
|
|
279 g_object_unref(G_OBJECT(docklet));
|
|
280 docklet = NULL;
|
|
281
|
|
282 g_idle_add(docklet_create, NULL);
|
3554
|
283 }
|
|
284
|
4093
|
285 static gboolean docklet_create(void *data) {
|
3510
|
286 GtkWidget *box;
|
|
287
|
3570
|
288 if (docklet) {
|
4093
|
289 /* if this is being called when a tray icon exists, it's because
|
|
290 something messed up. try destroying it before we proceed,
|
|
291 although docklet_refcount may be all hosed. hopefully won't happen. */
|
|
292 debug_printf("Tray Icon: trying to create icon but it already exists?\n");
|
|
293 gaim_plugin_remove();
|
3510
|
294 }
|
|
295
|
|
296 docklet = egg_tray_icon_new("Gaim");
|
|
297 box = gtk_event_box_new();
|
|
298 icon = gtk_image_new();
|
|
299
|
3554
|
300 g_signal_connect(G_OBJECT(docklet), "embedded", G_CALLBACK(docklet_embedded), NULL);
|
|
301 g_signal_connect(G_OBJECT(docklet), "destroy", G_CALLBACK(docklet_destroyed), NULL);
|
|
302 g_signal_connect(G_OBJECT(box), "button-press-event", G_CALLBACK(docklet_clicked), NULL);
|
3510
|
303
|
|
304 gtk_container_add(GTK_CONTAINER(box), icon);
|
|
305 gtk_container_add(GTK_CONTAINER(docklet), box);
|
|
306 gtk_widget_show_all(GTK_WIDGET(docklet));
|
|
307
|
3554
|
308 /* ref the docklet before we bandy it about the place */
|
|
309 g_object_ref(G_OBJECT(docklet));
|
3510
|
310 docklet_update_status();
|
|
311 docklet_update_icon();
|
|
312
|
4093
|
313 debug_printf("Tray Icon: created\n");
|
|
314
|
|
315 return FALSE; /* for when we're called by the glib idle handler */
|
3510
|
316 }
|
|
317
|
|
318 static void gaim_signon(struct gaim_connection *gc, void *data) {
|
|
319 docklet_update_status();
|
|
320 }
|
|
321
|
|
322 static void gaim_signoff(struct gaim_connection *gc, void *data) {
|
4157
|
323 /* do this when idle so that if the prpl was connecting
|
|
324 and was cancelled, we register that connecting_count
|
|
325 has returned to 0 */
|
|
326 g_idle_add(docklet_update_status, NULL);
|
3510
|
327 }
|
|
328
|
|
329 static void gaim_connecting(struct aim_user *user, void *data) {
|
|
330 docklet_update_status();
|
|
331 }
|
|
332
|
|
333 static void gaim_away(struct gaim_connection *gc, char *state, char *message, void *data) {
|
|
334 /* we only support global away. this is the way it is, ok? */
|
|
335 docklet_update_status();
|
|
336 }
|
|
337
|
3570
|
338 static void gaim_im_displayed_recv(struct gaim_connection *gc, char **who, char **what, void *data) {
|
3510
|
339 /* if message queuing while away is enabled, this event could be the first
|
|
340 message so we need to see if the status (and hence icon) needs changing */
|
|
341 docklet_update_status();
|
|
342 }
|
|
343
|
3570
|
344 /* static void gaim_buddy_signon(struct gaim_connection *gc, char *who, void *data) {
|
3510
|
345 }
|
|
346
|
|
347 static void gaim_buddy_signoff(struct gaim_connection *gc, char *who, void *data) {
|
|
348 }
|
|
349
|
|
350 static void gaim_buddy_away(struct gaim_connection *gc, char *who, void *data) {
|
|
351 }
|
|
352
|
|
353 static void gaim_buddy_back(struct gaim_connection *gc, char *who, void *data) {
|
|
354 }
|
|
355
|
|
356 static void gaim_new_conversation(char *who, void *data) {
|
3570
|
357 } */
|
3510
|
358
|
|
359 char *gaim_plugin_init(GModule *handle) {
|
4093
|
360 docklet_create(NULL);
|
3510
|
361
|
|
362 gaim_signal_connect(handle, event_signon, gaim_signon, NULL);
|
|
363 gaim_signal_connect(handle, event_signoff, gaim_signoff, NULL);
|
|
364 gaim_signal_connect(handle, event_connecting, gaim_connecting, NULL);
|
|
365 gaim_signal_connect(handle, event_away, gaim_away, NULL);
|
3570
|
366 gaim_signal_connect(handle, event_im_displayed_rcvd, gaim_im_displayed_recv, NULL);
|
|
367 /* gaim_signal_connect(handle, event_buddy_signon, gaim_buddy_signon, NULL);
|
3510
|
368 gaim_signal_connect(handle, event_buddy_signoff, gaim_buddy_signoff, NULL);
|
|
369 gaim_signal_connect(handle, event_buddy_away, gaim_buddy_away, NULL);
|
|
370 gaim_signal_connect(handle, event_buddy_back, gaim_buddy_back, NULL);
|
3570
|
371 gaim_signal_connect(handle, event_new_conversation, gaim_new_conversation, NULL); */
|
3510
|
372
|
|
373 return NULL;
|
|
374 }
|
|
375
|
3554
|
376 void gaim_plugin_remove() {
|
3570
|
377 if (GTK_WIDGET_VISIBLE(docklet)) {
|
|
378 docklet_remove();
|
|
379 }
|
3554
|
380
|
3570
|
381 docklet_flush_queue();
|
3554
|
382
|
3570
|
383 g_signal_handlers_disconnect_by_func(G_OBJECT(docklet), G_CALLBACK(docklet_destroyed), NULL);
|
|
384 gtk_widget_destroy(GTK_WIDGET(docklet));
|
3554
|
385
|
4093
|
386 g_object_unref(G_OBJECT(docklet));
|
|
387 docklet = NULL;
|
|
388
|
|
389 debug_printf("Tray Icon: removed\n");
|
3554
|
390 }
|
|
391
|
3570
|
392 GtkWidget *gaim_plugin_config_gtk() {
|
|
393 GtkWidget *frame;
|
|
394 GtkWidget *vbox, *hbox;
|
|
395 GtkWidget *toggle;
|
3517
|
396
|
3570
|
397 frame = gtk_vbox_new(FALSE, 18);
|
|
398 gtk_container_set_border_width(GTK_CONTAINER(frame), 12);
|
|
399
|
4093
|
400 vbox = make_frame(frame, _("Tray Icon Configuration"));
|
3570
|
401 hbox = gtk_hbox_new(FALSE, 18);
|
|
402 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
|
3517
|
403
|
3570
|
404 /* toggle = gtk_check_button_new_with_mnemonic(_("_Automatically show buddy list on sign on"));
|
|
405 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), blist_options & OPT_BLIST_APP_BUDDY_SHOW);
|
|
406 g_signal_connect(G_OBJECT(toggle), "clicked", G_CALLBACK(docklet_toggle_blist_show), NULL);
|
|
407 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); */
|
3517
|
408
|
4093
|
409 toggle = gtk_check_button_new_with_mnemonic(_("_Hide new messages until tray icon is clicked"));
|
3570
|
410 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), away_options & OPT_AWAY_QUEUE_UNREAD);
|
|
411 g_signal_connect(G_OBJECT(toggle), "clicked", G_CALLBACK(docklet_toggle_queue), NULL);
|
|
412 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0);
|
|
413
|
|
414 gtk_widget_show_all(frame);
|
|
415 return frame;
|
3517
|
416 }
|
3570
|
417
|
3551
|
418 struct gaim_plugin_description desc;
|
|
419 struct gaim_plugin_description *gaim_plugin_desc() {
|
|
420 desc.api_version = PLUGIN_API_VERSION;
|
3773
|
421 desc.name = g_strdup(_("Tray Icon"));
|
3551
|
422 desc.version = g_strdup(VERSION);
|
4108
|
423 desc.description = g_strdup(_("Interacts with a System Tray applet (in GNOME or KDE, for example) to display the current status of Gaim, allow fast access to commonly used functions, and to toggle display of the buddy list or login window. Also allows messages to be queued until the icon is clicked, similar to ICQ (although the icon doesn't flash yet =)."));
|
3570
|
424 desc.authors = g_strdup(_("Robert McQueen <robot101@debian.org>"));
|
3551
|
425 desc.url = g_strdup(WEBSITE);
|
|
426 return &desc;
|
|
427 }
|
3510
|
428
|
3570
|
429 char *name() {
|
4093
|
430 return _("System Tray Icon");
|
3510
|
431 }
|
|
432
|
3570
|
433 char *description() {
|
4093
|
434 return _("Interacts with a System Tray applet (in GNOME or KDE, for example) to display the current status of Gaim, allow fast access to commonly used functions, and to toggle display of the buddy list or login window. Also allows messages to be queued until the icon is clicked, similar to ICQ (although the icon doesn't flash yet =).");
|
3510
|
435 }
|