2 * System tray icon (aka docklet) plugin for Purple
4 * Copyright (C) 2002-3 Robert McQueen <robot101@debian.org>
5 * Copyright (C) 2003 Herman Bloggs <hermanator12002@yahoo.com>
6 * Inspired by a similar plugin by:
7 * John (J5) Palmieri <johnp@martianrock.com>
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of the
12 * License, or (at your option) any later version.
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
28 #include "conversation.h"
34 #include "gtkaccount.h"
37 #include "gtkplugin.h"
39 #include "gtksavedstatuses.h"
42 #include "pidginstock.h"
43 #include "gtkdocklet.h"
44 #include "gtkdialogs.h"
46 #ifndef DOCKLET_TOOLTIP_LINE_LIMIT
47 #define DOCKLET_TOOLTIP_LINE_LIMIT 5
51 static struct docklet_ui_ops
*ui_ops
= NULL
;
52 static PurpleStatusPrimitive status
= PURPLE_STATUS_OFFLINE
;
53 static gboolean pending
= FALSE
;
54 static gboolean connecting
= FALSE
;
55 static gboolean enable_join_chat
= FALSE
;
56 static guint docklet_blinking_timer
= 0;
57 static gboolean visible
= FALSE
;
58 static gboolean visibility_manager
= FALSE
;
60 /**************************************************************************
61 * docklet status and utility functions
62 **************************************************************************/
66 static gboolean blinked
= FALSE
;
67 gboolean ret
= FALSE
; /* by default, don't keep blinking */
71 if(pending
&& !connecting
) {
73 if (ui_ops
&& ui_ops
->blank_icon
)
76 pidgin_docklet_update_icon();
78 ret
= TRUE
; /* keep blinking */
80 docklet_blinking_timer
= 0;
88 get_pending_list(guint max
)
93 l_im
= pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_IM
,
97 l_chat
= pidgin_conversations_find_unseen_list(PURPLE_CONV_TYPE_CHAT
,
101 if (l_im
!= NULL
&& l_chat
!= NULL
)
102 return g_list_concat(l_im
, l_chat
);
103 else if (l_im
!= NULL
)
110 docklet_update_status()
114 PurpleSavedStatus
*saved_status
;
115 PurpleStatusPrimitive newstatus
= PURPLE_STATUS_OFFLINE
;
116 gboolean newpending
= FALSE
, newconnecting
= FALSE
;
118 /* get the current savedstatus */
119 saved_status
= purple_savedstatus_get_current();
121 /* determine if any ims have unseen messages */
122 convs
= get_pending_list(DOCKLET_TOOLTIP_LINE_LIMIT
);
124 if (!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/docklet/show"), "pending")) {
125 if (convs
&& ui_ops
->create
&& !visible
) {
129 } else if (!convs
&& ui_ops
->destroy
&& visible
) {
143 /* set tooltip if messages are pending */
144 if (ui_ops
->set_tooltip
) {
145 GString
*tooltip_text
= g_string_new("");
146 for (l
= convs
, count
= 0 ; l
!= NULL
; l
= l
->next
, count
++) {
147 if (PIDGIN_IS_PIDGIN_CONVERSATION(l
->data
)) {
148 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION((PurpleConversation
*)l
->data
);
149 if (count
== DOCKLET_TOOLTIP_LINE_LIMIT
- 1)
150 g_string_append(tooltip_text
, _("Right-click for more unread messages...\n"));
152 g_string_append_printf(tooltip_text
,
153 ngettext("%d unread message from %s\n", "%d unread messages from %s\n", gtkconv
->unseen_count
),
154 gtkconv
->unseen_count
,
155 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
159 /* get rid of the last newline */
160 if (tooltip_text
->len
> 0)
161 tooltip_text
= g_string_truncate(tooltip_text
, tooltip_text
->len
- 1);
163 ui_ops
->set_tooltip(tooltip_text
->str
);
165 g_string_free(tooltip_text
, TRUE
);
170 } else if (ui_ops
->set_tooltip
) {
171 char *tooltip_text
= g_strconcat(PIDGIN_NAME
, " - ",
172 purple_savedstatus_get_title(saved_status
), NULL
);
173 ui_ops
->set_tooltip(tooltip_text
);
174 g_free(tooltip_text
);
177 for(l
= purple_accounts_get_all(); l
!= NULL
; l
= l
->next
) {
179 PurpleAccount
*account
= (PurpleAccount
*)l
->data
;
180 PurpleStatus
*account_status
;
182 if (!purple_account_get_enabled(account
, PIDGIN_UI
))
185 if (purple_account_is_disconnected(account
))
188 account_status
= purple_account_get_active_status(account
);
189 if (purple_account_is_connecting(account
))
190 newconnecting
= TRUE
;
193 newstatus
= purple_savedstatus_get_type(saved_status
);
195 /* update the icon if we changed status */
196 if (status
!= newstatus
|| pending
!=newpending
|| connecting
!=newconnecting
) {
198 pending
= newpending
;
199 connecting
= newconnecting
;
201 pidgin_docklet_update_icon();
203 /* and schedule the blinker function if messages are pending */
204 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/docklet/blink")
205 && pending
&& !connecting
&& docklet_blinking_timer
== 0) {
206 docklet_blinking_timer
= g_timeout_add(500, docklet_blink_icon
, NULL
);
210 return FALSE
; /* for when we're called by the glib idle handler */
214 online_account_supports_chat()
217 c
= purple_connections_get_all();
220 PurpleConnection
*gc
= c
->data
;
221 PurplePluginProtocolInfo
*prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
222 if (prpl_info
!= NULL
&& prpl_info
->chat_info
!= NULL
)
230 /**************************************************************************
231 * callbacks and signal handlers
232 **************************************************************************/
237 /* TODO: confirm quit while pending */
242 docklet_update_status_cb(void *data
)
244 docklet_update_status();
248 docklet_conv_updated_cb(PurpleConversation
*conv
, PurpleConvUpdateType type
)
250 if (type
== PURPLE_CONV_UPDATE_UNSEEN
)
251 docklet_update_status();
255 docklet_signed_on_cb(PurpleConnection
*gc
)
257 if (!enable_join_chat
) {
258 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->chat_info
!= NULL
)
259 enable_join_chat
= TRUE
;
261 docklet_update_status();
265 docklet_signed_off_cb(PurpleConnection
*gc
)
267 if (enable_join_chat
) {
268 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->chat_info
!= NULL
)
269 enable_join_chat
= online_account_supports_chat();
271 docklet_update_status();
275 docklet_show_pref_changed_cb(const char *name
, PurplePrefType type
,
276 gconstpointer value
, gpointer data
)
278 const char *val
= value
;
279 if (!strcmp(val
, "always")) {
280 if (ui_ops
->create
) {
283 else if (!visibility_manager
) {
284 pidgin_blist_visibility_manager_add();
285 visibility_manager
= TRUE
;
288 } else if (!strcmp(val
, "never")) {
289 if (visible
&& ui_ops
->destroy
)
292 if (visibility_manager
) {
293 pidgin_blist_visibility_manager_remove();
294 visibility_manager
= FALSE
;
296 docklet_update_status();
301 /**************************************************************************
302 * docklet pop-up menu
303 **************************************************************************/
305 docklet_toggle_mute(GtkWidget
*toggle
, void *data
)
307 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/sound/mute", GTK_CHECK_MENU_ITEM(toggle
)->active
);
311 docklet_toggle_blink(GtkWidget
*toggle
, void *data
)
313 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/docklet/blink", GTK_CHECK_MENU_ITEM(toggle
)->active
);
317 docklet_toggle_blist(GtkWidget
*toggle
, void *data
)
319 purple_blist_set_visible(GTK_CHECK_MENU_ITEM(toggle
)->active
);
323 /* This is a workaround for a bug in windows GTK+. Clicking outside of the
324 menu does not get rid of it, so instead we get rid of it as soon as the
325 pointer leaves the menu. */
327 hide_docklet_menu(gpointer data
)
330 gtk_menu_popdown(GTK_MENU(data
));
336 docklet_menu_leave_enter(GtkWidget
*menu
, GdkEventCrossing
*event
, void *data
)
338 static guint hide_docklet_timer
= 0;
339 if (event
->type
== GDK_LEAVE_NOTIFY
&& event
->detail
== GDK_NOTIFY_ANCESTOR
) {
340 purple_debug(PURPLE_DEBUG_INFO
, "docklet", "menu leave-notify-event\n");
341 /* Add some slop so that the menu doesn't annoyingly disappear when mousing around */
342 if (hide_docklet_timer
== 0) {
343 hide_docklet_timer
= purple_timeout_add(500,
344 hide_docklet_menu
, menu
);
346 } else if (event
->type
== GDK_ENTER_NOTIFY
&& event
->detail
== GDK_NOTIFY_ANCESTOR
) {
347 purple_debug(PURPLE_DEBUG_INFO
, "docklet", "menu enter-notify-event\n");
348 if (hide_docklet_timer
!= 0) {
349 /* Cancel the hiding if we reenter */
351 purple_timeout_remove(hide_docklet_timer
);
352 hide_docklet_timer
= 0;
360 show_custom_status_editor_cb(GtkMenuItem
*menuitem
, gpointer user_data
)
362 PurpleSavedStatus
*saved_status
;
363 saved_status
= purple_savedstatus_get_current();
365 if (purple_savedstatus_get_type(saved_status
) == PURPLE_STATUS_AVAILABLE
)
366 saved_status
= purple_savedstatus_new(NULL
, PURPLE_STATUS_AWAY
);
368 pidgin_status_editor_show(FALSE
,
369 purple_savedstatus_is_transient(saved_status
) ? saved_status
: NULL
);
373 activate_status_primitive_cb(GtkMenuItem
*menuitem
, gpointer user_data
)
375 PurpleStatusPrimitive primitive
;
376 PurpleSavedStatus
*saved_status
;
378 primitive
= GPOINTER_TO_INT(user_data
);
380 /* Try to lookup an already existing transient saved status */
381 saved_status
= purple_savedstatus_find_transient_by_type_and_message(primitive
, NULL
);
383 /* Create a new transient saved status if we weren't able to find one */
384 if (saved_status
== NULL
)
385 saved_status
= purple_savedstatus_new(NULL
, primitive
);
387 /* Set the status for each account */
388 purple_savedstatus_activate(saved_status
);
392 activate_saved_status_cb(GtkMenuItem
*menuitem
, gpointer user_data
)
394 time_t creation_time
;
395 PurpleSavedStatus
*saved_status
;
397 creation_time
= GPOINTER_TO_INT(user_data
);
398 saved_status
= purple_savedstatus_find_by_creation_time(creation_time
);
399 if (saved_status
!= NULL
)
400 purple_savedstatus_activate(saved_status
);
404 new_menu_item_with_status_icon(GtkWidget
*menu
, const char *str
, PurpleStatusPrimitive primitive
, GtkSignalFunc sf
, gpointer data
, guint accel_key
, guint accel_mods
, char *mod
)
410 menuitem
= gtk_image_menu_item_new_with_mnemonic(str
);
413 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
416 g_signal_connect(G_OBJECT(menuitem
), "activate", sf
, data
);
418 pixbuf
= pidgin_create_status_icon(primitive
, menu
, PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
);
419 image
= gtk_image_new_from_pixbuf(pixbuf
);
420 g_object_unref(pixbuf
);
421 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem
), image
);
423 gtk_widget_show_all(menuitem
);
429 docklet_status_submenu()
431 GtkWidget
*submenu
, *menuitem
;
432 GList
*popular_statuses
, *cur
;
434 submenu
= gtk_menu_new();
435 menuitem
= gtk_menu_item_new_with_label(_("Change Status"));
436 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
438 new_menu_item_with_status_icon(submenu
, _("Available"),
439 PURPLE_STATUS_AVAILABLE
, G_CALLBACK(activate_status_primitive_cb
),
440 GINT_TO_POINTER(PURPLE_STATUS_AVAILABLE
), 0, 0, NULL
);
442 new_menu_item_with_status_icon(submenu
, _("Away"),
443 PURPLE_STATUS_AWAY
, G_CALLBACK(activate_status_primitive_cb
),
444 GINT_TO_POINTER(PURPLE_STATUS_AWAY
), 0, 0, NULL
);
446 new_menu_item_with_status_icon(submenu
, _("Invisible"),
447 PURPLE_STATUS_INVISIBLE
, G_CALLBACK(activate_status_primitive_cb
),
448 GINT_TO_POINTER(PURPLE_STATUS_INVISIBLE
), 0, 0, NULL
);
450 new_menu_item_with_status_icon(submenu
, _("Offline"),
451 PURPLE_STATUS_OFFLINE
, G_CALLBACK(activate_status_primitive_cb
),
452 GINT_TO_POINTER(PURPLE_STATUS_OFFLINE
), 0, 0, NULL
);
454 popular_statuses
= purple_savedstatuses_get_popular(6);
455 if (popular_statuses
!= NULL
)
456 pidgin_separator(submenu
);
457 for (cur
= popular_statuses
; cur
!= NULL
; cur
= cur
->next
)
459 PurpleSavedStatus
*saved_status
= cur
->data
;
460 time_t creation_time
= purple_savedstatus_get_creation_time(saved_status
);
461 new_menu_item_with_status_icon(submenu
,
462 purple_savedstatus_get_title(saved_status
),
463 purple_savedstatus_get_type(saved_status
), G_CALLBACK(activate_saved_status_cb
),
464 GINT_TO_POINTER(creation_time
), 0, 0, NULL
);
466 g_list_free(popular_statuses
);
468 pidgin_separator(submenu
);
470 pidgin_new_item_from_stock(submenu
, _("New..."), NULL
, G_CALLBACK(show_custom_status_editor_cb
), NULL
, 0, 0, NULL
);
471 pidgin_new_item_from_stock(submenu
, _("Saved..."), NULL
, G_CALLBACK(pidgin_status_window_show
), NULL
, 0, 0, NULL
);
478 static GtkWidget
*menu
= NULL
;
482 gtk_widget_destroy(menu
);
485 menu
= gtk_menu_new();
487 menuitem
= gtk_check_menu_item_new_with_label(_("Show Buddy List"));
488 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem
), purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/list_visible"));
489 g_signal_connect(G_OBJECT(menuitem
), "toggled", G_CALLBACK(docklet_toggle_blist
), NULL
);
490 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
492 menuitem
= gtk_menu_item_new_with_label(_("Unread Messages"));
495 GtkWidget
*submenu
= gtk_menu_new();
496 GList
*l
= get_pending_list(0);
498 gtk_widget_set_sensitive(menuitem
, FALSE
);
499 purple_debug_warning("docklet",
500 "status indicates messages pending, but no conversations with unseen messages were found.");
502 pidgin_conversations_fill_menu(submenu
, l
);
504 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), submenu
);
507 gtk_widget_set_sensitive(menuitem
, FALSE
);
509 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
511 pidgin_separator(menu
);
513 menuitem
= pidgin_new_item_from_stock(menu
, _("New Message..."), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
, G_CALLBACK(pidgin_dialogs_im
), NULL
, 0, 0, NULL
);
514 if (status
== PURPLE_STATUS_OFFLINE
)
515 gtk_widget_set_sensitive(menuitem
, FALSE
);
517 menuitem
= docklet_status_submenu();
518 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
520 pidgin_separator(menu
);
522 pidgin_new_item_from_stock(menu
, _("Accounts"), NULL
, G_CALLBACK(pidgin_accounts_window_show
), NULL
, 0, 0, NULL
);
523 pidgin_new_item_from_stock(menu
, _("Plugins"), PIDGIN_STOCK_TOOLBAR_PLUGINS
, G_CALLBACK(pidgin_plugin_dialog_show
), NULL
, 0, 0, NULL
);
524 pidgin_new_item_from_stock(menu
, _("Preferences"), GTK_STOCK_PREFERENCES
, G_CALLBACK(pidgin_prefs_show
), NULL
, 0, 0, NULL
);
526 pidgin_separator(menu
);
528 menuitem
= gtk_check_menu_item_new_with_label(_("Mute Sounds"));
529 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem
), purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/sound/mute"));
530 if (!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method"), "none"))
531 gtk_widget_set_sensitive(GTK_WIDGET(menuitem
), FALSE
);
532 g_signal_connect(G_OBJECT(menuitem
), "toggled", G_CALLBACK(docklet_toggle_mute
), NULL
);
533 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
535 menuitem
= gtk_check_menu_item_new_with_label(_("Blink on new message"));
536 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem
), purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/docklet/blink"));
537 g_signal_connect(G_OBJECT(menuitem
), "toggled", G_CALLBACK(docklet_toggle_blink
), NULL
);
538 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
540 pidgin_separator(menu
);
542 pidgin_new_item_from_stock(menu
, _("Quit"), GTK_STOCK_QUIT
, G_CALLBACK(purple_core_quit
), NULL
, 0, 0, NULL
);
545 g_signal_connect(menu
, "leave-notify-event", G_CALLBACK(docklet_menu_leave_enter
), NULL
);
546 g_signal_connect(menu
, "enter-notify-event", G_CALLBACK(docklet_menu_leave_enter
), NULL
);
548 gtk_widget_show_all(menu
);
549 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
,
550 ui_ops
->position_menu
,
551 NULL
, 0, gtk_get_current_event_time());
554 /**************************************************************************
555 * public api for ui_ops
556 **************************************************************************/
558 pidgin_docklet_update_icon()
560 if (ui_ops
&& ui_ops
->update_icon
)
561 ui_ops
->update_icon(status
, connecting
, pending
);
565 pidgin_docklet_clicked(int button_type
)
567 switch (button_type
) {
570 GList
*l
= get_pending_list(1);
572 purple_conversation_present((PurpleConversation
*)l
->data
);
576 pidgin_blist_toggle_visibility();
586 pidgin_docklet_embedded()
588 if (!visibility_manager
589 && strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/docklet/show"), "pending")) {
590 pidgin_blist_visibility_manager_add();
591 visibility_manager
= TRUE
;
594 docklet_update_status();
595 pidgin_docklet_update_icon();
599 pidgin_docklet_remove()
602 if (visibility_manager
) {
603 pidgin_blist_visibility_manager_remove();
604 visibility_manager
= FALSE
;
606 if (docklet_blinking_timer
) {
607 g_source_remove(docklet_blinking_timer
);
608 docklet_blinking_timer
= 0;
611 status
= PURPLE_STATUS_OFFLINE
;
616 pidgin_docklet_set_ui_ops(struct docklet_ui_ops
*ops
)
622 pidgin_docklet_get_handle()
629 pidgin_docklet_init()
631 void *conn_handle
= purple_connections_get_handle();
632 void *conv_handle
= purple_conversations_get_handle();
633 void *accounts_handle
= purple_accounts_get_handle();
634 void *status_handle
= purple_savedstatuses_get_handle();
635 void *docklet_handle
= pidgin_docklet_get_handle();
637 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/docklet");
638 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/docklet/blink", FALSE
);
639 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/docklet/show", "always");
640 purple_prefs_connect_callback(docklet_handle
, PIDGIN_PREFS_ROOT
"/docklet/show",
641 docklet_show_pref_changed_cb
, NULL
);
644 if (!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/docklet/show"), "always") && ui_ops
&& ui_ops
->create
)
647 purple_signal_connect(conn_handle
, "signed-on",
648 docklet_handle
, PURPLE_CALLBACK(docklet_signed_on_cb
), NULL
);
649 purple_signal_connect(conn_handle
, "signed-off",
650 docklet_handle
, PURPLE_CALLBACK(docklet_signed_off_cb
), NULL
);
651 purple_signal_connect(accounts_handle
, "account-connecting",
652 docklet_handle
, PURPLE_CALLBACK(docklet_update_status_cb
), NULL
);
653 purple_signal_connect(conv_handle
, "received-im-msg",
654 docklet_handle
, PURPLE_CALLBACK(docklet_update_status_cb
), NULL
);
655 purple_signal_connect(conv_handle
, "conversation-created",
656 docklet_handle
, PURPLE_CALLBACK(docklet_update_status_cb
), NULL
);
657 purple_signal_connect(conv_handle
, "deleting-conversation",
658 docklet_handle
, PURPLE_CALLBACK(docklet_update_status_cb
), NULL
);
659 purple_signal_connect(conv_handle
, "conversation-updated",
660 docklet_handle
, PURPLE_CALLBACK(docklet_conv_updated_cb
), NULL
);
661 purple_signal_connect(status_handle
, "savedstatus-changed",
662 docklet_handle
, PURPLE_CALLBACK(docklet_update_status_cb
), NULL
);
664 purple_signal_connect(purple_get_core(), "quitting",
665 docklet_handle
, PURPLE_CALLBACK(purple_quit_cb
), NULL
);
668 enable_join_chat
= online_account_supports_chat();
672 pidgin_docklet_uninit()
674 if (visible
&& ui_ops
&& ui_ops
->destroy
)