3510
|
1 /* System tray docklet plugin for Gaim
|
|
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):
|
|
23 - don't crash when the plugin gets unloaded (it seems to crash after
|
|
24 the plugin has gone, when gtk updates the button in the plugins
|
|
25 dialog. backtrace is always useless. weird)
|
|
26 - handle and update tooltips to show your current accounts
|
|
27 - dernyi's account status menu in the right click
|
|
28 - store icons in gtk2 stock icon thing (needs doing for the whole prog)
|
3554
|
29 - optional pop up notices when GNOME2's system-tray-applet supports it
|
|
30 - support blinking the icon when messages are pending */
|
3510
|
31
|
|
32 /* includes */
|
|
33 #include <gtk/gtk.h>
|
|
34 #include "gaim.h"
|
|
35 #include "eggtrayicon.h"
|
|
36
|
3867
|
37 #ifndef GAIM_PLUGINS
|
|
38 #define GAIM_PLUGINS
|
|
39 #endif
|
|
40
|
3510
|
41 /* types */
|
|
42 enum docklet_status {
|
|
43 online,
|
|
44 away,
|
|
45 away_pending,
|
3517
|
46 unread_pending,
|
3510
|
47 connecting,
|
|
48 offline
|
|
49 };
|
|
50
|
|
51 /* functions */
|
|
52 static void docklet_create();
|
3554
|
53 static void docklet_update_status();
|
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) {
|
|
167 switch (event->button) {
|
|
168 case 1:
|
3517
|
169 if (unread_message_queue) {
|
3570
|
170 docklet_flush_queue();
|
3517
|
171 docklet_update_status();
|
3570
|
172 } else {
|
3517
|
173 docklet_toggle();
|
3554
|
174 }
|
3510
|
175 break;
|
|
176 case 2:
|
|
177 break;
|
|
178 case 3:
|
|
179 docklet_menu(event);
|
|
180 break;
|
|
181 }
|
|
182 }
|
|
183
|
|
184 static void docklet_update_icon() {
|
|
185 gchar *filename;
|
|
186 GdkPixbuf *unscaled;
|
|
187
|
|
188 switch (status) {
|
|
189 case online:
|
|
190 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "online.png", NULL);
|
|
191 break;
|
|
192 case away:
|
|
193 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "away.png", NULL);
|
|
194 break;
|
|
195 case away_pending:
|
|
196 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "msgpend.png", NULL);
|
|
197 break;
|
3570
|
198 case unread_pending:
|
3517
|
199 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "msgunread.png", NULL);
|
|
200 break;
|
3510
|
201 case connecting:
|
3517
|
202 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "connect.png", NULL);
|
3510
|
203 break;
|
3867
|
204 default:
|
3510
|
205 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "offline.png", NULL);
|
|
206 }
|
|
207
|
|
208 unscaled = gdk_pixbuf_new_from_file(filename, NULL);
|
|
209
|
|
210 if (unscaled) {
|
|
211 GdkPixbuf *scaled;
|
|
212
|
|
213 scaled = gdk_pixbuf_scale_simple(unscaled, 24, 24, GDK_INTERP_BILINEAR);
|
|
214 gtk_image_set_from_pixbuf(GTK_IMAGE(icon), scaled);
|
|
215 g_object_unref(unscaled);
|
|
216 g_object_unref(scaled);
|
|
217
|
|
218 debug_printf("Docklet: updated icon to %s\n",filename);
|
|
219 } else {
|
|
220 debug_printf("Docklet: failed to load icon from %s\n",filename);
|
|
221 }
|
|
222
|
|
223 g_free(filename);
|
|
224 }
|
|
225
|
|
226 static void docklet_update_status() {
|
|
227 enum docklet_status oldstatus;
|
|
228
|
|
229 oldstatus = status;
|
|
230
|
|
231 if (connections) {
|
3517
|
232 if (unread_message_queue) {
|
|
233 status = unread_pending;
|
|
234 } else if (awaymessage) {
|
3510
|
235 if (message_queue) {
|
|
236 status = away_pending;
|
|
237 } else {
|
|
238 status = away;
|
|
239 }
|
3554
|
240 } else if (connecting_count) {
|
|
241 status = connecting;
|
3510
|
242 } else {
|
|
243 status = online;
|
|
244 }
|
|
245 } else {
|
3517
|
246 if (connecting_count) {
|
|
247 status = connecting;
|
|
248 } else {
|
|
249 status = offline;
|
|
250 }
|
3510
|
251 }
|
|
252
|
|
253 if (status != oldstatus) {
|
|
254 docklet_update_icon();
|
|
255 }
|
|
256 }
|
|
257
|
3554
|
258 static void docklet_embedded(GtkWidget *widget, void *data) {
|
3570
|
259 debug_printf("Docklet: embedded\n");
|
|
260 docklet_add();
|
3554
|
261 }
|
|
262
|
|
263 static void docklet_destroyed(GtkWidget *widget, void *data) {
|
3570
|
264 debug_printf("Docklet: destroyed\n");
|
|
265 docklet_flush_queue();
|
|
266 docklet_remove();
|
|
267 docklet_create();
|
3554
|
268 }
|
|
269
|
3510
|
270 static void docklet_create() {
|
|
271 GtkWidget *box;
|
|
272
|
3570
|
273 if (docklet) {
|
|
274 /* if this is being called when a docklet exists, it's because that
|
|
275 docklet is in the process of being destroyed. all we need to do
|
|
276 is tell gobject we're not interested in it any more, and throw
|
|
277 the pointer away. */
|
|
278 g_object_unref(G_OBJECT(docklet));
|
|
279 docklet = NULL;
|
3510
|
280 }
|
|
281
|
|
282 docklet = egg_tray_icon_new("Gaim");
|
|
283 box = gtk_event_box_new();
|
|
284 icon = gtk_image_new();
|
|
285
|
3554
|
286 g_signal_connect(G_OBJECT(docklet), "embedded", G_CALLBACK(docklet_embedded), NULL);
|
|
287 g_signal_connect(G_OBJECT(docklet), "destroy", G_CALLBACK(docklet_destroyed), NULL);
|
|
288 g_signal_connect(G_OBJECT(box), "button-press-event", G_CALLBACK(docklet_clicked), NULL);
|
3510
|
289
|
|
290 gtk_container_add(GTK_CONTAINER(box), icon);
|
|
291 gtk_container_add(GTK_CONTAINER(docklet), box);
|
|
292 gtk_widget_show_all(GTK_WIDGET(docklet));
|
|
293
|
3554
|
294 /* ref the docklet before we bandy it about the place */
|
|
295 g_object_ref(G_OBJECT(docklet));
|
3510
|
296 docklet_update_status();
|
|
297 docklet_update_icon();
|
|
298
|
|
299 debug_printf("Docklet: created\n");
|
|
300 }
|
|
301
|
|
302 static void gaim_signon(struct gaim_connection *gc, void *data) {
|
|
303 docklet_update_status();
|
|
304 }
|
|
305
|
|
306 static void gaim_signoff(struct gaim_connection *gc, void *data) {
|
|
307 docklet_update_status();
|
|
308 }
|
|
309
|
|
310 static void gaim_connecting(struct aim_user *user, void *data) {
|
|
311 docklet_update_status();
|
|
312 }
|
|
313
|
|
314 static void gaim_away(struct gaim_connection *gc, char *state, char *message, void *data) {
|
|
315 /* we only support global away. this is the way it is, ok? */
|
|
316 docklet_update_status();
|
|
317 }
|
|
318
|
3570
|
319 static void gaim_im_displayed_recv(struct gaim_connection *gc, char **who, char **what, void *data) {
|
3510
|
320 /* if message queuing while away is enabled, this event could be the first
|
|
321 message so we need to see if the status (and hence icon) needs changing */
|
|
322 docklet_update_status();
|
|
323 }
|
|
324
|
3570
|
325 /* static void gaim_buddy_signon(struct gaim_connection *gc, char *who, void *data) {
|
3510
|
326 }
|
|
327
|
|
328 static void gaim_buddy_signoff(struct gaim_connection *gc, char *who, void *data) {
|
|
329 }
|
|
330
|
|
331 static void gaim_buddy_away(struct gaim_connection *gc, char *who, void *data) {
|
|
332 }
|
|
333
|
|
334 static void gaim_buddy_back(struct gaim_connection *gc, char *who, void *data) {
|
|
335 }
|
|
336
|
|
337 static void gaim_new_conversation(char *who, void *data) {
|
3570
|
338 } */
|
3510
|
339
|
|
340 char *gaim_plugin_init(GModule *handle) {
|
|
341 docklet_create();
|
|
342
|
|
343 gaim_signal_connect(handle, event_signon, gaim_signon, NULL);
|
|
344 gaim_signal_connect(handle, event_signoff, gaim_signoff, NULL);
|
|
345 gaim_signal_connect(handle, event_connecting, gaim_connecting, NULL);
|
|
346 gaim_signal_connect(handle, event_away, gaim_away, NULL);
|
3570
|
347 gaim_signal_connect(handle, event_im_displayed_rcvd, gaim_im_displayed_recv, NULL);
|
|
348 /* gaim_signal_connect(handle, event_buddy_signon, gaim_buddy_signon, NULL);
|
3510
|
349 gaim_signal_connect(handle, event_buddy_signoff, gaim_buddy_signoff, NULL);
|
|
350 gaim_signal_connect(handle, event_buddy_away, gaim_buddy_away, NULL);
|
|
351 gaim_signal_connect(handle, event_buddy_back, gaim_buddy_back, NULL);
|
3570
|
352 gaim_signal_connect(handle, event_new_conversation, gaim_new_conversation, NULL); */
|
3510
|
353
|
|
354 return NULL;
|
|
355 }
|
|
356
|
3554
|
357 void gaim_plugin_remove() {
|
3570
|
358 if (GTK_WIDGET_VISIBLE(docklet)) {
|
|
359 docklet_remove();
|
|
360 }
|
3554
|
361
|
3570
|
362 docklet_flush_queue();
|
3554
|
363
|
3570
|
364 g_object_unref(G_OBJECT(docklet));
|
|
365 g_signal_handlers_disconnect_by_func(G_OBJECT(docklet), G_CALLBACK(docklet_destroyed), NULL);
|
|
366 gtk_widget_destroy(GTK_WIDGET(docklet));
|
3554
|
367
|
3570
|
368 debug_printf("Docklet: removed\n");
|
3554
|
369 }
|
|
370
|
3570
|
371 GtkWidget *gaim_plugin_config_gtk() {
|
|
372 GtkWidget *frame;
|
|
373 GtkWidget *vbox, *hbox;
|
|
374 GtkWidget *toggle;
|
3517
|
375
|
3570
|
376 frame = gtk_vbox_new(FALSE, 18);
|
|
377 gtk_container_set_border_width(GTK_CONTAINER(frame), 12);
|
|
378
|
|
379 vbox = make_frame(frame, _("Docklet Configuration"));
|
|
380 hbox = gtk_hbox_new(FALSE, 18);
|
|
381 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
|
3517
|
382
|
3570
|
383 /* toggle = gtk_check_button_new_with_mnemonic(_("_Automatically show buddy list on sign on"));
|
|
384 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), blist_options & OPT_BLIST_APP_BUDDY_SHOW);
|
|
385 g_signal_connect(G_OBJECT(toggle), "clicked", G_CALLBACK(docklet_toggle_blist_show), NULL);
|
|
386 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); */
|
3517
|
387
|
3570
|
388 toggle = gtk_check_button_new_with_mnemonic(_("_Hide new messages until docklet is clicked"));
|
|
389 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), away_options & OPT_AWAY_QUEUE_UNREAD);
|
|
390 g_signal_connect(G_OBJECT(toggle), "clicked", G_CALLBACK(docklet_toggle_queue), NULL);
|
|
391 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0);
|
|
392
|
|
393 gtk_widget_show_all(frame);
|
|
394 return frame;
|
3517
|
395 }
|
3570
|
396
|
3551
|
397 struct gaim_plugin_description desc;
|
|
398 struct gaim_plugin_description *gaim_plugin_desc() {
|
|
399 desc.api_version = PLUGIN_API_VERSION;
|
3773
|
400 desc.name = g_strdup(_("Tray Icon"));
|
3551
|
401 desc.version = g_strdup(VERSION);
|
3570
|
402 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."));
|
|
403 desc.authors = g_strdup(_("Robert McQueen <robot101@debian.org>"));
|
3551
|
404 desc.url = g_strdup(WEBSITE);
|
|
405 return &desc;
|
|
406 }
|
3510
|
407
|
3570
|
408 char *name() {
|
3550
|
409 return _("System Tray Docklet");
|
3510
|
410 }
|
|
411
|
3570
|
412 char *description() {
|
3550
|
413 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.");
|
3510
|
414 }
|