| |
1 /* |
| |
2 * Purple buddy notification plugin. |
| |
3 * |
| |
4 * Copyright (C) 2000-2001, Eric Warmenhoven (original code) |
| |
5 * Copyright (C) 2002, Etan Reisner <deryni@eden.rutgers.edu> (rewritten code) |
| |
6 * Copyright (C) 2003, Christian Hammond (update for changed API) |
| |
7 * Copyright (C) 2003, Brian Tarricone <bjt23@cornell.edu> (mostly rewritten) |
| |
8 * Copyright (C) 2003, Mark Doliner (minor cleanup) |
| |
9 * Copyright (C) 2003, Etan Reisner (largely rewritten again) |
| |
10 * |
| |
11 * This program is free software; you can redistribute it and/or modify |
| |
12 * it under the terms of the GNU General Public License as published by |
| |
13 * the Free Software Foundation; either version 2 of the License, or |
| |
14 * (at your option) any later version. |
| |
15 * |
| |
16 * This program is distributed in the hope that it will be useful, |
| |
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| |
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| |
19 * GNU General Public License for more details. |
| |
20 * |
| |
21 * You should have received a copy of the GNU General Public License |
| |
22 * along with this program; if not, write to the Free Software |
| |
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA |
| |
24 * |
| |
25 */ |
| |
26 |
| |
27 /* TODO |
| |
28 * 22:22:17 <seanegan> deryni: speaking of notify.c... you know what else |
| |
29 * might be a neat feature? |
| |
30 * 22:22:30 <seanegan> Changing the window icon. |
| |
31 * 22:23:25 <deryni> seanegan: To what? |
| |
32 * 22:23:42 <seanegan> deryni: I dunno. Flash it between the regular icon and |
| |
33 * blank or something. |
| |
34 * 22:23:53 <deryni> Also I think purple might re-set that sort of frequently, |
| |
35 * but I'd have to look. |
| |
36 * 22:25:16 <seanegan> deryni: I keep my conversations in one workspace and am |
| |
37 * frequently in an another, and the icon flashing in the pager would be a |
| |
38 * neat visual clue. |
| |
39 */ |
| |
40 |
| |
41 /* |
| |
42 * From Etan, 2002: |
| |
43 * -Added config dialog |
| |
44 * -Added control over notification method |
| |
45 * -Added control over when to release notification |
| |
46 * |
| |
47 * -Added option to get notification for chats also |
| |
48 * -Cleaned up code |
| |
49 * -Added option to notify on click as it's own option |
| |
50 * rather then as what happens when on focus isn't clicked |
| |
51 * -Added apply button to change the denotification methods for |
| |
52 * open conversation windows |
| |
53 * -Fixed apply to conversations, count now keeps count across applies |
| |
54 * -Fixed(?) memory leak, and in the process fixed some stupidities |
| |
55 * -Hit enter when done editing the title string entry box to save it |
| |
56 * |
| |
57 * Thanks to Carles Pina i Estany <carles@pinux.info> |
| |
58 * for count of new messages option |
| |
59 * |
| |
60 * From Brian, 20 July 2003: |
| |
61 * -Use new xml prefs |
| |
62 * -Better handling of notification states tracking |
| |
63 * -Better pref change handling |
| |
64 * -Fixed a possible memleak and possible crash (rare) |
| |
65 * -Use gtk_window_get_title() rather than gtkwin->title |
| |
66 * -Other random fixes and cleanups |
| |
67 * |
| |
68 * Etan again, 12 August 2003: |
| |
69 * -Better use of the new xml prefs |
| |
70 * -Removed all bitmask stuff |
| |
71 * -Even better pref change handling |
| |
72 * -Removed unnecessary functions |
| |
73 * -Reworking of notification/unnotification stuff |
| |
74 * -Header file include cleanup |
| |
75 * -General code cleanup |
| |
76 * |
| |
77 * Etan yet again, 04 April 2004: |
| |
78 * -Re-added Urgent option |
| |
79 * -Re-added unnotify on focus option (still needs work, as it will only |
| |
80 * react to focus-in events when the entry or history widgets are focused) |
| |
81 * |
| |
82 * Sean, 08 January, 2005: |
| |
83 * -Added Raise option, formally in Purple proper |
| |
84 */ |
| |
85 |
| |
86 #include <glib/gi18n-lib.h> |
| |
87 |
| |
88 #include <purple.h> |
| |
89 |
| |
90 #include <pidgin.h> |
| |
91 |
| |
92 #define NOTIFY_PLUGIN_ID "gtk-x11-notify" |
| |
93 |
| |
94 static PurplePlugin *my_plugin = NULL; |
| |
95 #ifdef HAVE_X11 |
| |
96 static GdkAtom _Cardinal = GDK_NONE; |
| |
97 static GdkAtom _PurpleUnseenCount = GDK_NONE; |
| |
98 #endif |
| |
99 |
| |
100 /* notification set/unset */ |
| |
101 static int notify(PurpleConversation *conv, gboolean increment); |
| |
102 static void notify_win(PidginConvWindow *purplewin, PurpleConversation *conv); |
| |
103 static void unnotify(PurpleConversation *conv, gboolean reset); |
| |
104 static int unnotify_cb(GtkWidget *widget, gpointer data, |
| |
105 PurpleConversation *conv); |
| |
106 |
| |
107 /* gtk widget callbacks for prefs panel */ |
| |
108 static void type_toggle_cb(GtkWidget *widget, gpointer data); |
| |
109 static void method_toggle_cb(GtkWidget *widget, gpointer data); |
| |
110 static void notify_toggle_cb(GtkWidget *widget, gpointer data); |
| |
111 static gboolean options_entry_cb(GtkWidget *widget, GdkEventFocus *event, |
| |
112 gpointer data); |
| |
113 static void apply_method(void); |
| |
114 static void apply_notify(void); |
| |
115 |
| |
116 /* string function */ |
| |
117 static void handle_string(PidginConvWindow *purplewin); |
| |
118 |
| |
119 /* count_title function */ |
| |
120 static void handle_count_title(PidginConvWindow *purplewin); |
| |
121 |
| |
122 /* count_xprop function */ |
| |
123 static void handle_count_xprop(PidginConvWindow *purplewin); |
| |
124 |
| |
125 /* urgent function */ |
| |
126 static void handle_urgent(PidginConvWindow *purplewin, gboolean set); |
| |
127 |
| |
128 /* present function */ |
| |
129 static void handle_present(PurpleConversation *conv); |
| |
130 |
| |
131 /****************************************/ |
| |
132 /* Begin doing stuff below this line... */ |
| |
133 /****************************************/ |
| |
134 static guint |
| |
135 count_messages(PidginConvWindow *purplewin) |
| |
136 { |
| |
137 guint count = 0; |
| |
138 GList *convs = NULL, *l; |
| |
139 |
| |
140 for (convs = purplewin->gtkconvs; convs != NULL; convs = convs->next) { |
| |
141 PidginConversation *conv = convs->data; |
| |
142 for (l = conv->convs; l != NULL; l = l->next) { |
| |
143 count += GPOINTER_TO_INT(g_object_get_data(G_OBJECT(l->data), "notify-message-count")); |
| |
144 } |
| |
145 } |
| |
146 |
| |
147 return count; |
| |
148 } |
| |
149 |
| |
150 static int |
| |
151 notify(PurpleConversation *conv, gboolean increment) |
| |
152 { |
| |
153 gint count; |
| |
154 gboolean has_focus; |
| |
155 PidginConvWindow *purplewin = NULL; |
| |
156 |
| |
157 if (conv == NULL || PIDGIN_CONVERSATION(conv) == NULL) |
| |
158 return 0; |
| |
159 |
| |
160 /* We want to remove the notifications, but not reset the counter */ |
| |
161 unnotify(conv, FALSE); |
| |
162 |
| |
163 purplewin = PIDGIN_CONVERSATION(conv)->win; |
| |
164 |
| |
165 /* If we aren't doing notifications for this type of conversation, return */ |
| |
166 if ((PURPLE_IS_IM_CONVERSATION(conv) && |
| |
167 !purple_prefs_get_bool("/plugins/gtk/X11/notify/type_im")) || |
| |
168 (PURPLE_IS_CHAT_CONVERSATION(conv) && |
| |
169 !purple_prefs_get_bool("/plugins/gtk/X11/notify/type_chat"))) |
| |
170 return 0; |
| |
171 |
| |
172 g_object_get(G_OBJECT(purplewin->window), |
| |
173 "has-toplevel-focus", &has_focus, NULL); |
| |
174 |
| |
175 if (purple_prefs_get_bool("/plugins/gtk/X11/notify/type_focused") || |
| |
176 !has_focus) { |
| |
177 if (increment) { |
| |
178 count = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv), "notify-message-count")); |
| |
179 count++; |
| |
180 g_object_set_data(G_OBJECT(conv), "notify-message-count", GINT_TO_POINTER(count)); |
| |
181 } |
| |
182 |
| |
183 notify_win(purplewin, conv); |
| |
184 } |
| |
185 |
| |
186 return 0; |
| |
187 } |
| |
188 |
| |
189 static void |
| |
190 notify_win(PidginConvWindow *purplewin, PurpleConversation *conv) |
| |
191 { |
| |
192 if (count_messages(purplewin) <= 0) |
| |
193 return; |
| |
194 |
| |
195 if (purple_prefs_get_bool("/plugins/gtk/X11/notify/method_count")) |
| |
196 handle_count_title(purplewin); |
| |
197 if (purple_prefs_get_bool("/plugins/gtk/X11/notify/method_count_xprop")) |
| |
198 handle_count_xprop(purplewin); |
| |
199 if (purple_prefs_get_bool("/plugins/gtk/X11/notify/method_string")) |
| |
200 handle_string(purplewin); |
| |
201 if (purple_prefs_get_bool("/plugins/gtk/X11/notify/method_urgent")) |
| |
202 handle_urgent(purplewin, TRUE); |
| |
203 if (purple_prefs_get_bool("/plugins/gtk/X11/notify/method_present")) |
| |
204 handle_present(conv); |
| |
205 } |
| |
206 |
| |
207 static void |
| |
208 unnotify(PurpleConversation *conv, gboolean reset) |
| |
209 { |
| |
210 PurpleConversation *active_conv = NULL; |
| |
211 PidginConversationWindow *purplewin = NULL; |
| |
212 GtkWidget *win; |
| |
213 |
| |
214 g_return_if_fail(conv != NULL); |
| |
215 if (PIDGIN_CONVERSATION(conv) == NULL) |
| |
216 return; |
| |
217 |
| |
218 win = gtk_widget_get_toplevel(PIDGIN_CONVERSATION(conv)->tab_cont); |
| |
219 purplewin = PIDGIN_CONVERSATION_WINDOW(win); |
| |
220 |
| |
221 activate_conv = pidgin_conversation_window_get_selected(purplewin); |
| |
222 |
| |
223 /* reset the conversation window title */ |
| |
224 purple_conversation_autoset_title(active_conv); |
| |
225 |
| |
226 if (reset) { |
| |
227 /* Only need to actually remove the urgent hinting here, since |
| |
228 * removing it just to have it re-added in re-notify is an |
| |
229 * unnecessary couple extra RTs to the server */ |
| |
230 handle_urgent(purplewin, FALSE); |
| |
231 g_object_set_data(G_OBJECT(conv), "notify-message-count", GINT_TO_POINTER(0)); |
| |
232 /* Same logic as for the urgent hint, xprops are also a RT. |
| |
233 * This needs to go here so that it gets the updated message |
| |
234 * count. */ |
| |
235 handle_count_xprop(purplewin); |
| |
236 } |
| |
237 |
| |
238 return; |
| |
239 } |
| |
240 |
| |
241 static int |
| |
242 unnotify_cb(GtkWidget *widget, gpointer data, PurpleConversation *conv) |
| |
243 { |
| |
244 if (GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv), "notify-message-count")) != 0) |
| |
245 unnotify(conv, TRUE); |
| |
246 |
| |
247 return 0; |
| |
248 } |
| |
249 |
| |
250 static gboolean |
| |
251 message_displayed_cb(PurpleConversation *conv, PurpleMessage *msg, gpointer _unused) |
| |
252 { |
| |
253 PurpleMessageFlags flags = purple_message_get_flags(msg); |
| |
254 |
| |
255 /* Ignore anything that's not a received message or a system message */ |
| |
256 if (!(flags & (PURPLE_MESSAGE_RECV|PURPLE_MESSAGE_SYSTEM))) |
| |
257 return FALSE; |
| |
258 /* Don't highlight for delayed messages */ |
| |
259 if ((flags & PURPLE_MESSAGE_RECV) && (flags & PURPLE_MESSAGE_DELAYED)) |
| |
260 return FALSE; |
| |
261 /* Check whether to highlight for system message for either chat or IM */ |
| |
262 if (flags & PURPLE_MESSAGE_SYSTEM) { |
| |
263 if (PURPLE_IS_CHAT_CONVERSATION(conv)) { |
| |
264 if (!purple_prefs_get_bool("/plugins/gtk/X11/notify/type_chat_sys")) |
| |
265 return FALSE; |
| |
266 } else if (PURPLE_IS_IM_CONVERSATION(conv)) { |
| |
267 if (!purple_prefs_get_bool("/plugins/gtk/X11/notify/type_im_sys")) |
| |
268 return FALSE; |
| |
269 } else { |
| |
270 /* System message not from chat or IM, ignore */ |
| |
271 return FALSE; |
| |
272 } |
| |
273 } |
| |
274 |
| |
275 /* If it's a chat, check if we should only highlight when nick is mentioned */ |
| |
276 if ((PURPLE_IS_CHAT_CONVERSATION(conv) && |
| |
277 purple_prefs_get_bool("/plugins/gtk/X11/notify/type_chat_nick") && |
| |
278 !(flags & PURPLE_MESSAGE_NICK))) |
| |
279 return FALSE; |
| |
280 |
| |
281 /* Nothing speaks against notifying, do so */ |
| |
282 notify(conv, TRUE); |
| |
283 |
| |
284 return FALSE; |
| |
285 } |
| |
286 |
| |
287 static void |
| |
288 im_sent_im(PurpleAccount *account, PurpleMessage *msg, gpointer _unused) |
| |
289 { |
| |
290 PurpleIMConversation *im = NULL; |
| |
291 |
| |
292 if (purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_send")) { |
| |
293 im = purple_conversations_find_im_with_account( |
| |
294 purple_message_get_recipient(msg), account); |
| |
295 unnotify(PURPLE_CONVERSATION(im), TRUE); |
| |
296 } |
| |
297 } |
| |
298 |
| |
299 static void |
| |
300 chat_sent_im(PurpleAccount *account, PurpleMessage *msg, int id) |
| |
301 { |
| |
302 PurpleChatConversation *chat = NULL; |
| |
303 |
| |
304 if (purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_send")) { |
| |
305 chat = purple_conversations_find_chat(purple_account_get_connection(account), id); |
| |
306 unnotify(PURPLE_CONVERSATION(chat), TRUE); |
| |
307 } |
| |
308 } |
| |
309 |
| |
310 static int |
| |
311 attach_signals(PurpleConversation *conv) |
| |
312 { |
| |
313 PidginConversation *gtkconv = NULL; |
| |
314 GSList *webview_ids = NULL, *entry_ids = NULL; |
| |
315 guint id; |
| |
316 |
| |
317 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
318 if (!gtkconv) { |
| |
319 purple_debug_misc("notify", "Failed to find gtkconv\n"); |
| |
320 return 0; |
| |
321 } |
| |
322 |
| |
323 if (purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_focus")) { |
| |
324 /* TODO should really find a way to make this work no matter |
| |
325 * where the focus is inside the conv window, without having |
| |
326 * to bind to focus-in-event on the g(d|t)kwindow */ |
| |
327 /* try setting the signal on the focus-in-event for |
| |
328 * gtkwin->notebook->container? */ |
| |
329 id = g_signal_connect(G_OBJECT(gtkconv->entry), "focus-in-event", |
| |
330 G_CALLBACK(unnotify_cb), conv); |
| |
331 entry_ids = g_slist_append(entry_ids, GUINT_TO_POINTER(id)); |
| |
332 |
| |
333 id = g_signal_connect(G_OBJECT(gtkconv->webview), "focus-in-event", |
| |
334 G_CALLBACK(unnotify_cb), conv); |
| |
335 webview_ids = g_slist_append(webview_ids, GUINT_TO_POINTER(id)); |
| |
336 } |
| |
337 |
| |
338 if (purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_click")) { |
| |
339 /* TODO similarly should really find a way to allow for |
| |
340 * clicking in other places of the window */ |
| |
341 id = g_signal_connect(G_OBJECT(gtkconv->entry), "button-press-event", |
| |
342 G_CALLBACK(unnotify_cb), conv); |
| |
343 entry_ids = g_slist_append(entry_ids, GUINT_TO_POINTER(id)); |
| |
344 |
| |
345 id = g_signal_connect(G_OBJECT(gtkconv->webview), "button-press-event", |
| |
346 G_CALLBACK(unnotify_cb), conv); |
| |
347 webview_ids = g_slist_append(webview_ids, GUINT_TO_POINTER(id)); |
| |
348 } |
| |
349 |
| |
350 if (purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_type")) { |
| |
351 id = g_signal_connect(G_OBJECT(gtkconv->entry), "key-press-event", |
| |
352 G_CALLBACK(unnotify_cb), conv); |
| |
353 entry_ids = g_slist_append(entry_ids, GUINT_TO_POINTER(id)); |
| |
354 } |
| |
355 |
| |
356 g_object_set_data(G_OBJECT(conv), "notify-webview-signals", webview_ids); |
| |
357 g_object_set_data(G_OBJECT(conv), "notify-entry-signals", entry_ids); |
| |
358 |
| |
359 return 0; |
| |
360 } |
| |
361 |
| |
362 static void |
| |
363 detach_signals(PurpleConversation *conv) |
| |
364 { |
| |
365 PidginConversation *gtkconv = NULL; |
| |
366 GSList *ids = NULL, *l; |
| |
367 |
| |
368 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
369 if (!gtkconv) |
| |
370 return; |
| |
371 |
| |
372 ids = g_object_get_data(G_OBJECT(conv), "notify-webview-signals"); |
| |
373 for (l = ids; l != NULL; l = l->next) |
| |
374 g_signal_handler_disconnect(gtkconv->webview, GPOINTER_TO_INT(l->data)); |
| |
375 g_slist_free(ids); |
| |
376 |
| |
377 ids = g_object_get_data(G_OBJECT(conv), "notify-entry-signals"); |
| |
378 for (l = ids; l != NULL; l = l->next) |
| |
379 g_signal_handler_disconnect(gtkconv->entry, GPOINTER_TO_INT(l->data)); |
| |
380 g_slist_free(ids); |
| |
381 |
| |
382 g_object_set_data(G_OBJECT(conv), "notify-message-count", GINT_TO_POINTER(0)); |
| |
383 |
| |
384 g_object_set_data(G_OBJECT(conv), "notify-webview-signals", NULL); |
| |
385 g_object_set_data(G_OBJECT(conv), "notify-entry-signals", NULL); |
| |
386 } |
| |
387 |
| |
388 static void |
| |
389 conv_created(PurpleConversation *conv) |
| |
390 { |
| |
391 g_object_set_data(G_OBJECT(conv), "notify-message-count", |
| |
392 GINT_TO_POINTER(0)); |
| |
393 |
| |
394 /* always attach the signals, notify() will take care of conversation |
| |
395 * type checking */ |
| |
396 attach_signals(conv); |
| |
397 } |
| |
398 |
| |
399 static void |
| |
400 conv_switched(PurpleConversation *conv) |
| |
401 { |
| |
402 #if 0 |
| |
403 PidginConvWindow *purplewin = purple_conversation_get_window(new_conv); |
| |
404 #endif |
| |
405 |
| |
406 /* |
| |
407 * If the conversation was switched, then make sure we re-notify |
| |
408 * because Purple will have overwritten our custom window title. |
| |
409 */ |
| |
410 notify(conv, FALSE); |
| |
411 |
| |
412 #if 0 |
| |
413 printf("conv_switched - %p - %p\n", old_conv, new_conv); |
| |
414 printf("count - %d\n", count_messages(purplewin)); |
| |
415 if (purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_switch")) |
| |
416 unnotify(new_conv, FALSE); |
| |
417 else { |
| |
418 /* if we don't have notification on the window then we don't want to |
| |
419 * re-notify it */ |
| |
420 if (count_messages(purplewin)) |
| |
421 notify_win(purplewin); |
| |
422 } |
| |
423 #endif |
| |
424 } |
| |
425 |
| |
426 static void |
| |
427 deleting_conv(PurpleConversation *conv) |
| |
428 { |
| |
429 PidginConvWindow *purplewin = NULL; |
| |
430 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); |
| |
431 |
| |
432 if (gtkconv == NULL) |
| |
433 return; |
| |
434 |
| |
435 detach_signals(conv); |
| |
436 |
| |
437 purplewin = gtkconv->win; |
| |
438 |
| |
439 handle_urgent(purplewin, FALSE); |
| |
440 g_object_set_data(G_OBJECT(conv), "notify-message-count", GINT_TO_POINTER(0)); |
| |
441 |
| |
442 return; |
| |
443 |
| |
444 #if 0 |
| |
445 /* i think this line crashes */ |
| |
446 if (count_messages(purplewin)) |
| |
447 notify_win(purplewin); |
| |
448 #endif |
| |
449 } |
| |
450 |
| |
451 static void |
| |
452 handle_string(PidginConvWindow *purplewin) |
| |
453 { |
| |
454 GtkWindow *window = NULL; |
| |
455 gchar newtitle[256]; |
| |
456 |
| |
457 g_return_if_fail(purplewin != NULL); |
| |
458 |
| |
459 window = GTK_WINDOW(purplewin->window); |
| |
460 g_return_if_fail(window != NULL); |
| |
461 |
| |
462 g_snprintf(newtitle, sizeof(newtitle), "%s%s", |
| |
463 purple_prefs_get_string("/plugins/gtk/X11/notify/title_string"), |
| |
464 gtk_window_get_title(window)); |
| |
465 gtk_window_set_title(window, newtitle); |
| |
466 } |
| |
467 |
| |
468 static void |
| |
469 handle_count_title(PidginConvWindow *purplewin) |
| |
470 { |
| |
471 GtkWindow *window; |
| |
472 char newtitle[256]; |
| |
473 |
| |
474 g_return_if_fail(purplewin != NULL); |
| |
475 |
| |
476 window = GTK_WINDOW(purplewin->window); |
| |
477 g_return_if_fail(window != NULL); |
| |
478 |
| |
479 g_snprintf(newtitle, sizeof(newtitle), "[%d] %s", |
| |
480 count_messages(purplewin), gtk_window_get_title(window)); |
| |
481 gtk_window_set_title(window, newtitle); |
| |
482 } |
| |
483 |
| |
484 static void |
| |
485 handle_count_xprop(PidginConvWindow *purplewin) |
| |
486 { |
| |
487 #ifdef HAVE_X11 |
| |
488 guint count; |
| |
489 GtkWidget *window; |
| |
490 GdkWindow *gdkwin; |
| |
491 |
| |
492 window = purplewin->window; |
| |
493 g_return_if_fail(window != NULL); |
| |
494 |
| |
495 if (_PurpleUnseenCount == GDK_NONE) { |
| |
496 _PurpleUnseenCount = gdk_atom_intern("_PIDGIN_UNSEEN_COUNT", FALSE); |
| |
497 } |
| |
498 |
| |
499 if (_Cardinal == GDK_NONE) { |
| |
500 _Cardinal = gdk_atom_intern("CARDINAL", FALSE); |
| |
501 } |
| |
502 |
| |
503 count = count_messages(purplewin); |
| |
504 gdkwin = gtk_widget_get_window(window); |
| |
505 |
| |
506 gdk_property_change(gdkwin, _PurpleUnseenCount, _Cardinal, 32, |
| |
507 GDK_PROP_MODE_REPLACE, (guchar *) &count, 1); |
| |
508 #endif |
| |
509 } |
| |
510 |
| |
511 static void |
| |
512 handle_urgent(PidginConvWindow *purplewin, gboolean set) |
| |
513 { |
| |
514 g_return_if_fail(purplewin != NULL); |
| |
515 g_return_if_fail(purplewin->window != NULL); |
| |
516 |
| |
517 gtk_window_set_urgency_hint(GTK_WINDOW(purplewin->window), set); |
| |
518 } |
| |
519 |
| |
520 static void |
| |
521 handle_present(PurpleConversation *conv) |
| |
522 { |
| |
523 if (pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv))) |
| |
524 return; |
| |
525 |
| |
526 purple_conversation_present(conv); |
| |
527 } |
| |
528 |
| |
529 static void |
| |
530 type_toggle_cb(GtkWidget *widget, gpointer data) |
| |
531 { |
| |
532 gboolean on = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); |
| |
533 gchar pref[256]; |
| |
534 |
| |
535 g_snprintf(pref, sizeof(pref), "/plugins/gtk/X11/notify/%s", |
| |
536 (char *)data); |
| |
537 |
| |
538 purple_prefs_set_bool(pref, on); |
| |
539 } |
| |
540 |
| |
541 static void |
| |
542 method_toggle_cb(GtkWidget *widget, gpointer data) |
| |
543 { |
| |
544 gboolean on = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); |
| |
545 gchar pref[256]; |
| |
546 |
| |
547 g_snprintf(pref, sizeof(pref), "/plugins/gtk/X11/notify/%s", |
| |
548 (char *)data); |
| |
549 |
| |
550 purple_prefs_set_bool(pref, on); |
| |
551 |
| |
552 if (purple_strequal(data, "method_string")) { |
| |
553 GtkWidget *entry = g_object_get_data(G_OBJECT(widget), "title-entry"); |
| |
554 gtk_widget_set_sensitive(entry, on); |
| |
555 |
| |
556 purple_prefs_set_string("/plugins/gtk/X11/notify/title_string", |
| |
557 gtk_editable_get_text(GTK_EDITABLE(entry))); |
| |
558 } |
| |
559 |
| |
560 apply_method(); |
| |
561 } |
| |
562 |
| |
563 static void |
| |
564 notify_toggle_cb(GtkWidget *widget, gpointer data) |
| |
565 { |
| |
566 gboolean on = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); |
| |
567 gchar pref[256]; |
| |
568 |
| |
569 g_snprintf(pref, sizeof(pref), "/plugins/gtk/X11/notify/%s", |
| |
570 (char *)data); |
| |
571 |
| |
572 purple_prefs_set_bool(pref, on); |
| |
573 |
| |
574 apply_notify(); |
| |
575 } |
| |
576 |
| |
577 static gboolean |
| |
578 options_entry_cb(GtkWidget *widget, GdkEventFocus *evt, gpointer data) |
| |
579 { |
| |
580 if (data == NULL) |
| |
581 return FALSE; |
| |
582 |
| |
583 if (purple_strequal(data, "method_string")) { |
| |
584 purple_prefs_set_string("/plugins/gtk/X11/notify/title_string", |
| |
585 gtk_editable_get_text(GTK_EDITABLE(widget))); |
| |
586 } |
| |
587 |
| |
588 apply_method(); |
| |
589 |
| |
590 return FALSE; |
| |
591 } |
| |
592 |
| |
593 static void |
| |
594 apply_method() |
| |
595 { |
| |
596 GList *convs; |
| |
597 |
| |
598 for (convs = purple_conversations_get_all(); convs != NULL; |
| |
599 convs = convs->next) { |
| |
600 PurpleConversation *conv = (PurpleConversation *)convs->data; |
| |
601 |
| |
602 /* remove notifications */ |
| |
603 unnotify(conv, FALSE); |
| |
604 |
| |
605 if (GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv), "notify-message-count")) != 0) |
| |
606 /* reattach appropriate notifications */ |
| |
607 notify(conv, FALSE); |
| |
608 } |
| |
609 } |
| |
610 |
| |
611 static void |
| |
612 apply_notify() |
| |
613 { |
| |
614 GList *convs = purple_conversations_get_all(); |
| |
615 |
| |
616 while (convs) { |
| |
617 PurpleConversation *conv = (PurpleConversation *)convs->data; |
| |
618 |
| |
619 /* detach signals */ |
| |
620 detach_signals(conv); |
| |
621 /* reattach appropriate signals */ |
| |
622 attach_signals(conv); |
| |
623 |
| |
624 convs = convs->next; |
| |
625 } |
| |
626 } |
| |
627 |
| |
628 static GtkWidget * |
| |
629 get_config_frame(PurplePlugin *plugin) |
| |
630 { |
| |
631 GtkWidget *ret = NULL, *frame = NULL; |
| |
632 GtkWidget *vbox = NULL, *hbox = NULL; |
| |
633 GtkWidget *toggle = NULL, *entry = NULL, *ref; |
| |
634 |
| |
635 ret = gtk_box_new(GTK_ORIENTATION_VERTICAL, 18); |
| |
636 gtk_container_set_border_width(GTK_CONTAINER (ret), 12); |
| |
637 |
| |
638 /*---------- "Notify For" ----------*/ |
| |
639 frame = pidgin_make_frame(ret, _("Notify For")); |
| |
640 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); |
| |
641 gtk_container_add(GTK_CONTAINER(frame), vbox); |
| |
642 |
| |
643 toggle = gtk_check_button_new_with_mnemonic(_("_IM windows")); |
| |
644 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
645 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
646 purple_prefs_get_bool("/plugins/gtk/X11/notify/type_im")); |
| |
647 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
648 G_CALLBACK(type_toggle_cb), "type_im"); |
| |
649 |
| |
650 ref = toggle; |
| |
651 toggle = gtk_check_button_new_with_mnemonic(_("\tS_ystem messages")); |
| |
652 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
653 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
654 purple_prefs_get_bool("/plugins/gtk/X11/notify/type_im_sys")); |
| |
655 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
656 G_CALLBACK(type_toggle_cb), "type_im_sys"); |
| |
657 g_object_bind_property(ref, "active", toggle, "sensitive", |
| |
658 G_BINDING_SYNC_CREATE); |
| |
659 |
| |
660 toggle = gtk_check_button_new_with_mnemonic(_("C_hat windows")); |
| |
661 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
662 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
663 purple_prefs_get_bool("/plugins/gtk/X11/notify/type_chat")); |
| |
664 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
665 G_CALLBACK(type_toggle_cb), "type_chat"); |
| |
666 |
| |
667 ref = toggle; |
| |
668 toggle = gtk_check_button_new_with_mnemonic(_("\t_Only when someone says your username")); |
| |
669 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
670 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
671 purple_prefs_get_bool("/plugins/gtk/X11/notify/type_chat_nick")); |
| |
672 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
673 G_CALLBACK(type_toggle_cb), "type_chat_nick"); |
| |
674 g_object_bind_property(ref, "active", toggle, "sensitive", |
| |
675 G_BINDING_SYNC_CREATE); |
| |
676 |
| |
677 toggle = gtk_check_button_new_with_mnemonic(_("\tS_ystem messages")); |
| |
678 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
679 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
680 purple_prefs_get_bool("/plugins/gtk/X11/notify/type_chat_sys")); |
| |
681 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
682 G_CALLBACK(type_toggle_cb), "type_chat_sys"); |
| |
683 g_object_bind_property(ref, "active", toggle, "sensitive", |
| |
684 G_BINDING_SYNC_CREATE); |
| |
685 |
| |
686 toggle = gtk_check_button_new_with_mnemonic(_("_Focused windows")); |
| |
687 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
688 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
689 purple_prefs_get_bool("/plugins/gtk/X11/notify/type_focused")); |
| |
690 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
691 G_CALLBACK(type_toggle_cb), "type_focused"); |
| |
692 |
| |
693 /*---------- "Notification Methods" ----------*/ |
| |
694 frame = pidgin_make_frame(ret, _("Notification Methods")); |
| |
695 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); |
| |
696 gtk_container_add(GTK_CONTAINER(frame), vbox); |
| |
697 |
| |
698 /* String method button */ |
| |
699 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 18); |
| |
700 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); |
| |
701 toggle = gtk_check_button_new_with_mnemonic(_("Prepend _string into window title:")); |
| |
702 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
703 purple_prefs_get_bool("/plugins/gtk/X11/notify/method_string")); |
| |
704 gtk_box_pack_start(GTK_BOX(hbox), toggle, FALSE, FALSE, 0); |
| |
705 |
| |
706 entry = gtk_entry_new(); |
| |
707 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 0); |
| |
708 gtk_entry_set_max_length(GTK_ENTRY(entry), 10); |
| |
709 gtk_widget_set_sensitive(GTK_WIDGET(entry), |
| |
710 purple_prefs_get_bool("/plugins/gtk/X11/notify/method_string")); |
| |
711 gtk_editable_set_text(GTK_EDITABLE(entry), |
| |
712 purple_prefs_get_string("/plugins/gtk/X11/notify/title_string")); |
| |
713 g_object_set_data(G_OBJECT(toggle), "title-entry", entry); |
| |
714 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
715 G_CALLBACK(method_toggle_cb), "method_string"); |
| |
716 g_signal_connect(G_OBJECT(entry), "focus-out-event", |
| |
717 G_CALLBACK(options_entry_cb), "method_string"); |
| |
718 |
| |
719 /* Count method button */ |
| |
720 toggle = gtk_check_button_new_with_mnemonic(_("Insert c_ount of new messages into window title")); |
| |
721 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
722 purple_prefs_get_bool("/plugins/gtk/X11/notify/method_count")); |
| |
723 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
724 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
725 G_CALLBACK(method_toggle_cb), "method_count"); |
| |
726 |
| |
727 #ifdef HAVE_X11 |
| |
728 /* Count xprop method button */ |
| |
729 toggle = gtk_check_button_new_with_mnemonic(_("Insert count of new message into _X property")); |
| |
730 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
731 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
732 purple_prefs_get_bool("/plugins/gtk/X11/notify/method_count_xprop")); |
| |
733 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
734 G_CALLBACK(method_toggle_cb), "method_count_xprop"); |
| |
735 |
| |
736 /* Urgent method button */ |
| |
737 toggle = gtk_check_button_new_with_mnemonic(_("Set window manager \"_URGENT\" hint")); |
| |
738 #else |
| |
739 toggle = gtk_check_button_new_with_mnemonic(_("_Flash window")); |
| |
740 #endif |
| |
741 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
742 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
743 purple_prefs_get_bool("/plugins/gtk/X11/notify/method_urgent")); |
| |
744 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
745 G_CALLBACK(method_toggle_cb), "method_urgent"); |
| |
746 |
| |
747 /* Raise window method button */ |
| |
748 toggle = gtk_check_button_new_with_mnemonic(_("R_aise conversation window")); |
| |
749 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
750 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
751 purple_prefs_get_bool("/plugins/gtk/X11/notify/method_raise")); |
| |
752 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
753 G_CALLBACK(method_toggle_cb), "method_raise"); |
| |
754 |
| |
755 /* Present conversation method button */ |
| |
756 /* Translators: "Present" as used here is a verb. The plugin presents |
| |
757 * the window to the user. */ |
| |
758 toggle = gtk_check_button_new_with_mnemonic(_("_Present conversation window")); |
| |
759 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
760 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
761 purple_prefs_get_bool("/plugins/gtk/X11/notify/method_present")); |
| |
762 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
763 G_CALLBACK(method_toggle_cb), "method_present"); |
| |
764 |
| |
765 /*---------- "Notification Removals" ----------*/ |
| |
766 frame = pidgin_make_frame(ret, _("Notification Removal")); |
| |
767 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); |
| |
768 gtk_container_add(GTK_CONTAINER(frame), vbox); |
| |
769 |
| |
770 /* Remove on focus button */ |
| |
771 toggle = gtk_check_button_new_with_mnemonic(_("Remove when conversation window _gains focus")); |
| |
772 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
773 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
774 purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_focus")); |
| |
775 g_signal_connect(G_OBJECT(toggle), "toggled", G_CALLBACK(notify_toggle_cb), "notify_focus"); |
| |
776 |
| |
777 /* Remove on click button */ |
| |
778 toggle = gtk_check_button_new_with_mnemonic(_("Remove when conversation window _receives click")); |
| |
779 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
780 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
781 purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_click")); |
| |
782 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
783 G_CALLBACK(notify_toggle_cb), "notify_click"); |
| |
784 |
| |
785 /* Remove on type button */ |
| |
786 toggle = gtk_check_button_new_with_mnemonic(_("Remove when _typing in conversation window")); |
| |
787 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
788 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
789 purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_type")); |
| |
790 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
791 G_CALLBACK(notify_toggle_cb), "notify_type"); |
| |
792 |
| |
793 /* Remove on message send button */ |
| |
794 toggle = gtk_check_button_new_with_mnemonic(_("Remove when a _message gets sent")); |
| |
795 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
796 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
797 purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_send")); |
| |
798 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
799 G_CALLBACK(notify_toggle_cb), "notify_send"); |
| |
800 |
| |
801 #if 0 |
| |
802 /* Remove on conversation switch button */ |
| |
803 toggle = gtk_check_button_new_with_mnemonic(_("Remove on switch to conversation ta_b")); |
| |
804 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); |
| |
805 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), |
| |
806 purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_switch")); |
| |
807 g_signal_connect(G_OBJECT(toggle), "toggled", |
| |
808 G_CALLBACK(notify_toggle_cb), "notify_switch"); |
| |
809 #endif |
| |
810 |
| |
811 gtk_widget_show_all(ret); |
| |
812 return ret; |
| |
813 } |
| |
814 |
| |
815 static GPluginPluginInfo * |
| |
816 notify_query(GError **error) |
| |
817 { |
| |
818 const gchar * const authors[] = { |
| |
819 "Etan Reisner <deryni@eden.rutgers.edu>", |
| |
820 "Brian Tarricone <bjt23@cornell.edu>", |
| |
821 NULL |
| |
822 }; |
| |
823 |
| |
824 return pidgin_plugin_info_new( |
| |
825 "id", NOTIFY_PLUGIN_ID, |
| |
826 "name", N_("Message Notification"), |
| |
827 "version", DISPLAY_VERSION, |
| |
828 "category", N_("Notification"), |
| |
829 "summary", N_("Provides a variety of ways of notifying " |
| |
830 "you of unread messages."), |
| |
831 "description", N_("Provides a variety of ways of notifying " |
| |
832 "you of unread messages."), |
| |
833 "authors", authors, |
| |
834 "website", PURPLE_WEBSITE, |
| |
835 "abi-version", PURPLE_ABI_VERSION, |
| |
836 "gtk-config-frame-cb", get_config_frame, |
| |
837 NULL |
| |
838 ); |
| |
839 } |
| |
840 |
| |
841 static gboolean |
| |
842 notify_load(GPluginPlugin *plugin, GError **error) |
| |
843 { |
| |
844 GList *convs = purple_conversations_get_all(); |
| |
845 void *conv_handle = purple_conversations_get_handle(); |
| |
846 void *gtk_conv_handle = pidgin_conversations_get_handle(); |
| |
847 |
| |
848 my_plugin = plugin; |
| |
849 |
| |
850 purple_prefs_add_none("/plugins/gtk"); |
| |
851 purple_prefs_add_none("/plugins/gtk/X11"); |
| |
852 purple_prefs_add_none("/plugins/gtk/X11/notify"); |
| |
853 |
| |
854 purple_prefs_add_bool("/plugins/gtk/X11/notify/type_im", TRUE); |
| |
855 purple_prefs_add_bool("/plugins/gtk/X11/notify/type_im_sys", FALSE); |
| |
856 purple_prefs_add_bool("/plugins/gtk/X11/notify/type_chat", FALSE); |
| |
857 purple_prefs_add_bool("/plugins/gtk/X11/notify/type_chat_nick", FALSE); |
| |
858 purple_prefs_add_bool("/plugins/gtk/X11/notify/type_chat_sys", FALSE); |
| |
859 purple_prefs_add_bool("/plugins/gtk/X11/notify/type_focused", FALSE); |
| |
860 purple_prefs_add_bool("/plugins/gtk/X11/notify/method_string", FALSE); |
| |
861 purple_prefs_add_string("/plugins/gtk/X11/notify/title_string", "(*)"); |
| |
862 purple_prefs_add_bool("/plugins/gtk/X11/notify/method_urgent", FALSE); |
| |
863 purple_prefs_add_bool("/plugins/gtk/X11/notify/method_count", FALSE); |
| |
864 purple_prefs_add_bool("/plugins/gtk/X11/notify/method_count_xprop", FALSE); |
| |
865 purple_prefs_add_bool("/plugins/gtk/X11/notify/method_raise", FALSE); |
| |
866 purple_prefs_add_bool("/plugins/gtk/X11/notify/method_present", FALSE); |
| |
867 purple_prefs_add_bool("/plugins/gtk/X11/notify/notify_focus", TRUE); |
| |
868 purple_prefs_add_bool("/plugins/gtk/X11/notify/notify_click", FALSE); |
| |
869 purple_prefs_add_bool("/plugins/gtk/X11/notify/notify_type", TRUE); |
| |
870 purple_prefs_add_bool("/plugins/gtk/X11/notify/notify_send", TRUE); |
| |
871 purple_prefs_add_bool("/plugins/gtk/X11/notify/notify_switch", TRUE); |
| |
872 |
| |
873 purple_signal_connect(gtk_conv_handle, "displayed-im-msg", plugin, |
| |
874 G_CALLBACK(message_displayed_cb), NULL); |
| |
875 purple_signal_connect(gtk_conv_handle, "displayed-chat-msg", plugin, |
| |
876 G_CALLBACK(message_displayed_cb), NULL); |
| |
877 purple_signal_connect(gtk_conv_handle, "conversation-switched", plugin, |
| |
878 G_CALLBACK(conv_switched), NULL); |
| |
879 purple_signal_connect(conv_handle, "sent-im-msg", plugin, |
| |
880 G_CALLBACK(im_sent_im), NULL); |
| |
881 purple_signal_connect(conv_handle, "sent-chat-msg", plugin, |
| |
882 G_CALLBACK(chat_sent_im), NULL); |
| |
883 purple_signal_connect(conv_handle, "conversation-created", plugin, |
| |
884 G_CALLBACK(conv_created), NULL); |
| |
885 purple_signal_connect(conv_handle, "deleting-conversation", plugin, |
| |
886 G_CALLBACK(deleting_conv), NULL); |
| |
887 |
| |
888 while (convs) { |
| |
889 PurpleConversation *conv = (PurpleConversation *)convs->data; |
| |
890 |
| |
891 /* attach signals */ |
| |
892 attach_signals(conv); |
| |
893 |
| |
894 convs = convs->next; |
| |
895 } |
| |
896 |
| |
897 return TRUE; |
| |
898 } |
| |
899 |
| |
900 static gboolean |
| |
901 notify_unload(GPluginPlugin *plugin, gboolean shutdown, GError **error) |
| |
902 { |
| |
903 GList *convs = purple_conversations_get_all(); |
| |
904 |
| |
905 while (convs) { |
| |
906 PurpleConversation *conv = (PurpleConversation *)convs->data; |
| |
907 |
| |
908 /* kill signals */ |
| |
909 detach_signals(conv); |
| |
910 |
| |
911 convs = convs->next; |
| |
912 } |
| |
913 |
| |
914 return TRUE; |
| |
915 } |
| |
916 |
| |
917 GPLUGIN_NATIVE_PLUGIN_DECLARE(notify) |