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 - have a toggle on the menu to mute all Gaim sounds
|
|
27 - handle and update tooltips to show your current accounts
|
|
28 - connecting status support (needs more fruxing with the core)
|
|
29 - dernyi's account status menu in the right click
|
|
30 - store icons in gtk2 stock icon thing (needs doing for the whole prog)
|
|
31 - pop up notices when GNOME2's system-tray-applet supports it, with a
|
|
32 prefs dialog to choose what to alert for */
|
|
33
|
|
34 /* includes */
|
|
35 #define GAIM_PLUGINS
|
|
36 #include <gtk/gtk.h>
|
|
37 #include "gaim.h"
|
|
38 #include "eggtrayicon.h"
|
|
39
|
|
40 /* macros */
|
|
41 #define DOCKLET_WINDOW_ICONIFIED(x) (gdk_window_get_state(GTK_WIDGET(x)->window) & GDK_WINDOW_STATE_ICONIFIED)
|
|
42
|
|
43 /* types */
|
|
44 enum docklet_status {
|
|
45 online,
|
|
46 away,
|
|
47 away_pending,
|
|
48 connecting,
|
|
49 offline
|
|
50 };
|
|
51
|
|
52 /* functions */
|
|
53 static void docklet_create();
|
|
54
|
|
55 /* globals */
|
3513
|
56 static EggTrayIcon *docklet = NULL;
|
3510
|
57 static GtkWidget *icon;
|
|
58 static enum docklet_status status;
|
|
59
|
|
60 static void docklet_embedded(GtkWidget *widget, void *data) {
|
|
61 debug_printf("Docklet: embedded\n");
|
|
62 docklet_add();
|
|
63 }
|
|
64
|
|
65 static void docklet_destroyed(GtkWidget *widget, void *data) {
|
|
66 debug_printf("Docklet: destroyed\n");
|
|
67 docklet_remove();
|
|
68 docklet_create();
|
|
69 }
|
|
70
|
|
71 static void docklet_toggle() {
|
|
72 /* this looks bad, but we need to use (un)hide_buddy_list to allow buddy.c to
|
|
73 correctly hide/iconify depending on the docklet refcount, and to reposition
|
|
74 the blist for us when we unhide. no such constraint for the login window
|
|
75 because nothing else needs a unified way to hide/iconify it. otherwise I'd
|
|
76 make a function and use it for both. */
|
|
77 if (connections) {
|
|
78 if (GTK_WIDGET_VISIBLE(blist)) {
|
|
79 if (DOCKLET_WINDOW_ICONIFIED(blist)) {
|
|
80 unhide_buddy_list();
|
|
81 } else {
|
|
82 hide_buddy_list();
|
|
83 }
|
|
84 } else {
|
|
85 unhide_buddy_list();
|
|
86 }
|
|
87 } else {
|
|
88 if (GTK_WIDGET_VISIBLE(mainwindow)) {
|
|
89 if (DOCKLET_WINDOW_ICONIFIED(mainwindow)) {
|
|
90 gtk_window_present(GTK_WINDOW(mainwindow));
|
|
91 } else {
|
|
92 gtk_widget_hide(mainwindow);
|
|
93 }
|
|
94 } else {
|
|
95 gtk_window_present(GTK_WINDOW(mainwindow));
|
|
96 }
|
|
97 }
|
|
98 }
|
|
99
|
|
100 static void docklet_menu(GdkEventButton *event) {
|
3513
|
101 static GtkWidget *menu = NULL;
|
3512
|
102 GtkWidget *entry;
|
3510
|
103
|
|
104 if (menu) {
|
|
105 gtk_widget_destroy(menu);
|
|
106 }
|
|
107
|
|
108 menu = gtk_menu_new();
|
|
109
|
|
110 if (status == offline) {
|
|
111 entry = gtk_menu_item_new_with_label(_("Auto-login"));
|
|
112 g_signal_connect(GTK_WIDGET(entry), "activate", G_CALLBACK(auto_login), NULL);
|
|
113 gtk_menu_append(GTK_MENU(menu), entry);
|
|
114 } else {
|
|
115 if (status == online) {
|
|
116 GtkWidget *docklet_awaymenu;
|
|
117 GSList *awy = NULL;
|
|
118 struct away_message *a = NULL;
|
|
119
|
|
120 docklet_awaymenu = gtk_menu_new();
|
|
121 awy = away_messages;
|
|
122
|
|
123 while (awy) {
|
|
124 a = (struct away_message *)awy->data;
|
|
125
|
|
126 entry = gtk_menu_item_new_with_label(a->name);
|
|
127 g_signal_connect(GTK_WIDGET(entry), "activate", G_CALLBACK(do_away_message), a);
|
|
128 gtk_menu_append(GTK_MENU(docklet_awaymenu), entry);
|
|
129
|
|
130 awy = g_slist_next(awy);
|
|
131 }
|
|
132
|
|
133 entry = gtk_separator_menu_item_new();
|
|
134 gtk_menu_append(GTK_MENU(docklet_awaymenu), entry);
|
|
135
|
|
136 entry = gtk_menu_item_new_with_label(_("New..."));
|
|
137 g_signal_connect(GTK_WIDGET(entry), "activate", G_CALLBACK(create_away_mess), NULL);
|
|
138 gtk_menu_append(GTK_MENU(docklet_awaymenu), entry);
|
|
139
|
|
140 entry = gtk_menu_item_new_with_label(_("Away"));
|
3512
|
141 gtk_menu_item_set_submenu(GTK_MENU_ITEM(entry), docklet_awaymenu);
|
3510
|
142 gtk_menu_append(GTK_MENU(menu), entry);
|
|
143 } else {
|
|
144 entry = gtk_menu_item_new_with_label(_("Back"));
|
|
145 g_signal_connect(GTK_WIDGET(entry), "activate", G_CALLBACK(do_im_back), NULL);
|
|
146 gtk_menu_append(GTK_MENU(menu), entry);
|
|
147 }
|
|
148
|
|
149 entry = gtk_menu_item_new_with_label(_("Signoff"));
|
|
150 g_signal_connect(GTK_WIDGET(entry), "activate", G_CALLBACK(signoff_all), NULL);
|
|
151 gtk_menu_append(GTK_MENU(menu), entry);
|
|
152 }
|
|
153
|
|
154 entry = gtk_separator_menu_item_new();
|
|
155 gtk_menu_append(GTK_MENU(menu), entry);
|
|
156
|
|
157 entry = gtk_menu_item_new_with_label(_("Accounts"));
|
|
158 g_signal_connect(GTK_WIDGET(entry), "activate", G_CALLBACK(account_editor), NULL);
|
|
159 gtk_menu_append(GTK_MENU(menu), entry);
|
|
160
|
|
161 entry = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL);
|
|
162 g_signal_connect(GTK_WIDGET(entry), "activate", G_CALLBACK(show_prefs), NULL);
|
|
163 gtk_menu_append(GTK_MENU(menu), entry);
|
|
164
|
|
165 entry = gtk_menu_item_new_with_label(_("Plugins"));
|
|
166 g_signal_connect(GTK_WIDGET(entry), "activate", G_CALLBACK(show_plugins), NULL);
|
|
167 gtk_menu_append(GTK_MENU(menu), entry);
|
|
168
|
|
169 entry = gtk_separator_menu_item_new();
|
|
170 gtk_menu_append(GTK_MENU(menu), entry);
|
|
171
|
|
172 entry = gtk_menu_item_new_with_label(_("About"));
|
|
173 g_signal_connect(GTK_WIDGET(entry), "activate", G_CALLBACK(show_about), NULL);
|
|
174 gtk_menu_append(GTK_MENU(menu), entry);
|
|
175
|
|
176 entry = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL);
|
|
177 g_signal_connect(GTK_WIDGET(entry), "activate", G_CALLBACK(do_quit), NULL);
|
|
178 gtk_menu_append(GTK_MENU(menu), entry);
|
|
179
|
|
180 gtk_widget_show_all(menu);
|
|
181 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
|
|
182 }
|
|
183
|
|
184 static void docklet_clicked(GtkWidget *button, GdkEventButton *event, void *data) {
|
|
185 switch (event->button) {
|
|
186 case 1:
|
|
187 docklet_toggle();
|
|
188 break;
|
|
189 case 2:
|
|
190 break;
|
|
191 case 3:
|
|
192 docklet_menu(event);
|
|
193 break;
|
|
194 }
|
|
195 }
|
|
196
|
|
197 static void docklet_update_icon() {
|
|
198 gchar *filename;
|
|
199 GdkPixbuf *unscaled;
|
|
200
|
|
201 switch (status) {
|
|
202 case online:
|
|
203 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "online.png", NULL);
|
|
204 break;
|
|
205 case away:
|
|
206 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "away.png", NULL);
|
|
207 break;
|
|
208 case away_pending:
|
|
209 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "msgpend.png", NULL);
|
|
210 break;
|
|
211 case connecting:
|
|
212 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "connecting.png", NULL);
|
|
213 break;
|
|
214 case offline:
|
|
215 filename = g_build_filename(DATADIR, "pixmaps", "gaim", "offline.png", NULL);
|
|
216 }
|
|
217
|
|
218 unscaled = gdk_pixbuf_new_from_file(filename, NULL);
|
|
219
|
|
220 if (unscaled) {
|
|
221 GdkPixbuf *scaled;
|
|
222
|
|
223 scaled = gdk_pixbuf_scale_simple(unscaled, 24, 24, GDK_INTERP_BILINEAR);
|
|
224 gtk_image_set_from_pixbuf(GTK_IMAGE(icon), scaled);
|
|
225 g_object_unref(unscaled);
|
|
226 g_object_unref(scaled);
|
|
227
|
|
228 debug_printf("Docklet: updated icon to %s\n",filename);
|
|
229 } else {
|
|
230 debug_printf("Docklet: failed to load icon from %s\n",filename);
|
|
231 }
|
|
232
|
|
233 g_free(filename);
|
|
234 }
|
|
235
|
|
236 static void docklet_update_status() {
|
|
237 enum docklet_status oldstatus;
|
|
238
|
|
239 oldstatus = status;
|
|
240
|
|
241 if (connections) {
|
|
242 if (imaway) {
|
|
243 if (message_queue) {
|
|
244 status = away_pending;
|
|
245 } else {
|
|
246 status = away;
|
|
247 }
|
|
248 } else {
|
|
249 status = online;
|
|
250 }
|
|
251 } else {
|
|
252 status = offline;
|
|
253 }
|
|
254
|
|
255 if (status != oldstatus) {
|
|
256 docklet_update_icon();
|
|
257 }
|
|
258 }
|
|
259
|
|
260 static void docklet_create() {
|
|
261 GtkWidget *box;
|
|
262
|
|
263 /* is this necessary/wise? */
|
|
264 if (docklet) {
|
|
265 g_signal_handlers_disconnect_by_func(GTK_WIDGET(docklet), G_CALLBACK(docklet_destroyed), NULL);
|
|
266 gtk_widget_destroy(GTK_WIDGET(docklet));
|
|
267 debug_printf("Docklet: freed\n");
|
|
268 }
|
|
269
|
|
270 docklet = egg_tray_icon_new("Gaim");
|
|
271 box = gtk_event_box_new();
|
|
272 icon = gtk_image_new();
|
|
273
|
|
274 g_signal_connect(GTK_WIDGET(docklet), "embedded", G_CALLBACK(docklet_embedded), NULL);
|
|
275 g_signal_connect(GTK_WIDGET(docklet), "destroy", G_CALLBACK(docklet_destroyed), NULL);
|
|
276 g_signal_connect(box, "button-press-event", G_CALLBACK(docklet_clicked), NULL);
|
|
277
|
|
278 gtk_container_add(GTK_CONTAINER(box), icon);
|
|
279 gtk_container_add(GTK_CONTAINER(docklet), box);
|
|
280 gtk_widget_show_all(GTK_WIDGET(docklet));
|
|
281
|
|
282 docklet_update_status();
|
|
283 docklet_update_icon();
|
|
284
|
|
285 debug_printf("Docklet: created\n");
|
|
286 }
|
|
287
|
|
288 static void gaim_signon(struct gaim_connection *gc, void *data) {
|
|
289 docklet_update_status();
|
|
290 }
|
|
291
|
|
292 static void gaim_signoff(struct gaim_connection *gc, void *data) {
|
|
293 docklet_update_status();
|
|
294 }
|
|
295
|
|
296 static void gaim_connecting(struct aim_user *user, void *data) {
|
|
297 docklet_update_status();
|
|
298 }
|
|
299
|
|
300 static void gaim_away(struct gaim_connection *gc, char *state, char *message, void *data) {
|
|
301 /* we only support global away. this is the way it is, ok? */
|
|
302 docklet_update_status();
|
|
303 }
|
|
304
|
|
305 static void gaim_im_recv(struct gaim_connection *gc, char **who, char **what, void *data) {
|
|
306 /* if message queuing while away is enabled, this event could be the first
|
|
307 message so we need to see if the status (and hence icon) needs changing */
|
|
308 docklet_update_status();
|
|
309 }
|
|
310
|
|
311 static void gaim_buddy_signon(struct gaim_connection *gc, char *who, void *data) {
|
|
312 }
|
|
313
|
|
314 static void gaim_buddy_signoff(struct gaim_connection *gc, char *who, void *data) {
|
|
315 }
|
|
316
|
|
317 static void gaim_buddy_away(struct gaim_connection *gc, char *who, void *data) {
|
|
318 }
|
|
319
|
|
320 static void gaim_buddy_back(struct gaim_connection *gc, char *who, void *data) {
|
|
321 }
|
|
322
|
|
323 static void gaim_new_conversation(char *who, void *data) {
|
|
324 }
|
|
325
|
|
326 char *gaim_plugin_init(GModule *handle) {
|
|
327 docklet_create();
|
|
328
|
|
329 gaim_signal_connect(handle, event_signon, gaim_signon, NULL);
|
|
330 gaim_signal_connect(handle, event_signoff, gaim_signoff, NULL);
|
|
331 gaim_signal_connect(handle, event_connecting, gaim_connecting, NULL);
|
|
332 gaim_signal_connect(handle, event_away, gaim_away, NULL);
|
|
333 gaim_signal_connect(handle, event_im_recv, gaim_im_recv, NULL);
|
|
334 gaim_signal_connect(handle, event_buddy_signon, gaim_buddy_signon, NULL);
|
|
335 gaim_signal_connect(handle, event_buddy_signoff, gaim_buddy_signoff, NULL);
|
|
336 gaim_signal_connect(handle, event_buddy_away, gaim_buddy_away, NULL);
|
|
337 gaim_signal_connect(handle, event_buddy_back, gaim_buddy_back, NULL);
|
|
338 gaim_signal_connect(handle, event_new_conversation, gaim_new_conversation, NULL);
|
|
339
|
|
340 return NULL;
|
|
341 }
|
|
342
|
|
343 void gaim_plugin_remove() {
|
|
344 if (GTK_WIDGET_VISIBLE(docklet)) {
|
|
345 docklet_remove();
|
|
346 }
|
|
347
|
|
348 g_signal_handlers_disconnect_by_func(GTK_WIDGET(docklet), G_CALLBACK(docklet_destroyed), NULL);
|
|
349 gtk_widget_destroy(GTK_WIDGET(docklet));
|
|
350
|
|
351 debug_printf("Docklet: removed\n");
|
|
352 }
|
|
353
|
|
354 const char *name() {
|
|
355 return _("System Tray Docklet");
|
|
356 }
|
|
357
|
|
358 const char *description() {
|
|
359 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.");
|
|
360 }
|