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