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