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-gsettings.h>
39 #include <libempathy/empathy-utils.h>
41 #include <libempathy-gtk/empathy-presence-chooser.h>
42 #include <libempathy-gtk/empathy-ui-utils.h>
43 #include <libempathy-gtk/empathy-images.h>
44 #include <libempathy-gtk/empathy-new-message-dialog.h>
45 #include <libempathy-gtk/empathy-new-call-dialog.h>
46 #include <libempathy-gtk/empathy-notify-manager.h>
48 #include "empathy-accounts-dialog.h"
49 #include "empathy-status-icon.h"
50 #include "empathy-preferences.h"
51 #include "empathy-event-manager.h"
53 #define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER
54 #include <libempathy/empathy-debug.h>
56 /* Number of ms to wait when blinking */
57 #define BLINK_TIMEOUT 500
59 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyStatusIcon)
62 TpAccountManager *account_manager;
63 EmpathyNotifyManager *notify_mgr;
64 gboolean showing_event_icon;
66 EmpathyEventManager *event_manager;
68 NotifyNotification *notification;
69 GSettings *gsettings_ui;
72 GtkUIManager *ui_manager;
73 GtkWidget *popup_menu;
74 GtkAction *show_window_item;
75 GtkAction *new_message_item;
76 GtkAction *status_item;
77 } EmpathyStatusIconPriv;
79 G_DEFINE_TYPE (EmpathyStatusIcon, empathy_status_icon, G_TYPE_OBJECT);
82 status_icon_notification_closed_cb (NotifyNotification *notification,
83 EmpathyStatusIcon *icon)
85 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
87 g_object_unref (notification);
89 if (priv->notification == notification) {
90 priv->notification = NULL;
97 /* inhibit other updates for this event */
98 empathy_event_inhibit_updates (priv->event);
102 notification_close_helper (EmpathyStatusIconPriv *priv)
104 if (priv->notification != NULL) {
105 notify_notification_close (priv->notification, NULL);
106 priv->notification = NULL;
111 notification_approve_cb (NotifyNotification *notification,
113 EmpathyStatusIcon *icon)
115 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
118 empathy_event_approve (priv->event);
122 notification_decline_cb (NotifyNotification *notification,
124 EmpathyStatusIcon *icon)
126 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
129 empathy_event_decline (priv->event);
133 add_notification_actions (EmpathyStatusIcon *self,
134 NotifyNotification *notification)
136 EmpathyStatusIconPriv *priv = GET_PRIV (self);
138 switch (priv->event->type) {
139 case EMPATHY_EVENT_TYPE_CHAT:
140 notify_notification_add_action (notification,
141 "respond", _("Respond"), (NotifyActionCallback) notification_approve_cb,
145 case EMPATHY_EVENT_TYPE_VOIP:
146 notify_notification_add_action (notification,
147 "reject", _("Reject"), (NotifyActionCallback) notification_decline_cb,
150 notify_notification_add_action (notification,
151 "answer", _("Answer"), (NotifyActionCallback) notification_approve_cb,
161 status_icon_update_notification (EmpathyStatusIcon *icon)
163 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
164 GdkPixbuf *pixbuf = NULL;
166 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
167 /* always close the notification if this happens */
168 notification_close_helper (priv);
173 gchar *message_esc = NULL;
174 gboolean has_x_canonical_append;
175 NotifyNotification *notification = priv->notification;
177 if (priv->event->message != NULL)
178 message_esc = g_markup_escape_text (priv->event->message, -1);
180 has_x_canonical_append =
181 empathy_notify_manager_has_capability (priv->notify_mgr,
182 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
184 if (notification != NULL && ! has_x_canonical_append) {
185 /* if the notification server supports x-canonical-append, it is
186 better to not use notify_notification_update to avoid
187 overwriting the current notification message */
188 notify_notification_update (notification,
189 priv->event->header, message_esc,
192 /* if the notification server supports x-canonical-append,
193 the hint will be added, so that the message from the
194 just created notification will be automatically appended
195 to an existing notification with the same title.
196 In this way the previous message will not be lost: the new
197 message will appear below it, in the same notification */
198 notification = notify_notification_new_with_status_icon
199 (priv->event->header, message_esc, NULL, priv->icon);
201 if (priv->notification == NULL) {
202 priv->notification = notification;
205 notify_notification_set_timeout (notification,
206 NOTIFY_EXPIRES_DEFAULT);
208 if (has_x_canonical_append) {
209 notify_notification_set_hint_string (notification,
210 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
213 if (empathy_notify_manager_has_capability (priv->notify_mgr,
214 EMPATHY_NOTIFY_MANAGER_CAP_ACTIONS))
215 add_notification_actions (icon, notification);
217 g_signal_connect (notification, "closed",
218 G_CALLBACK (status_icon_notification_closed_cb), icon);
221 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (
222 priv->notify_mgr, priv->event->contact,
223 priv->event->icon_name);
225 if (pixbuf != NULL) {
226 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
227 g_object_unref (pixbuf);
230 notify_notification_show (notification, NULL);
232 g_free (message_esc);
234 notification_close_helper (priv);
239 status_icon_update_tooltip (EmpathyStatusIcon *icon)
241 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
244 gchar *tooltip = NULL;
246 if (priv->event->message != NULL)
247 tooltip = g_markup_printf_escaped ("<i>%s</i>\n%s",
249 priv->event->message);
251 tooltip = g_markup_printf_escaped ("<i>%s</i>",
252 priv->event->header);
253 gtk_status_icon_set_tooltip_markup (priv->icon, tooltip);
256 TpConnectionPresenceType type;
259 type = tp_account_manager_get_most_available_presence (
260 priv->account_manager, NULL, &msg);
262 if (!EMP_STR_EMPTY (msg)) {
263 gtk_status_icon_set_tooltip_text (priv->icon, msg);
266 gtk_status_icon_set_tooltip_text (priv->icon,
267 empathy_presence_get_default_message (type));
275 status_icon_update_icon (EmpathyStatusIcon *icon)
277 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
278 const gchar *icon_name;
280 if (priv->event && priv->showing_event_icon) {
281 icon_name = priv->event->icon_name;
283 TpConnectionPresenceType state;
285 state = tp_account_manager_get_most_available_presence (
286 priv->account_manager, NULL, NULL);
288 /* An unset presence type here doesn't make sense. Force it
290 if (state == TP_CONNECTION_PRESENCE_TYPE_UNSET) {
291 state = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
294 icon_name = empathy_icon_name_for_presence (state);
297 if (icon_name != NULL)
298 gtk_status_icon_set_from_icon_name (priv->icon, icon_name);
302 status_icon_blink_timeout_cb (EmpathyStatusIcon *icon)
304 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
306 priv->showing_event_icon = !priv->showing_event_icon;
307 status_icon_update_icon (icon);
312 status_icon_event_added_cb (EmpathyEventManager *manager,
314 EmpathyStatusIcon *icon)
316 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
322 DEBUG ("New event %p", event);
325 if (event->must_ack) {
326 priv->showing_event_icon = TRUE;
327 status_icon_update_icon (icon);
328 status_icon_update_tooltip (icon);
330 status_icon_update_notification (icon);
332 if (!priv->blink_timeout && priv->showing_event_icon) {
333 priv->blink_timeout = g_timeout_add (BLINK_TIMEOUT,
334 (GSourceFunc) status_icon_blink_timeout_cb,
340 status_icon_event_removed_cb (EmpathyEventManager *manager,
342 EmpathyStatusIcon *icon)
344 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
346 if (event != priv->event) {
350 priv->event = empathy_event_manager_get_top_event (priv->event_manager);
352 status_icon_update_tooltip (icon);
353 status_icon_update_icon (icon);
355 /* update notification anyway, as it's safe and we might have been
356 * changed presence in the meanwhile
358 status_icon_update_notification (icon);
360 if (!priv->event && priv->blink_timeout) {
361 g_source_remove (priv->blink_timeout);
362 priv->blink_timeout = 0;
367 status_icon_event_updated_cb (EmpathyEventManager *manager,
369 EmpathyStatusIcon *icon)
371 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
373 if (event != priv->event) {
377 if (empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
378 status_icon_update_notification (icon);
381 status_icon_update_tooltip (icon);
385 status_icon_set_visibility (EmpathyStatusIcon *icon,
389 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
392 g_settings_set_boolean (priv->gsettings_ui,
393 EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN,
398 empathy_window_iconify (priv->window, priv->icon);
400 empathy_window_present (GTK_WINDOW (priv->window));
405 status_icon_notify_visibility_cb (GSettings *gsettings,
409 EmpathyStatusIcon *icon = user_data;
410 gboolean hidden = FALSE;
412 hidden = g_settings_get_boolean (gsettings, key);
413 status_icon_set_visibility (icon, !hidden, FALSE);
417 status_icon_toggle_visibility (EmpathyStatusIcon *icon)
419 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
422 visible = gtk_window_is_active (priv->window);
423 status_icon_set_visibility (icon, !visible, TRUE);
427 status_icon_presence_changed_cb (EmpathyStatusIcon *icon)
429 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
431 status_icon_update_icon (icon);
432 status_icon_update_tooltip (icon);
434 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
435 /* dismiss the outstanding notification if present */
437 if (priv->notification) {
438 notify_notification_close (priv->notification, NULL);
439 priv->notification = NULL;
445 status_icon_delete_event_cb (GtkWidget *widget,
447 EmpathyStatusIcon *icon)
449 status_icon_set_visibility (icon, FALSE, TRUE);
454 status_icon_key_press_event_cb (GtkWidget *window,
456 EmpathyStatusIcon *icon)
458 if (event->keyval == GDK_Escape) {
459 status_icon_set_visibility (icon, FALSE, TRUE);
465 status_icon_activate_cb (GtkStatusIcon *status_icon,
466 EmpathyStatusIcon *icon)
468 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
470 DEBUG ("%s", priv->event ? "event" : "toggle");
473 empathy_event_activate (priv->event);
475 status_icon_toggle_visibility (icon);
480 status_icon_show_hide_window_cb (GtkToggleAction *action,
481 EmpathyStatusIcon *icon)
485 visible = gtk_toggle_action_get_active (action);
486 status_icon_set_visibility (icon, visible, TRUE);
490 status_icon_new_message_cb (GtkAction *action,
491 EmpathyStatusIcon *icon)
493 empathy_new_message_dialog_show (NULL);
497 status_icon_new_call_cb (GtkAction *action,
498 EmpathyStatusIcon *icon)
500 empathy_new_call_dialog_show (NULL);
504 status_icon_quit_cb (GtkAction *action,
505 EmpathyStatusIcon *icon)
511 status_icon_popup_menu_cb (GtkStatusIcon *status_icon,
514 EmpathyStatusIcon *icon)
516 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
517 GtkWidget *menu_item;
521 show = empathy_window_get_is_visible (GTK_WINDOW (priv->window));
523 g_signal_handlers_block_by_func (priv->show_window_item,
524 status_icon_show_hide_window_cb,
526 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_window_item),
528 g_signal_handlers_unblock_by_func (priv->show_window_item,
529 status_icon_show_hide_window_cb,
532 menu_item = gtk_ui_manager_get_widget (priv->ui_manager, "/menu/status");
533 submenu = empathy_presence_chooser_create_menu ();
534 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
536 gtk_menu_popup (GTK_MENU (priv->popup_menu),
538 gtk_status_icon_position_menu,
545 status_icon_create_menu (EmpathyStatusIcon *icon)
547 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
551 filename = empathy_file_lookup ("empathy-status-icon.ui", "src");
552 gui = empathy_builder_get_file (filename,
553 "ui_manager", &priv->ui_manager,
554 "menu", &priv->popup_menu,
555 "show_list", &priv->show_window_item,
556 "new_message", &priv->new_message_item,
557 "status", &priv->status_item,
561 empathy_builder_connect (gui, icon,
562 "show_list", "toggled", status_icon_show_hide_window_cb,
563 "new_message", "activate", status_icon_new_message_cb,
564 "new_call", "activate", status_icon_new_call_cb,
565 "quit", "activate", status_icon_quit_cb,
568 g_object_ref (priv->ui_manager);
569 g_object_unref (gui);
573 status_icon_status_changed_cb (TpAccount *account,
574 TpConnectionStatus current,
575 TpConnectionStatus previous,
576 TpConnectionStatusReason reason,
577 gchar *dbus_error_name,
579 EmpathyStatusIcon *icon)
581 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
583 gtk_action_set_sensitive (priv->new_message_item,
584 empathy_account_manager_get_accounts_connected (NULL));
588 status_icon_finalize (GObject *object)
590 EmpathyStatusIconPriv *priv = GET_PRIV (object);
592 if (priv->blink_timeout) {
593 g_source_remove (priv->blink_timeout);
596 if (priv->notification) {
597 notify_notification_close (priv->notification, NULL);
598 g_object_unref (priv->notification);
599 priv->notification = NULL;
602 g_object_unref (priv->icon);
603 g_object_unref (priv->account_manager);
604 g_object_unref (priv->event_manager);
605 g_object_unref (priv->ui_manager);
606 g_object_unref (priv->notify_mgr);
607 g_object_unref (priv->gsettings_ui);
608 g_object_unref (priv->window);
612 empathy_status_icon_class_init (EmpathyStatusIconClass *klass)
614 GObjectClass *object_class = G_OBJECT_CLASS (klass);
616 object_class->finalize = status_icon_finalize;
618 g_type_class_add_private (object_class, sizeof (EmpathyStatusIconPriv));
622 account_manager_prepared_cb (GObject *source_object,
623 GAsyncResult *result,
627 TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
628 EmpathyStatusIcon *icon = user_data;
629 GError *error = NULL;
631 if (!tp_account_manager_prepare_finish (account_manager, result, &error)) {
632 DEBUG ("Failed to prepare account manager: %s", error->message);
633 g_error_free (error);
637 list = tp_account_manager_get_valid_accounts (account_manager);
638 for (l = list; l != NULL; l = l->next) {
639 tp_g_signal_connect_object (l->data, "status-changed",
640 G_CALLBACK (status_icon_status_changed_cb),
645 status_icon_presence_changed_cb (icon);
649 empathy_status_icon_init (EmpathyStatusIcon *icon)
651 EmpathyStatusIconPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (icon,
652 EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconPriv);
655 priv->icon = gtk_status_icon_new ();
656 priv->account_manager = tp_account_manager_dup ();
657 priv->event_manager = empathy_event_manager_dup_singleton ();
659 tp_account_manager_prepare_async (priv->account_manager, NULL,
660 account_manager_prepared_cb, icon);
662 /* make icon listen and respond to MAIN_WINDOW_HIDDEN changes */
663 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
664 g_signal_connect (priv->gsettings_ui,
665 "changed::" EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN,
666 G_CALLBACK (status_icon_notify_visibility_cb),
669 status_icon_create_menu (icon);
671 g_signal_connect_swapped (priv->account_manager,
672 "most-available-presence-changed",
673 G_CALLBACK (status_icon_presence_changed_cb),
675 g_signal_connect (priv->event_manager, "event-added",
676 G_CALLBACK (status_icon_event_added_cb),
678 g_signal_connect (priv->event_manager, "event-removed",
679 G_CALLBACK (status_icon_event_removed_cb),
681 g_signal_connect (priv->event_manager, "event-updated",
682 G_CALLBACK (status_icon_event_updated_cb),
684 g_signal_connect (priv->icon, "activate",
685 G_CALLBACK (status_icon_activate_cb),
687 g_signal_connect (priv->icon, "popup-menu",
688 G_CALLBACK (status_icon_popup_menu_cb),
691 priv->notification = NULL;
692 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
696 empathy_status_icon_new (GtkWindow *window, gboolean hide_contact_list)
698 EmpathyStatusIconPriv *priv;
699 EmpathyStatusIcon *icon;
700 gboolean should_hide;
702 g_return_val_if_fail (GTK_IS_WINDOW (window), NULL);
704 icon = g_object_new (EMPATHY_TYPE_STATUS_ICON, NULL);
705 priv = GET_PRIV (icon);
707 priv->window = g_object_ref (window);
709 g_signal_connect_after (priv->window, "key-press-event",
710 G_CALLBACK (status_icon_key_press_event_cb),
713 g_signal_connect (priv->window, "delete-event",
714 G_CALLBACK (status_icon_delete_event_cb),
717 should_hide = g_settings_get_boolean (priv->gsettings_ui,
718 EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN);
720 if (gtk_window_is_active (priv->window) == should_hide) {
721 status_icon_set_visibility (icon, !should_hide, FALSE);