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) {
|
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
|
|
221 debug_printf("Docklet: updated icon to %s\n",filename);
|
|
222 } else {
|
|
223 debug_printf("Docklet: failed to load icon from %s\n",filename);
|
|
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) {
|
3570
|
262 debug_printf("Docklet: embedded\n");
|
|
263 docklet_add();
|
3554
|
264 }
|
|
265
|
|
266 static void docklet_destroyed(GtkWidget *widget, void *data) {
|
3570
|
267 debug_printf("Docklet: destroyed\n");
|
|
268 docklet_flush_queue();
|
|
269 docklet_remove();
|
|
270 docklet_create();
|
3554
|
271 }
|
|
272
|
3510
|
273 static void docklet_create() {
|
|
274 GtkWidget *box;
|
|
275
|
3570
|
276 if (docklet) {
|
|
277 /* if this is being called when a docklet exists, it's because that
|
|
278 docklet is in the process of being destroyed. all we need to do
|
|
279 is tell gobject we're not interested in it any more, and throw
|
|
280 the pointer away. */
|
|
281 g_object_unref(G_OBJECT(docklet));
|
|
282 docklet = NULL;
|
3510
|
283 }
|
|
284
|
|
285 docklet = egg_tray_icon_new("Gaim");
|
|
286 box = gtk_event_box_new();
|
|
287 icon = gtk_image_new();
|
|
288
|
3554
|
289 g_signal_connect(G_OBJECT(docklet), "embedded", G_CALLBACK(docklet_embedded), NULL);
|
|
290 g_signal_connect(G_OBJECT(docklet), "destroy", G_CALLBACK(docklet_destroyed), NULL);
|
|
291 g_signal_connect(G_OBJECT(box), "button-press-event", G_CALLBACK(docklet_clicked), NULL);
|
3510
|
292
|
|
293 gtk_container_add(GTK_CONTAINER(box), icon);
|
|
294 gtk_container_add(GTK_CONTAINER(docklet), box);
|
|
295 gtk_widget_show_all(GTK_WIDGET(docklet));
|
|
296
|
3554
|
297 /* ref the docklet before we bandy it about the place */
|
|
298 g_object_ref(G_OBJECT(docklet));
|
3510
|
299 docklet_update_status();
|
|
300 docklet_update_icon();
|
|
301
|
|
302 debug_printf("Docklet: created\n");
|
|
303 }
|
|
304
|
|
305 static void gaim_signon(struct gaim_connection *gc, void *data) {
|
|
306 docklet_update_status();
|
|
307 }
|
|
308
|
|
309 static void gaim_signoff(struct gaim_connection *gc, void *data) {
|
|
310 docklet_update_status();
|
|
311 }
|
|
312
|
|
313 static void gaim_connecting(struct aim_user *user, void *data) {
|
|
314 docklet_update_status();
|
|
315 }
|
|
316
|
|
317 static void gaim_away(struct gaim_connection *gc, char *state, char *message, void *data) {
|
|
318 /* we only support global away. this is the way it is, ok? */
|
|
319 docklet_update_status();
|
|
320 }
|
|
321
|
3570
|
322 static void gaim_im_displayed_recv(struct gaim_connection *gc, char **who, char **what, void *data) {
|
3510
|
323 /* if message queuing while away is enabled, this event could be the first
|
|
324 message so we need to see if the status (and hence icon) needs changing */
|
|
325 docklet_update_status();
|
|
326 }
|
|
327
|
3570
|
328 /* static void gaim_buddy_signon(struct gaim_connection *gc, char *who, void *data) {
|
3510
|
329 }
|
|
330
|
|
331 static void gaim_buddy_signoff(struct gaim_connection *gc, char *who, void *data) {
|
|
332 }
|
|
333
|
|
334 static void gaim_buddy_away(struct gaim_connection *gc, char *who, void *data) {
|
|
335 }
|
|
336
|
|
337 static void gaim_buddy_back(struct gaim_connection *gc, char *who, void *data) {
|
|
338 }
|
|
339
|
|
340 static void gaim_new_conversation(char *who, void *data) {
|
3570
|
341 } */
|
3510
|
342
|
|
343 char *gaim_plugin_init(GModule *handle) {
|
|
344 docklet_create();
|
|
345
|
|
346 gaim_signal_connect(handle, event_signon, gaim_signon, NULL);
|
|
347 gaim_signal_connect(handle, event_signoff, gaim_signoff, NULL);
|
|
348 gaim_signal_connect(handle, event_connecting, gaim_connecting, NULL);
|
|
349 gaim_signal_connect(handle, event_away, gaim_away, NULL);
|
3570
|
350 gaim_signal_connect(handle, event_im_displayed_rcvd, gaim_im_displayed_recv, NULL);
|
|
351 /* gaim_signal_connect(handle, event_buddy_signon, gaim_buddy_signon, NULL);
|
3510
|
352 gaim_signal_connect(handle, event_buddy_signoff, gaim_buddy_signoff, NULL);
|
|
353 gaim_signal_connect(handle, event_buddy_away, gaim_buddy_away, NULL);
|
|
354 gaim_signal_connect(handle, event_buddy_back, gaim_buddy_back, NULL);
|
3570
|
355 gaim_signal_connect(handle, event_new_conversation, gaim_new_conversation, NULL); */
|
3510
|
356
|
|
357 return NULL;
|
|
358 }
|
|
359
|
3554
|
360 void gaim_plugin_remove() {
|
3570
|
361 if (GTK_WIDGET_VISIBLE(docklet)) {
|
|
362 docklet_remove();
|
|
363 }
|
3554
|
364
|
3570
|
365 docklet_flush_queue();
|
3554
|
366
|
3570
|
367 g_object_unref(G_OBJECT(docklet));
|
|
368 g_signal_handlers_disconnect_by_func(G_OBJECT(docklet), G_CALLBACK(docklet_destroyed), NULL);
|
|
369 gtk_widget_destroy(GTK_WIDGET(docklet));
|
3554
|
370
|
3570
|
371 debug_printf("Docklet: removed\n");
|
3554
|
372 }
|
|
373
|
3570
|
374 GtkWidget *gaim_plugin_config_gtk() {
|
|
375 GtkWidget *frame;
|
|
376 GtkWidget *vbox, *hbox;
|
|
377 GtkWidget *toggle;
|
3517
|
378
|
3570
|
379 frame = gtk_vbox_new(FALSE, 18);
|
|
380 gtk_container_set_border_width(GTK_CONTAINER(frame), 12);
|
|
381
|
|
382 vbox = make_frame(frame, _("Docklet Configuration"));
|
|
383 hbox = gtk_hbox_new(FALSE, 18);
|
|
384 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
|
3517
|
385
|
3570
|
386 /* toggle = gtk_check_button_new_with_mnemonic(_("_Automatically show buddy list on sign on"));
|
|
387 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), blist_options & OPT_BLIST_APP_BUDDY_SHOW);
|
|
388 g_signal_connect(G_OBJECT(toggle), "clicked", G_CALLBACK(docklet_toggle_blist_show), NULL);
|
|
389 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); */
|
3517
|
390
|
3570
|
391 toggle = gtk_check_button_new_with_mnemonic(_("_Hide new messages until docklet is clicked"));
|
|
392 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), away_options & OPT_AWAY_QUEUE_UNREAD);
|
|
393 g_signal_connect(G_OBJECT(toggle), "clicked", G_CALLBACK(docklet_toggle_queue), NULL);
|
|
394 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0);
|
|
395
|
|
396 gtk_widget_show_all(frame);
|
|
397 return frame;
|
3517
|
398 }
|
3570
|
399
|
3551
|
400 struct gaim_plugin_description desc;
|
|
401 struct gaim_plugin_description *gaim_plugin_desc() {
|
|
402 desc.api_version = PLUGIN_API_VERSION;
|
3773
|
403 desc.name = g_strdup(_("Tray Icon"));
|
3551
|
404 desc.version = g_strdup(VERSION);
|
3570
|
405 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."));
|
|
406 desc.authors = g_strdup(_("Robert McQueen <robot101@debian.org>"));
|
3551
|
407 desc.url = g_strdup(WEBSITE);
|
|
408 return &desc;
|
|
409 }
|
3510
|
410
|
3570
|
411 char *name() {
|
3550
|
412 return _("System Tray Docklet");
|
3510
|
413 }
|
|
414
|
3570
|
415 char *description() {
|
3550
|
416 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
|
417 }
|