1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2007-2008 Collabora Ltd.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 * Authors: Xavier Claessens <xclaesse@gmail.com>
29 #include <gdk/gdkkeysyms.h>
30 #include <glib/gi18n.h>
32 #include <libnotify/notification.h>
33 #include <libnotify/notify.h>
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
38 #include <libempathy/empathy-contact-manager.h>
39 #include <libempathy/empathy-gsettings.h>
40 #include <libempathy/empathy-utils.h>
42 #include <libempathy-gtk/empathy-presence-chooser.h>
43 #include <libempathy-gtk/empathy-ui-utils.h>
44 #include <libempathy-gtk/empathy-images.h>
45 #include <libempathy-gtk/empathy-new-message-dialog.h>
46 #include <libempathy-gtk/empathy-new-call-dialog.h>
47 #include <libempathy-gtk/empathy-notify-manager.h>
49 #include "empathy-accounts-dialog.h"
50 #include "empathy-status-icon.h"
51 #include "empathy-preferences.h"
52 #include "empathy-event-manager.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER
55 #include <libempathy/empathy-debug.h>
57 /* Number of ms to wait when blinking */
58 #define BLINK_TIMEOUT 500
60 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyStatusIcon)
63 TpAccountManager *account_manager;
64 EmpathyNotifyManager *notify_mgr;
65 gboolean showing_event_icon;
67 EmpathyEventManager *event_manager;
69 NotifyNotification *notification;
70 GSettings *gsettings_ui;
73 GtkUIManager *ui_manager;
74 GtkWidget *popup_menu;
75 GtkAction *show_window_item;
76 GtkAction *new_message_item;
77 GtkAction *status_item;
78 } EmpathyStatusIconPriv;
80 G_DEFINE_TYPE (EmpathyStatusIcon, empathy_status_icon, G_TYPE_OBJECT);
83 status_icon_notification_closed_cb (NotifyNotification *notification,
84 EmpathyStatusIcon *icon)
86 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
88 g_object_unref (notification);
90 if (priv->notification == notification) {
91 priv->notification = NULL;
98 /* inhibit other updates for this event */
99 empathy_event_inhibit_updates (priv->event);
103 notification_close_helper (EmpathyStatusIconPriv *priv)
105 if (priv->notification != NULL) {
106 notify_notification_close (priv->notification, NULL);
107 priv->notification = NULL;
112 notification_approve_cb (NotifyNotification *notification,
114 EmpathyStatusIcon *icon)
116 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
119 empathy_event_approve (priv->event);
123 notification_decline_cb (NotifyNotification *notification,
125 EmpathyStatusIcon *icon)
127 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
130 empathy_event_decline (priv->event);
134 notification_decline_subscription_cb (NotifyNotification *notification,
136 EmpathyStatusIcon *icon)
138 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
139 EmpathyContactManager *manager;
141 if (priv->event == NULL)
145 manager = empathy_contact_manager_dup_singleton ();
146 empathy_contact_list_remove (EMPATHY_CONTACT_LIST (manager),
147 priv->event->contact, "");
149 empathy_event_remove (priv->event);
151 g_object_unref (manager);
155 notification_accept_subscription_cb (NotifyNotification *notification,
157 EmpathyStatusIcon *icon)
159 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
160 EmpathyContactManager *manager;
162 if (priv->event == NULL)
166 manager = empathy_contact_manager_dup_singleton ();
167 empathy_contact_list_add (EMPATHY_CONTACT_LIST (manager),
168 priv->event->contact, "");
170 empathy_event_remove (priv->event);
172 g_object_unref (manager);
176 add_notification_actions (EmpathyStatusIcon *self,
177 NotifyNotification *notification)
179 EmpathyStatusIconPriv *priv = GET_PRIV (self);
181 switch (priv->event->type) {
182 case EMPATHY_EVENT_TYPE_CHAT:
183 notify_notification_add_action (notification,
184 "respond", _("Respond"), (NotifyActionCallback) notification_approve_cb,
188 case EMPATHY_EVENT_TYPE_VOIP:
189 notify_notification_add_action (notification,
190 "reject", _("Reject"), (NotifyActionCallback) notification_decline_cb,
193 notify_notification_add_action (notification,
194 "answer", _("Answer"), (NotifyActionCallback) notification_approve_cb,
198 case EMPATHY_EVENT_TYPE_TRANSFER:
199 case EMPATHY_EVENT_TYPE_INVITATION:
200 notify_notification_add_action (notification,
201 "decline", _("Decline"), (NotifyActionCallback) notification_decline_cb,
204 notify_notification_add_action (notification,
205 "accept", _("Accept"), (NotifyActionCallback) notification_approve_cb,
209 case EMPATHY_EVENT_TYPE_SUBSCRIPTION:
210 notify_notification_add_action (notification,
211 "decline", _("Decline"),
212 (NotifyActionCallback) notification_decline_subscription_cb,
215 notify_notification_add_action (notification,
216 "accept", _("Accept"),
217 (NotifyActionCallback) notification_accept_subscription_cb,
226 status_icon_update_notification (EmpathyStatusIcon *icon)
228 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
229 GdkPixbuf *pixbuf = NULL;
231 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
232 /* always close the notification if this happens */
233 notification_close_helper (priv);
238 gchar *message_esc = NULL;
239 gboolean has_x_canonical_append;
240 NotifyNotification *notification = priv->notification;
242 if (priv->event->message != NULL)
243 message_esc = g_markup_escape_text (priv->event->message, -1);
245 has_x_canonical_append =
246 empathy_notify_manager_has_capability (priv->notify_mgr,
247 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
249 if (notification != NULL && ! has_x_canonical_append) {
250 /* if the notification server supports x-canonical-append, it is
251 better to not use notify_notification_update to avoid
252 overwriting the current notification message */
253 notify_notification_update (notification,
254 priv->event->header, message_esc,
257 /* if the notification server supports x-canonical-append,
258 the hint will be added, so that the message from the
259 just created notification will be automatically appended
260 to an existing notification with the same title.
261 In this way the previous message will not be lost: the new
262 message will appear below it, in the same notification */
263 notification = notify_notification_new
264 (priv->event->header, message_esc, NULL);
266 if (priv->notification == NULL) {
267 priv->notification = notification;
270 notify_notification_set_timeout (notification,
271 NOTIFY_EXPIRES_DEFAULT);
273 if (has_x_canonical_append) {
274 notify_notification_set_hint_string (notification,
275 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
278 if (empathy_notify_manager_has_capability (priv->notify_mgr,
279 EMPATHY_NOTIFY_MANAGER_CAP_ACTIONS))
280 add_notification_actions (icon, notification);
282 g_signal_connect (notification, "closed",
283 G_CALLBACK (status_icon_notification_closed_cb), icon);
286 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (
287 priv->notify_mgr, priv->event->contact,
288 priv->event->icon_name);
290 if (pixbuf != NULL) {
291 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
292 g_object_unref (pixbuf);
295 notify_notification_show (notification, NULL);
297 g_free (message_esc);
299 notification_close_helper (priv);
304 status_icon_update_tooltip (EmpathyStatusIcon *icon)
306 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
309 gchar *tooltip = NULL;
311 if (priv->event->message != NULL)
312 tooltip = g_markup_printf_escaped ("<i>%s</i>\n%s",
314 priv->event->message);
316 tooltip = g_markup_printf_escaped ("<i>%s</i>",
317 priv->event->header);
318 gtk_status_icon_set_tooltip_markup (priv->icon, tooltip);
321 TpConnectionPresenceType type;
324 type = tp_account_manager_get_most_available_presence (
325 priv->account_manager, NULL, &msg);
327 if (!EMP_STR_EMPTY (msg)) {
328 gtk_status_icon_set_tooltip_text (priv->icon, msg);
331 gtk_status_icon_set_tooltip_text (priv->icon,
332 empathy_presence_get_default_message (type));
340 status_icon_update_icon (EmpathyStatusIcon *icon)
342 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
343 const gchar *icon_name;
345 if (priv->event && priv->showing_event_icon) {
346 icon_name = priv->event->icon_name;
348 TpConnectionPresenceType state;
350 state = tp_account_manager_get_most_available_presence (
351 priv->account_manager, NULL, NULL);
353 /* An unset presence type here doesn't make sense. Force it
355 if (state == TP_CONNECTION_PRESENCE_TYPE_UNSET) {
356 state = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
359 icon_name = empathy_icon_name_for_presence (state);
362 if (icon_name != NULL)
363 gtk_status_icon_set_from_icon_name (priv->icon, icon_name);
367 status_icon_blink_timeout_cb (EmpathyStatusIcon *icon)
369 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
371 priv->showing_event_icon = !priv->showing_event_icon;
372 status_icon_update_icon (icon);
377 status_icon_event_added_cb (EmpathyEventManager *manager,
379 EmpathyStatusIcon *icon)
381 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
387 DEBUG ("New event %p", event);
390 if (event->must_ack) {
391 priv->showing_event_icon = TRUE;
392 status_icon_update_icon (icon);
393 status_icon_update_tooltip (icon);
395 status_icon_update_notification (icon);
397 if (!priv->blink_timeout && priv->showing_event_icon) {
398 priv->blink_timeout = g_timeout_add (BLINK_TIMEOUT,
399 (GSourceFunc) status_icon_blink_timeout_cb,
405 status_icon_event_removed_cb (EmpathyEventManager *manager,
407 EmpathyStatusIcon *icon)
409 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
411 if (event != priv->event) {
415 priv->event = empathy_event_manager_get_top_event (priv->event_manager);
417 status_icon_update_tooltip (icon);
418 status_icon_update_icon (icon);
420 /* update notification anyway, as it's safe and we might have been
421 * changed presence in the meanwhile
423 status_icon_update_notification (icon);
425 if (!priv->event && priv->blink_timeout) {
426 g_source_remove (priv->blink_timeout);
427 priv->blink_timeout = 0;
432 status_icon_event_updated_cb (EmpathyEventManager *manager,
434 EmpathyStatusIcon *icon)
436 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
438 if (event != priv->event) {
442 if (empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
443 status_icon_update_notification (icon);
446 status_icon_update_tooltip (icon);
450 status_icon_set_visibility (EmpathyStatusIcon *icon,
454 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
457 g_settings_set_boolean (priv->gsettings_ui,
458 EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN,
463 empathy_window_iconify (priv->window, priv->icon);
465 empathy_window_present (GTK_WINDOW (priv->window));
470 status_icon_notify_visibility_cb (GSettings *gsettings,
474 EmpathyStatusIcon *icon = user_data;
475 gboolean hidden = FALSE;
477 hidden = g_settings_get_boolean (gsettings, key);
478 status_icon_set_visibility (icon, !hidden, FALSE);
482 status_icon_toggle_visibility (EmpathyStatusIcon *icon)
484 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
487 visible = gtk_window_is_active (priv->window);
488 status_icon_set_visibility (icon, !visible, TRUE);
492 status_icon_presence_changed_cb (EmpathyStatusIcon *icon)
494 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
496 status_icon_update_icon (icon);
497 status_icon_update_tooltip (icon);
499 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
500 /* dismiss the outstanding notification if present */
502 if (priv->notification) {
503 notify_notification_close (priv->notification, NULL);
504 priv->notification = NULL;
510 status_icon_delete_event_cb (GtkWidget *widget,
512 EmpathyStatusIcon *icon)
514 status_icon_set_visibility (icon, FALSE, TRUE);
519 status_icon_key_press_event_cb (GtkWidget *window,
521 EmpathyStatusIcon *icon)
523 if (event->keyval == GDK_KEY_Escape) {
524 status_icon_set_visibility (icon, FALSE, TRUE);
530 status_icon_activate_cb (GtkStatusIcon *status_icon,
531 EmpathyStatusIcon *icon)
533 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
535 DEBUG ("%s", priv->event ? "event" : "toggle");
538 empathy_event_activate (priv->event);
540 status_icon_toggle_visibility (icon);
545 status_icon_show_hide_window_cb (GtkToggleAction *action,
546 EmpathyStatusIcon *icon)
550 visible = gtk_toggle_action_get_active (action);
551 status_icon_set_visibility (icon, visible, TRUE);
555 status_icon_new_message_cb (GtkAction *action,
556 EmpathyStatusIcon *icon)
558 empathy_new_message_dialog_show (NULL);
562 status_icon_new_call_cb (GtkAction *action,
563 EmpathyStatusIcon *icon)
565 empathy_new_call_dialog_show (NULL);
569 status_icon_quit_cb (GtkAction *action,
570 EmpathyStatusIcon *icon)
576 status_icon_popup_menu_cb (GtkStatusIcon *status_icon,
579 EmpathyStatusIcon *icon)
581 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
582 GtkWidget *menu_item;
586 show = empathy_window_get_is_visible (GTK_WINDOW (priv->window));
588 g_signal_handlers_block_by_func (priv->show_window_item,
589 status_icon_show_hide_window_cb,
591 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_window_item),
593 g_signal_handlers_unblock_by_func (priv->show_window_item,
594 status_icon_show_hide_window_cb,
597 menu_item = gtk_ui_manager_get_widget (priv->ui_manager, "/menu/status");
598 submenu = empathy_presence_chooser_create_menu ();
599 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
601 gtk_menu_popup (GTK_MENU (priv->popup_menu),
603 gtk_status_icon_position_menu,
610 status_icon_create_menu (EmpathyStatusIcon *icon)
612 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
616 filename = empathy_file_lookup ("empathy-status-icon.ui", "src");
617 gui = empathy_builder_get_file (filename,
618 "ui_manager", &priv->ui_manager,
619 "menu", &priv->popup_menu,
620 "show_list", &priv->show_window_item,
621 "new_message", &priv->new_message_item,
622 "status", &priv->status_item,
626 empathy_builder_connect (gui, icon,
627 "show_list", "toggled", status_icon_show_hide_window_cb,
628 "new_message", "activate", status_icon_new_message_cb,
629 "new_call", "activate", status_icon_new_call_cb,
630 "quit", "activate", status_icon_quit_cb,
633 g_object_ref (priv->ui_manager);
634 g_object_unref (gui);
638 status_icon_status_changed_cb (TpAccount *account,
639 TpConnectionStatus current,
640 TpConnectionStatus previous,
641 TpConnectionStatusReason reason,
642 gchar *dbus_error_name,
644 EmpathyStatusIcon *icon)
646 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
648 gtk_action_set_sensitive (priv->new_message_item,
649 empathy_account_manager_get_accounts_connected (NULL));
653 status_icon_finalize (GObject *object)
655 EmpathyStatusIconPriv *priv = GET_PRIV (object);
657 if (priv->blink_timeout) {
658 g_source_remove (priv->blink_timeout);
661 if (priv->notification) {
662 notify_notification_close (priv->notification, NULL);
663 g_object_unref (priv->notification);
664 priv->notification = NULL;
667 g_object_unref (priv->icon);
668 g_object_unref (priv->account_manager);
669 g_object_unref (priv->event_manager);
670 g_object_unref (priv->ui_manager);
671 g_object_unref (priv->notify_mgr);
672 g_object_unref (priv->gsettings_ui);
673 g_object_unref (priv->window);
677 empathy_status_icon_class_init (EmpathyStatusIconClass *klass)
679 GObjectClass *object_class = G_OBJECT_CLASS (klass);
681 object_class->finalize = status_icon_finalize;
683 g_type_class_add_private (object_class, sizeof (EmpathyStatusIconPriv));
687 account_manager_prepared_cb (GObject *source_object,
688 GAsyncResult *result,
692 TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
693 EmpathyStatusIcon *icon = user_data;
694 GError *error = NULL;
696 if (!tp_account_manager_prepare_finish (account_manager, result, &error)) {
697 DEBUG ("Failed to prepare account manager: %s", error->message);
698 g_error_free (error);
702 list = tp_account_manager_get_valid_accounts (account_manager);
703 for (l = list; l != NULL; l = l->next) {
704 tp_g_signal_connect_object (l->data, "status-changed",
705 G_CALLBACK (status_icon_status_changed_cb),
710 status_icon_presence_changed_cb (icon);
714 empathy_status_icon_init (EmpathyStatusIcon *icon)
716 EmpathyStatusIconPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (icon,
717 EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconPriv);
720 priv->icon = gtk_status_icon_new ();
721 priv->account_manager = tp_account_manager_dup ();
722 priv->event_manager = empathy_event_manager_dup_singleton ();
724 tp_account_manager_prepare_async (priv->account_manager, NULL,
725 account_manager_prepared_cb, icon);
727 /* make icon listen and respond to MAIN_WINDOW_HIDDEN changes */
728 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
729 g_signal_connect (priv->gsettings_ui,
730 "changed::" EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN,
731 G_CALLBACK (status_icon_notify_visibility_cb),
734 status_icon_create_menu (icon);
736 g_signal_connect_swapped (priv->account_manager,
737 "most-available-presence-changed",
738 G_CALLBACK (status_icon_presence_changed_cb),
740 g_signal_connect (priv->event_manager, "event-added",
741 G_CALLBACK (status_icon_event_added_cb),
743 g_signal_connect (priv->event_manager, "event-removed",
744 G_CALLBACK (status_icon_event_removed_cb),
746 g_signal_connect (priv->event_manager, "event-updated",
747 G_CALLBACK (status_icon_event_updated_cb),
749 g_signal_connect (priv->icon, "activate",
750 G_CALLBACK (status_icon_activate_cb),
752 g_signal_connect (priv->icon, "popup-menu",
753 G_CALLBACK (status_icon_popup_menu_cb),
756 priv->notification = NULL;
757 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
761 empathy_status_icon_new (GtkWindow *window, gboolean hide_contact_list)
763 EmpathyStatusIconPriv *priv;
764 EmpathyStatusIcon *icon;
765 gboolean should_hide;
767 g_return_val_if_fail (GTK_IS_WINDOW (window), NULL);
769 icon = g_object_new (EMPATHY_TYPE_STATUS_ICON, NULL);
770 priv = GET_PRIV (icon);
772 priv->window = g_object_ref (window);
774 g_signal_connect_after (priv->window, "key-press-event",
775 G_CALLBACK (status_icon_key_press_event_cb),
778 g_signal_connect (priv->window, "delete-event",
779 G_CALLBACK (status_icon_delete_event_cb),
782 if (!hide_contact_list) {
783 should_hide = g_settings_get_boolean (priv->gsettings_ui,
784 EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN);
789 status_icon_set_visibility (icon, !should_hide, FALSE);