select_account_once_ready: free the account and ID to avoid leaks
[empathy-mirror.git] / src / empathy-status-icon.c
blob97ab255e66b2f7267d0ae85345b4c2cef2dca038
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
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>
22 #include <config.h>
24 #include <string.h>
26 #include <glib.h>
28 #include <gtk/gtk.h>
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)
60 typedef struct {
61 GtkStatusIcon *icon;
62 TpAccountManager *account_manager;
63 EmpathyNotifyManager *notify_mgr;
64 gboolean showing_event_icon;
65 guint blink_timeout;
66 EmpathyEventManager *event_manager;
67 EmpathyEvent *event;
68 NotifyNotification *notification;
69 GSettings *gsettings_ui;
71 GtkWindow *window;
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);
81 static void
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;
93 if (!priv->event) {
94 return;
97 /* inhibit other updates for this event */
98 empathy_event_inhibit_updates (priv->event);
101 static void
102 notification_close_helper (EmpathyStatusIconPriv *priv)
104 if (priv->notification != NULL) {
105 notify_notification_close (priv->notification, NULL);
106 priv->notification = NULL;
110 static void
111 notification_approve_cb (NotifyNotification *notification,
112 gchar *action,
113 EmpathyStatusIcon *icon)
115 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
117 if (priv->event)
118 empathy_event_approve (priv->event);
121 static void
122 notification_decline_cb (NotifyNotification *notification,
123 gchar *action,
124 EmpathyStatusIcon *icon)
126 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
128 if (priv->event)
129 empathy_event_decline (priv->event);
132 static void
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,
142 self, NULL);
143 break;
145 case EMPATHY_EVENT_TYPE_VOIP:
146 notify_notification_add_action (notification,
147 "reject", _("Reject"), (NotifyActionCallback) notification_decline_cb,
148 self, NULL);
150 notify_notification_add_action (notification,
151 "answer", _("Answer"), (NotifyActionCallback) notification_approve_cb,
152 self, NULL);
153 break;
155 case EMPATHY_EVENT_TYPE_TRANSFER:
156 case EMPATHY_EVENT_TYPE_INVITATION:
157 notify_notification_add_action (notification,
158 "decline", _("Decline"), (NotifyActionCallback) notification_decline_cb,
159 self, NULL);
161 notify_notification_add_action (notification,
162 "accept", _("Accept"), (NotifyActionCallback) notification_approve_cb,
163 self, NULL);
164 break;
166 default:
167 break;
171 static void
172 status_icon_update_notification (EmpathyStatusIcon *icon)
174 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
175 GdkPixbuf *pixbuf = NULL;
177 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
178 /* always close the notification if this happens */
179 notification_close_helper (priv);
180 return;
183 if (priv->event) {
184 gchar *message_esc = NULL;
185 gboolean has_x_canonical_append;
186 NotifyNotification *notification = priv->notification;
188 if (priv->event->message != NULL)
189 message_esc = g_markup_escape_text (priv->event->message, -1);
191 has_x_canonical_append =
192 empathy_notify_manager_has_capability (priv->notify_mgr,
193 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
195 if (notification != NULL && ! has_x_canonical_append) {
196 /* if the notification server supports x-canonical-append, it is
197 better to not use notify_notification_update to avoid
198 overwriting the current notification message */
199 notify_notification_update (notification,
200 priv->event->header, message_esc,
201 NULL);
202 } else {
203 /* if the notification server supports x-canonical-append,
204 the hint will be added, so that the message from the
205 just created notification will be automatically appended
206 to an existing notification with the same title.
207 In this way the previous message will not be lost: the new
208 message will appear below it, in the same notification */
209 notification = notify_notification_new_with_status_icon
210 (priv->event->header, message_esc, NULL, priv->icon);
212 if (priv->notification == NULL) {
213 priv->notification = notification;
216 notify_notification_set_timeout (notification,
217 NOTIFY_EXPIRES_DEFAULT);
219 if (has_x_canonical_append) {
220 notify_notification_set_hint_string (notification,
221 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
224 if (empathy_notify_manager_has_capability (priv->notify_mgr,
225 EMPATHY_NOTIFY_MANAGER_CAP_ACTIONS))
226 add_notification_actions (icon, notification);
228 g_signal_connect (notification, "closed",
229 G_CALLBACK (status_icon_notification_closed_cb), icon);
232 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (
233 priv->notify_mgr, priv->event->contact,
234 priv->event->icon_name);
236 if (pixbuf != NULL) {
237 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
238 g_object_unref (pixbuf);
241 notify_notification_show (notification, NULL);
243 g_free (message_esc);
244 } else {
245 notification_close_helper (priv);
249 static void
250 status_icon_update_tooltip (EmpathyStatusIcon *icon)
252 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
254 if (priv->event) {
255 gchar *tooltip = NULL;
257 if (priv->event->message != NULL)
258 tooltip = g_markup_printf_escaped ("<i>%s</i>\n%s",
259 priv->event->header,
260 priv->event->message);
261 else
262 tooltip = g_markup_printf_escaped ("<i>%s</i>",
263 priv->event->header);
264 gtk_status_icon_set_tooltip_markup (priv->icon, tooltip);
265 g_free (tooltip);
266 } else {
267 TpConnectionPresenceType type;
268 gchar *msg;
270 type = tp_account_manager_get_most_available_presence (
271 priv->account_manager, NULL, &msg);
273 if (!EMP_STR_EMPTY (msg)) {
274 gtk_status_icon_set_tooltip_text (priv->icon, msg);
276 else {
277 gtk_status_icon_set_tooltip_text (priv->icon,
278 empathy_presence_get_default_message (type));
281 g_free (msg);
285 static void
286 status_icon_update_icon (EmpathyStatusIcon *icon)
288 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
289 const gchar *icon_name;
291 if (priv->event && priv->showing_event_icon) {
292 icon_name = priv->event->icon_name;
293 } else {
294 TpConnectionPresenceType state;
296 state = tp_account_manager_get_most_available_presence (
297 priv->account_manager, NULL, NULL);
299 /* An unset presence type here doesn't make sense. Force it
300 * to be offline. */
301 if (state == TP_CONNECTION_PRESENCE_TYPE_UNSET) {
302 state = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
305 icon_name = empathy_icon_name_for_presence (state);
308 if (icon_name != NULL)
309 gtk_status_icon_set_from_icon_name (priv->icon, icon_name);
312 static gboolean
313 status_icon_blink_timeout_cb (EmpathyStatusIcon *icon)
315 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
317 priv->showing_event_icon = !priv->showing_event_icon;
318 status_icon_update_icon (icon);
320 return TRUE;
322 static void
323 status_icon_event_added_cb (EmpathyEventManager *manager,
324 EmpathyEvent *event,
325 EmpathyStatusIcon *icon)
327 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
329 if (priv->event) {
330 return;
333 DEBUG ("New event %p", event);
335 priv->event = event;
336 if (event->must_ack) {
337 priv->showing_event_icon = TRUE;
338 status_icon_update_icon (icon);
339 status_icon_update_tooltip (icon);
341 status_icon_update_notification (icon);
343 if (!priv->blink_timeout && priv->showing_event_icon) {
344 priv->blink_timeout = g_timeout_add (BLINK_TIMEOUT,
345 (GSourceFunc) status_icon_blink_timeout_cb,
346 icon);
350 static void
351 status_icon_event_removed_cb (EmpathyEventManager *manager,
352 EmpathyEvent *event,
353 EmpathyStatusIcon *icon)
355 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
357 if (event != priv->event) {
358 return;
361 priv->event = empathy_event_manager_get_top_event (priv->event_manager);
363 status_icon_update_tooltip (icon);
364 status_icon_update_icon (icon);
366 /* update notification anyway, as it's safe and we might have been
367 * changed presence in the meanwhile
369 status_icon_update_notification (icon);
371 if (!priv->event && priv->blink_timeout) {
372 g_source_remove (priv->blink_timeout);
373 priv->blink_timeout = 0;
377 static void
378 status_icon_event_updated_cb (EmpathyEventManager *manager,
379 EmpathyEvent *event,
380 EmpathyStatusIcon *icon)
382 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
384 if (event != priv->event) {
385 return;
388 if (empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
389 status_icon_update_notification (icon);
392 status_icon_update_tooltip (icon);
395 static void
396 status_icon_set_visibility (EmpathyStatusIcon *icon,
397 gboolean visible,
398 gboolean store)
400 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
402 if (store) {
403 g_settings_set_boolean (priv->gsettings_ui,
404 EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN,
405 !visible);
408 if (!visible) {
409 empathy_window_iconify (priv->window, priv->icon);
410 } else {
411 empathy_window_present (GTK_WINDOW (priv->window));
415 static void
416 status_icon_notify_visibility_cb (GSettings *gsettings,
417 const gchar *key,
418 gpointer user_data)
420 EmpathyStatusIcon *icon = user_data;
421 gboolean hidden = FALSE;
423 hidden = g_settings_get_boolean (gsettings, key);
424 status_icon_set_visibility (icon, !hidden, FALSE);
427 static void
428 status_icon_toggle_visibility (EmpathyStatusIcon *icon)
430 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
431 gboolean visible;
433 visible = gtk_window_is_active (priv->window);
434 status_icon_set_visibility (icon, !visible, TRUE);
437 static void
438 status_icon_presence_changed_cb (EmpathyStatusIcon *icon)
440 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
442 status_icon_update_icon (icon);
443 status_icon_update_tooltip (icon);
445 if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
446 /* dismiss the outstanding notification if present */
448 if (priv->notification) {
449 notify_notification_close (priv->notification, NULL);
450 priv->notification = NULL;
455 static gboolean
456 status_icon_delete_event_cb (GtkWidget *widget,
457 GdkEvent *event,
458 EmpathyStatusIcon *icon)
460 status_icon_set_visibility (icon, FALSE, TRUE);
461 return TRUE;
464 static gboolean
465 status_icon_key_press_event_cb (GtkWidget *window,
466 GdkEventKey *event,
467 EmpathyStatusIcon *icon)
469 if (event->keyval == GDK_Escape) {
470 status_icon_set_visibility (icon, FALSE, TRUE);
472 return FALSE;
475 static void
476 status_icon_activate_cb (GtkStatusIcon *status_icon,
477 EmpathyStatusIcon *icon)
479 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
481 DEBUG ("%s", priv->event ? "event" : "toggle");
483 if (priv->event) {
484 empathy_event_activate (priv->event);
485 } else {
486 status_icon_toggle_visibility (icon);
490 static void
491 status_icon_show_hide_window_cb (GtkToggleAction *action,
492 EmpathyStatusIcon *icon)
494 gboolean visible;
496 visible = gtk_toggle_action_get_active (action);
497 status_icon_set_visibility (icon, visible, TRUE);
500 static void
501 status_icon_new_message_cb (GtkAction *action,
502 EmpathyStatusIcon *icon)
504 empathy_new_message_dialog_show (NULL);
507 static void
508 status_icon_new_call_cb (GtkAction *action,
509 EmpathyStatusIcon *icon)
511 empathy_new_call_dialog_show (NULL);
514 static void
515 status_icon_quit_cb (GtkAction *action,
516 EmpathyStatusIcon *icon)
518 gtk_main_quit ();
521 static void
522 status_icon_popup_menu_cb (GtkStatusIcon *status_icon,
523 guint button,
524 guint activate_time,
525 EmpathyStatusIcon *icon)
527 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
528 GtkWidget *menu_item;
529 GtkWidget *submenu;
530 gboolean show;
532 show = empathy_window_get_is_visible (GTK_WINDOW (priv->window));
534 g_signal_handlers_block_by_func (priv->show_window_item,
535 status_icon_show_hide_window_cb,
536 icon);
537 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_window_item),
538 show);
539 g_signal_handlers_unblock_by_func (priv->show_window_item,
540 status_icon_show_hide_window_cb,
541 icon);
543 menu_item = gtk_ui_manager_get_widget (priv->ui_manager, "/menu/status");
544 submenu = empathy_presence_chooser_create_menu ();
545 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
547 gtk_menu_popup (GTK_MENU (priv->popup_menu),
548 NULL, NULL,
549 gtk_status_icon_position_menu,
550 priv->icon,
551 button,
552 activate_time);
555 static void
556 status_icon_create_menu (EmpathyStatusIcon *icon)
558 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
559 GtkBuilder *gui;
560 gchar *filename;
562 filename = empathy_file_lookup ("empathy-status-icon.ui", "src");
563 gui = empathy_builder_get_file (filename,
564 "ui_manager", &priv->ui_manager,
565 "menu", &priv->popup_menu,
566 "show_list", &priv->show_window_item,
567 "new_message", &priv->new_message_item,
568 "status", &priv->status_item,
569 NULL);
570 g_free (filename);
572 empathy_builder_connect (gui, icon,
573 "show_list", "toggled", status_icon_show_hide_window_cb,
574 "new_message", "activate", status_icon_new_message_cb,
575 "new_call", "activate", status_icon_new_call_cb,
576 "quit", "activate", status_icon_quit_cb,
577 NULL);
579 g_object_ref (priv->ui_manager);
580 g_object_unref (gui);
583 static void
584 status_icon_status_changed_cb (TpAccount *account,
585 TpConnectionStatus current,
586 TpConnectionStatus previous,
587 TpConnectionStatusReason reason,
588 gchar *dbus_error_name,
589 GHashTable *details,
590 EmpathyStatusIcon *icon)
592 EmpathyStatusIconPriv *priv = GET_PRIV (icon);
594 gtk_action_set_sensitive (priv->new_message_item,
595 empathy_account_manager_get_accounts_connected (NULL));
598 static void
599 status_icon_finalize (GObject *object)
601 EmpathyStatusIconPriv *priv = GET_PRIV (object);
603 if (priv->blink_timeout) {
604 g_source_remove (priv->blink_timeout);
607 if (priv->notification) {
608 notify_notification_close (priv->notification, NULL);
609 g_object_unref (priv->notification);
610 priv->notification = NULL;
613 g_object_unref (priv->icon);
614 g_object_unref (priv->account_manager);
615 g_object_unref (priv->event_manager);
616 g_object_unref (priv->ui_manager);
617 g_object_unref (priv->notify_mgr);
618 g_object_unref (priv->gsettings_ui);
619 g_object_unref (priv->window);
622 static void
623 empathy_status_icon_class_init (EmpathyStatusIconClass *klass)
625 GObjectClass *object_class = G_OBJECT_CLASS (klass);
627 object_class->finalize = status_icon_finalize;
629 g_type_class_add_private (object_class, sizeof (EmpathyStatusIconPriv));
632 static void
633 account_manager_prepared_cb (GObject *source_object,
634 GAsyncResult *result,
635 gpointer user_data)
637 GList *list, *l;
638 TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
639 EmpathyStatusIcon *icon = user_data;
640 GError *error = NULL;
642 if (!tp_account_manager_prepare_finish (account_manager, result, &error)) {
643 DEBUG ("Failed to prepare account manager: %s", error->message);
644 g_error_free (error);
645 return;
648 list = tp_account_manager_get_valid_accounts (account_manager);
649 for (l = list; l != NULL; l = l->next) {
650 tp_g_signal_connect_object (l->data, "status-changed",
651 G_CALLBACK (status_icon_status_changed_cb),
652 icon, 0);
654 g_list_free (list);
656 status_icon_presence_changed_cb (icon);
659 static void
660 empathy_status_icon_init (EmpathyStatusIcon *icon)
662 EmpathyStatusIconPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (icon,
663 EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconPriv);
665 icon->priv = priv;
666 priv->icon = gtk_status_icon_new ();
667 priv->account_manager = tp_account_manager_dup ();
668 priv->event_manager = empathy_event_manager_dup_singleton ();
670 tp_account_manager_prepare_async (priv->account_manager, NULL,
671 account_manager_prepared_cb, icon);
673 /* make icon listen and respond to MAIN_WINDOW_HIDDEN changes */
674 priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
675 g_signal_connect (priv->gsettings_ui,
676 "changed::" EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN,
677 G_CALLBACK (status_icon_notify_visibility_cb),
678 icon);
680 status_icon_create_menu (icon);
682 g_signal_connect_swapped (priv->account_manager,
683 "most-available-presence-changed",
684 G_CALLBACK (status_icon_presence_changed_cb),
685 icon);
686 g_signal_connect (priv->event_manager, "event-added",
687 G_CALLBACK (status_icon_event_added_cb),
688 icon);
689 g_signal_connect (priv->event_manager, "event-removed",
690 G_CALLBACK (status_icon_event_removed_cb),
691 icon);
692 g_signal_connect (priv->event_manager, "event-updated",
693 G_CALLBACK (status_icon_event_updated_cb),
694 icon);
695 g_signal_connect (priv->icon, "activate",
696 G_CALLBACK (status_icon_activate_cb),
697 icon);
698 g_signal_connect (priv->icon, "popup-menu",
699 G_CALLBACK (status_icon_popup_menu_cb),
700 icon);
702 priv->notification = NULL;
703 priv->notify_mgr = empathy_notify_manager_dup_singleton ();
706 EmpathyStatusIcon *
707 empathy_status_icon_new (GtkWindow *window, gboolean hide_contact_list)
709 EmpathyStatusIconPriv *priv;
710 EmpathyStatusIcon *icon;
711 gboolean should_hide;
713 g_return_val_if_fail (GTK_IS_WINDOW (window), NULL);
715 icon = g_object_new (EMPATHY_TYPE_STATUS_ICON, NULL);
716 priv = GET_PRIV (icon);
718 priv->window = g_object_ref (window);
720 g_signal_connect_after (priv->window, "key-press-event",
721 G_CALLBACK (status_icon_key_press_event_cb),
722 icon);
724 g_signal_connect (priv->window, "delete-event",
725 G_CALLBACK (status_icon_delete_event_cb),
726 icon);
728 if (!hide_contact_list) {
729 should_hide = g_settings_get_boolean (priv->gsettings_ui,
730 EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN);
731 } else {
732 should_hide = TRUE;
735 status_icon_set_visibility (icon, !should_hide, FALSE);
737 return icon;