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 add_notification_actions (EmpathyStatusIcon *self,
123 NotifyNotification *notification)
125 EmpathyStatusIconPriv *priv = GET_PRIV (self);
127 switch (priv->event->type) {
128 case EMPATHY_EVENT_TYPE_CHAT:
129 notify_notification_add_action (notification,
130 "respond", _("Respond"), (NotifyActionCallback) notification_approve_cb,
140 status_icon_update_notification (EmpathyStatusIcon *icon)
142 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
143 GdkPixbuf *pixbuf = NULL;
145 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
146 /* always close the notification if this happens */
147 notification_close_helper (priv);
152 gchar *message_esc = NULL;
153 gboolean has_x_canonical_append;
154 NotifyNotification *notification = priv->notification;
156 if (priv->event->message != NULL)
157 message_esc = g_markup_escape_text (priv->event->message, -1);
159 has_x_canonical_append =
160 empathy_notify_manager_has_capability (priv->notify_mgr,
161 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
163 if (notification != NULL && ! has_x_canonical_append) {
164 /* if the notification server supports x-canonical-append, it is
165 better to not use notify_notification_update to avoid
166 overwriting the current notification message */
167 notify_notification_update (notification,
168 priv->event->header, message_esc,
171 /* if the notification server supports x-canonical-append,
172 the hint will be added, so that the message from the
173 just created notification will be automatically appended
174 to an existing notification with the same title.
175 In this way the previous message will not be lost: the new
176 message will appear below it, in the same notification */
177 notification = notify_notification_new_with_status_icon
178 (priv->event->header, message_esc, NULL, priv->icon);
180 if (priv->notification == NULL) {
181 priv->notification = notification;
184 notify_notification_set_timeout (notification,
185 NOTIFY_EXPIRES_DEFAULT);
187 if (has_x_canonical_append) {
188 notify_notification_set_hint_string (notification,
189 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
192 if (empathy_notify_manager_has_capability (priv->notify_mgr,
193 EMPATHY_NOTIFY_MANAGER_CAP_ACTIONS))
194 add_notification_actions (icon, notification);
196 g_signal_connect (notification, "closed",
197 G_CALLBACK (status_icon_notification_closed_cb), icon);
200 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (
201 priv->notify_mgr, priv->event->contact,
202 priv->event->icon_name);
204 if (pixbuf != NULL) {
205 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
206 g_object_unref (pixbuf);
209 notify_notification_show (notification, NULL);
211 g_free (message_esc);
213 notification_close_helper (priv);
218 status_icon_update_tooltip (EmpathyStatusIcon *icon)
220 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
223 gchar *tooltip = NULL;
225 if (priv->event->message != NULL)
226 tooltip = g_markup_printf_escaped ("<i>%s</i>\n%s",
228 priv->event->message);
230 tooltip = g_markup_printf_escaped ("<i>%s</i>",
231 priv->event->header);
232 gtk_status_icon_set_tooltip_markup (priv->icon, tooltip);
235 TpConnectionPresenceType type;
238 type = tp_account_manager_get_most_available_presence (
239 priv->account_manager, NULL, &msg);
241 if (!EMP_STR_EMPTY (msg)) {
242 gtk_status_icon_set_tooltip_text (priv->icon, msg);
245 gtk_status_icon_set_tooltip_text (priv->icon,
246 empathy_presence_get_default_message (type));
254 status_icon_update_icon (EmpathyStatusIcon *icon)
256 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
257 const gchar *icon_name;
259 if (priv->event && priv->showing_event_icon) {
260 icon_name = priv->event->icon_name;
262 TpConnectionPresenceType state;
264 state = tp_account_manager_get_most_available_presence (
265 priv->account_manager, NULL, NULL);
267 /* An unset presence type here doesn't make sense. Force it
269 if (state == TP_CONNECTION_PRESENCE_TYPE_UNSET) {
270 state = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
273 icon_name = empathy_icon_name_for_presence (state);
276 if (icon_name != NULL)
277 gtk_status_icon_set_from_icon_name (priv->icon, icon_name);
281 status_icon_blink_timeout_cb (EmpathyStatusIcon *icon)
283 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
285 priv->showing_event_icon = !priv->showing_event_icon;
286 status_icon_update_icon (icon);
291 status_icon_event_added_cb (EmpathyEventManager *manager,
293 EmpathyStatusIcon *icon)
295 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
301 DEBUG ("New event %p", event);
304 if (event->must_ack) {
305 priv->showing_event_icon = TRUE;
306 status_icon_update_icon (icon);
307 status_icon_update_tooltip (icon);
309 status_icon_update_notification (icon);
311 if (!priv->blink_timeout && priv->showing_event_icon) {
312 priv->blink_timeout = g_timeout_add (BLINK_TIMEOUT,
313 (GSourceFunc) status_icon_blink_timeout_cb,
319 status_icon_event_removed_cb (EmpathyEventManager *manager,
321 EmpathyStatusIcon *icon)
323 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
325 if (event != priv->event) {
329 priv->event = empathy_event_manager_get_top_event (priv->event_manager);
331 status_icon_update_tooltip (icon);
332 status_icon_update_icon (icon);
334 /* update notification anyway, as it's safe and we might have been
335 * changed presence in the meanwhile
337 status_icon_update_notification (icon);
339 if (!priv->event && priv->blink_timeout) {
340 g_source_remove (priv->blink_timeout);
341 priv->blink_timeout = 0;
346 status_icon_event_updated_cb (EmpathyEventManager *manager,
348 EmpathyStatusIcon *icon)
350 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
352 if (event != priv->event) {
356 if (empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
357 status_icon_update_notification (icon);
360 status_icon_update_tooltip (icon);
364 status_icon_set_visibility (EmpathyStatusIcon *icon,
368 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
371 g_settings_set_boolean (priv->gsettings_ui,
372 EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN,
377 empathy_window_iconify (priv->window, priv->icon);
379 empathy_window_present (GTK_WINDOW (priv->window));
384 status_icon_notify_visibility_cb (GSettings *gsettings,
388 EmpathyStatusIcon *icon = user_data;
389 gboolean hidden = FALSE;
391 hidden = g_settings_get_boolean (gsettings, key);
392 status_icon_set_visibility (icon, !hidden, FALSE);
396 status_icon_toggle_visibility (EmpathyStatusIcon *icon)
398 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
401 visible = gtk_window_is_active (priv->window);
402 status_icon_set_visibility (icon, !visible, TRUE);
406 status_icon_presence_changed_cb (EmpathyStatusIcon *icon)
408 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
410 status_icon_update_icon (icon);
411 status_icon_update_tooltip (icon);
413 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
414 /* dismiss the outstanding notification if present */
416 if (priv->notification) {
417 notify_notification_close (priv->notification, NULL);
418 priv->notification = NULL;
424 status_icon_delete_event_cb (GtkWidget *widget,
426 EmpathyStatusIcon *icon)
428 status_icon_set_visibility (icon, FALSE, TRUE);
433 status_icon_key_press_event_cb (GtkWidget *window,
435 EmpathyStatusIcon *icon)
437 if (event->keyval == GDK_Escape) {
438 status_icon_set_visibility (icon, FALSE, TRUE);
444 status_icon_activate_cb (GtkStatusIcon *status_icon,
445 EmpathyStatusIcon *icon)
447 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
449 DEBUG ("%s", priv->event ? "event" : "toggle");
452 empathy_event_activate (priv->event);
454 status_icon_toggle_visibility (icon);
459 status_icon_show_hide_window_cb (GtkToggleAction *action,
460 EmpathyStatusIcon *icon)
464 visible = gtk_toggle_action_get_active (action);
465 status_icon_set_visibility (icon, visible, TRUE);
469 status_icon_new_message_cb (GtkAction *action,
470 EmpathyStatusIcon *icon)
472 empathy_new_message_dialog_show (NULL);
476 status_icon_new_call_cb (GtkAction *action,
477 EmpathyStatusIcon *icon)
479 empathy_new_call_dialog_show (NULL);
483 status_icon_quit_cb (GtkAction *action,
484 EmpathyStatusIcon *icon)
490 status_icon_popup_menu_cb (GtkStatusIcon *status_icon,
493 EmpathyStatusIcon *icon)
495 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
496 GtkWidget *menu_item;
500 show = empathy_window_get_is_visible (GTK_WINDOW (priv->window));
502 g_signal_handlers_block_by_func (priv->show_window_item,
503 status_icon_show_hide_window_cb,
505 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_window_item),
507 g_signal_handlers_unblock_by_func (priv->show_window_item,
508 status_icon_show_hide_window_cb,
511 menu_item = gtk_ui_manager_get_widget (priv->ui_manager, "/menu/status");
512 submenu = empathy_presence_chooser_create_menu ();
513 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
515 gtk_menu_popup (GTK_MENU (priv->popup_menu),
517 gtk_status_icon_position_menu,
524 status_icon_create_menu (EmpathyStatusIcon *icon)
526 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
530 filename = empathy_file_lookup ("empathy-status-icon.ui", "src");
531 gui = empathy_builder_get_file (filename,
532 "ui_manager", &priv->ui_manager,
533 "menu", &priv->popup_menu,
534 "show_list", &priv->show_window_item,
535 "new_message", &priv->new_message_item,
536 "status", &priv->status_item,
540 empathy_builder_connect (gui, icon,
541 "show_list", "toggled", status_icon_show_hide_window_cb,
542 "new_message", "activate", status_icon_new_message_cb,
543 "new_call", "activate", status_icon_new_call_cb,
544 "quit", "activate", status_icon_quit_cb,
547 g_object_ref (priv->ui_manager);
548 g_object_unref (gui);
552 status_icon_status_changed_cb (TpAccount *account,
553 TpConnectionStatus current,
554 TpConnectionStatus previous,
555 TpConnectionStatusReason reason,
556 gchar *dbus_error_name,
558 EmpathyStatusIcon *icon)
560 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
562 gtk_action_set_sensitive (priv->new_message_item,
563 empathy_account_manager_get_accounts_connected (NULL));
567 status_icon_finalize (GObject *object)
569 EmpathyStatusIconPriv *priv = GET_PRIV (object);
571 if (priv->blink_timeout) {
572 g_source_remove (priv->blink_timeout);
575 if (priv->notification) {
576 notify_notification_close (priv->notification, NULL);
577 g_object_unref (priv->notification);
578 priv->notification = NULL;
581 g_object_unref (priv->icon);
582 g_object_unref (priv->account_manager);
583 g_object_unref (priv->event_manager);
584 g_object_unref (priv->ui_manager);
585 g_object_unref (priv->notify_mgr);
586 g_object_unref (priv->gsettings_ui);
587 g_object_unref (priv->window);
591 empathy_status_icon_class_init (EmpathyStatusIconClass *klass)
593 GObjectClass *object_class = G_OBJECT_CLASS (klass);
595 object_class->finalize = status_icon_finalize;
597 g_type_class_add_private (object_class, sizeof (EmpathyStatusIconPriv));
601 account_manager_prepared_cb (GObject *source_object,
602 GAsyncResult *result,
606 TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
607 EmpathyStatusIcon *icon = user_data;
608 GError *error = NULL;
610 if (!tp_account_manager_prepare_finish (account_manager, result, &error)) {
611 DEBUG ("Failed to prepare account manager: %s", error->message);
612 g_error_free (error);
616 list = tp_account_manager_get_valid_accounts (account_manager);
617 for (l = list; l != NULL; l = l->next) {
618 tp_g_signal_connect_object (l->data, "status-changed",
619 G_CALLBACK (status_icon_status_changed_cb),
624 status_icon_presence_changed_cb (icon);
628 empathy_status_icon_init (EmpathyStatusIcon *icon)
630 EmpathyStatusIconPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (icon,
631 EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconPriv);
634 priv->icon = gtk_status_icon_new ();
635 priv->account_manager = tp_account_manager_dup ();
636 priv->event_manager = empathy_event_manager_dup_singleton ();
638 tp_account_manager_prepare_async (priv->account_manager, NULL,
639 account_manager_prepared_cb, icon);
641 /* make icon listen and respond to MAIN_WINDOW_HIDDEN changes */
642 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
643 g_signal_connect (priv->gsettings_ui,
644 "changed::" EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN,
645 G_CALLBACK (status_icon_notify_visibility_cb),
648 status_icon_create_menu (icon);
650 g_signal_connect_swapped (priv->account_manager,
651 "most-available-presence-changed",
652 G_CALLBACK (status_icon_presence_changed_cb),
654 g_signal_connect (priv->event_manager, "event-added",
655 G_CALLBACK (status_icon_event_added_cb),
657 g_signal_connect (priv->event_manager, "event-removed",
658 G_CALLBACK (status_icon_event_removed_cb),
660 g_signal_connect (priv->event_manager, "event-updated",
661 G_CALLBACK (status_icon_event_updated_cb),
663 g_signal_connect (priv->icon, "activate",
664 G_CALLBACK (status_icon_activate_cb),
666 g_signal_connect (priv->icon, "popup-menu",
667 G_CALLBACK (status_icon_popup_menu_cb),
670 priv->notification = NULL;
671 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
675 empathy_status_icon_new (GtkWindow *window, gboolean hide_contact_list)
677 EmpathyStatusIconPriv *priv;
678 EmpathyStatusIcon *icon;
679 gboolean should_hide;
681 g_return_val_if_fail (GTK_IS_WINDOW (window), NULL);
683 icon = g_object_new (EMPATHY_TYPE_STATUS_ICON, NULL);
684 priv = GET_PRIV (icon);
686 priv->window = g_object_ref (window);
688 g_signal_connect_after (priv->window, "key-press-event",
689 G_CALLBACK (status_icon_key_press_event_cb),
692 g_signal_connect (priv->window, "delete-event",
693 G_CALLBACK (status_icon_delete_event_cb),
696 should_hide = g_settings_get_boolean (priv->gsettings_ui,
697 EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN);
699 if (gtk_window_is_active (priv->window) == should_hide) {
700 status_icon_set_visibility (icon, !should_hide, FALSE);