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>
37
#include <telepathy-yell/telepathy-yell.h>
39
#include <libempathy/empathy-gsettings.h>
40
#include <libempathy/empathy-utils.h>
41
#include <libempathy/empathy-tp-streamed-media.h>
43
#include <libempathy-gtk/empathy-presence-chooser.h>
44
#include <libempathy-gtk/empathy-ui-utils.h>
45
#include <libempathy-gtk/empathy-images.h>
46
#include <libempathy-gtk/empathy-new-message-dialog.h>
47
#include <libempathy-gtk/empathy-new-call-dialog.h>
48
#include <libempathy-gtk/empathy-notify-manager.h>
50
#include "empathy-accounts-dialog.h"
51
#include "empathy-status-icon.h"
52
#include "empathy-preferences.h"
53
#include "empathy-event-manager.h"
55
#define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER
56
#include <libempathy/empathy-debug.h>
58
/* Number of ms to wait when blinking */
59
#define BLINK_TIMEOUT 500
61
#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyStatusIcon)
64
TpAccountManager *account_manager;
65
EmpathyNotifyManager *notify_mgr;
66
gboolean showing_event_icon;
68
EmpathyEventManager *event_manager;
70
NotifyNotification *notification;
71
GSettings *gsettings_ui;
74
GtkUIManager *ui_manager;
75
GtkWidget *popup_menu;
76
GtkAction *show_window_item;
77
GtkAction *new_message_item;
78
GtkAction *status_item;
79
} EmpathyStatusIconPriv;
81
G_DEFINE_TYPE (EmpathyStatusIcon, empathy_status_icon, G_TYPE_OBJECT);
84
status_icon_notification_closed_cb (NotifyNotification *notification,
85
EmpathyStatusIcon *icon)
87
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
89
g_object_unref (notification);
91
if (priv->notification == notification) {
92
priv->notification = NULL;
99
/* inhibit other updates for this event */
100
empathy_event_inhibit_updates (priv->event);
104
notification_close_helper (EmpathyStatusIconPriv *priv)
106
if (priv->notification != NULL) {
107
notify_notification_close (priv->notification, NULL);
108
priv->notification = NULL;
113
notification_approve_no_video_cb (NotifyNotification *notification,
115
EmpathyStatusIcon *icon)
117
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
120
tpy_call_channel_send_video (
121
TPY_CALL_CHANNEL (priv->event->handler_instance),
123
empathy_event_approve (priv->event);
128
notification_approve_cb (NotifyNotification *notification,
130
EmpathyStatusIcon *icon)
132
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
135
empathy_event_approve (priv->event);
139
notification_decline_cb (NotifyNotification *notification,
141
EmpathyStatusIcon *icon)
143
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
146
empathy_event_decline (priv->event);
150
add_notification_actions (EmpathyStatusIcon *self,
151
NotifyNotification *notification)
153
EmpathyStatusIconPriv *priv = GET_PRIV (self);
156
switch (priv->event->type) {
157
case EMPATHY_EVENT_TYPE_CHAT:
158
notify_notification_add_action (notification,
159
"respond", _("Respond"), (NotifyActionCallback) notification_approve_cb,
163
case EMPATHY_EVENT_TYPE_VOIP:
164
case EMPATHY_EVENT_TYPE_CALL:
165
if (priv->event->type == EMPATHY_EVENT_TYPE_VOIP)
166
video = empathy_tp_streamed_media_has_initial_video (
167
EMPATHY_TP_STREAMED_MEDIA (priv->event->handler_instance));
169
video = tpy_call_channel_has_initial_video (
170
TPY_CALL_CHANNEL (priv->event->handler_instance));
172
notify_notification_add_action (notification,
173
"reject", _("Reject"), (NotifyActionCallback) notification_decline_cb,
177
if (video && priv->event->type == EMPATHY_EVENT_TYPE_CALL)
178
notify_notification_add_action (notification,
179
"answer-no-video", _("Answer"), (NotifyActionCallback)
180
notification_approve_no_video_cb,
183
notify_notification_add_action (notification,
184
"answer", video ? _("Answer with video") : _("Answer"),
185
(NotifyActionCallback) notification_approve_cb,
189
case EMPATHY_EVENT_TYPE_TRANSFER:
190
case EMPATHY_EVENT_TYPE_INVITATION:
191
notify_notification_add_action (notification,
192
"decline", _("Decline"), (NotifyActionCallback) notification_decline_cb,
195
notify_notification_add_action (notification,
196
"accept", _("Accept"), (NotifyActionCallback) notification_approve_cb,
206
status_icon_update_notification (EmpathyStatusIcon *icon)
208
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
209
GdkPixbuf *pixbuf = NULL;
211
if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
212
/* always close the notification if this happens */
213
notification_close_helper (priv);
218
gchar *message_esc = NULL;
219
gboolean has_x_canonical_append;
220
NotifyNotification *notification = priv->notification;
222
if (priv->event->message != NULL)
223
message_esc = g_markup_escape_text (priv->event->message, -1);
225
has_x_canonical_append =
226
empathy_notify_manager_has_capability (priv->notify_mgr,
227
EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
229
if (notification != NULL && ! has_x_canonical_append) {
230
/* if the notification server supports x-canonical-append, it is
231
better to not use notify_notification_update to avoid
232
overwriting the current notification message */
233
notify_notification_update (notification,
234
priv->event->header, message_esc,
237
/* if the notification server supports x-canonical-append,
238
the hint will be added, so that the message from the
239
just created notification will be automatically appended
240
to an existing notification with the same title.
241
In this way the previous message will not be lost: the new
242
message will appear below it, in the same notification */
243
notification = notify_notification_new
244
(priv->event->header, message_esc, NULL);
246
if (priv->notification == NULL) {
247
priv->notification = notification;
250
notify_notification_set_timeout (notification,
251
NOTIFY_EXPIRES_DEFAULT);
253
if (has_x_canonical_append) {
254
notify_notification_set_hint_string (notification,
255
EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
258
if (empathy_notify_manager_has_capability (priv->notify_mgr,
259
EMPATHY_NOTIFY_MANAGER_CAP_ACTIONS))
260
add_notification_actions (icon, notification);
262
g_signal_connect (notification, "closed",
263
G_CALLBACK (status_icon_notification_closed_cb), icon);
266
pixbuf = empathy_notify_manager_get_pixbuf_for_notification (
267
priv->notify_mgr, priv->event->contact,
268
priv->event->icon_name);
270
if (pixbuf != NULL) {
271
notify_notification_set_icon_from_pixbuf (notification, pixbuf);
272
g_object_unref (pixbuf);
275
notify_notification_show (notification, NULL);
277
g_free (message_esc);
279
notification_close_helper (priv);
284
status_icon_update_tooltip (EmpathyStatusIcon *icon)
286
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
289
gchar *tooltip = NULL;
291
if (priv->event->message != NULL)
292
tooltip = g_markup_printf_escaped ("<i>%s</i>\n%s",
294
priv->event->message);
296
tooltip = g_markup_printf_escaped ("<i>%s</i>",
297
priv->event->header);
298
gtk_status_icon_set_tooltip_markup (priv->icon, tooltip);
301
TpConnectionPresenceType type;
304
type = tp_account_manager_get_most_available_presence (
305
priv->account_manager, NULL, &msg);
307
if (!EMP_STR_EMPTY (msg)) {
308
gtk_status_icon_set_tooltip_text (priv->icon, msg);
311
gtk_status_icon_set_tooltip_text (priv->icon,
312
empathy_presence_get_default_message (type));
320
status_icon_update_icon (EmpathyStatusIcon *icon)
322
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
323
const gchar *icon_name;
325
if (priv->event && priv->showing_event_icon) {
326
icon_name = priv->event->icon_name;
328
TpConnectionPresenceType state;
330
state = tp_account_manager_get_most_available_presence (
331
priv->account_manager, NULL, NULL);
333
/* An unset presence type here doesn't make sense. Force it
335
if (state == TP_CONNECTION_PRESENCE_TYPE_UNSET) {
336
state = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
339
icon_name = empathy_icon_name_for_presence (state);
342
if (icon_name != NULL)
343
gtk_status_icon_set_from_icon_name (priv->icon, icon_name);
347
status_icon_blink_timeout_cb (EmpathyStatusIcon *icon)
349
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
351
priv->showing_event_icon = !priv->showing_event_icon;
352
status_icon_update_icon (icon);
357
status_icon_event_added_cb (EmpathyEventManager *manager,
359
EmpathyStatusIcon *icon)
361
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
367
if (event->type == EMPATHY_EVENT_TYPE_AUTH) {
371
DEBUG ("New event %p", event);
374
if (event->must_ack || event->type == EMPATHY_EVENT_TYPE_AUTH) {
375
priv->showing_event_icon = TRUE;
376
status_icon_update_icon (icon);
377
status_icon_update_tooltip (icon);
379
status_icon_update_notification (icon);
381
if (!priv->blink_timeout && priv->showing_event_icon) {
382
priv->blink_timeout = g_timeout_add (BLINK_TIMEOUT,
383
(GSourceFunc) status_icon_blink_timeout_cb,
389
status_icon_event_removed_cb (EmpathyEventManager *manager,
391
EmpathyStatusIcon *icon)
393
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
395
if (event != priv->event) {
399
if (event->type == EMPATHY_EVENT_TYPE_AUTH) {
403
priv->event = empathy_event_manager_get_top_event (priv->event_manager);
405
status_icon_update_tooltip (icon);
406
status_icon_update_icon (icon);
408
/* update notification anyway, as it's safe and we might have been
409
* changed presence in the meanwhile
411
status_icon_update_notification (icon);
413
if (!priv->event && priv->blink_timeout) {
414
g_source_remove (priv->blink_timeout);
415
priv->blink_timeout = 0;
420
status_icon_event_updated_cb (EmpathyEventManager *manager,
422
EmpathyStatusIcon *icon)
424
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
426
if (event != priv->event) {
430
if (event->type == EMPATHY_EVENT_TYPE_AUTH) {
434
if (empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
435
status_icon_update_notification (icon);
438
status_icon_update_tooltip (icon);
442
status_icon_set_visibility (EmpathyStatusIcon *icon,
446
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
449
g_settings_set_boolean (priv->gsettings_ui,
450
EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN,
455
empathy_window_iconify (priv->window, priv->icon);
457
empathy_window_present (GTK_WINDOW (priv->window));
462
status_icon_notify_visibility_cb (GSettings *gsettings,
466
EmpathyStatusIcon *icon = user_data;
467
gboolean hidden = FALSE;
469
hidden = g_settings_get_boolean (gsettings, key);
470
status_icon_set_visibility (icon, !hidden, FALSE);
474
status_icon_toggle_visibility (EmpathyStatusIcon *icon)
476
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
479
visible = gtk_window_is_active (priv->window);
480
status_icon_set_visibility (icon, !visible, TRUE);
484
status_icon_presence_changed_cb (EmpathyStatusIcon *icon)
486
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
488
status_icon_update_icon (icon);
489
status_icon_update_tooltip (icon);
491
if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
492
/* dismiss the outstanding notification if present */
494
if (priv->notification) {
495
notify_notification_close (priv->notification, NULL);
496
priv->notification = NULL;
502
status_icon_delete_event_cb (GtkWidget *widget,
504
EmpathyStatusIcon *icon)
506
status_icon_set_visibility (icon, FALSE, TRUE);
511
status_icon_key_press_event_cb (GtkWidget *window,
513
EmpathyStatusIcon *icon)
515
if (event->keyval == GDK_Escape) {
516
status_icon_set_visibility (icon, FALSE, TRUE);
522
status_icon_activate_cb (GtkStatusIcon *status_icon,
523
EmpathyStatusIcon *icon)
525
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
527
DEBUG ("%s", priv->event ? "event" : "toggle");
530
empathy_event_activate (priv->event);
532
status_icon_toggle_visibility (icon);
537
status_icon_show_hide_window_cb (GtkToggleAction *action,
538
EmpathyStatusIcon *icon)
542
visible = gtk_toggle_action_get_active (action);
543
status_icon_set_visibility (icon, visible, TRUE);
547
status_icon_new_message_cb (GtkAction *action,
548
EmpathyStatusIcon *icon)
550
empathy_new_message_dialog_show (NULL);
554
status_icon_new_call_cb (GtkAction *action,
555
EmpathyStatusIcon *icon)
557
empathy_new_call_dialog_show (NULL);
561
status_icon_quit_cb (GtkAction *action,
562
EmpathyStatusIcon *icon)
568
status_icon_popup_menu_cb (GtkStatusIcon *status_icon,
571
EmpathyStatusIcon *icon)
573
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
574
GtkWidget *menu_item;
578
show = empathy_window_get_is_visible (GTK_WINDOW (priv->window));
580
g_signal_handlers_block_by_func (priv->show_window_item,
581
status_icon_show_hide_window_cb,
583
gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_window_item),
585
g_signal_handlers_unblock_by_func (priv->show_window_item,
586
status_icon_show_hide_window_cb,
589
menu_item = gtk_ui_manager_get_widget (priv->ui_manager, "/menu/status");
590
submenu = empathy_presence_chooser_create_menu ();
591
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
593
gtk_menu_popup (GTK_MENU (priv->popup_menu),
595
gtk_status_icon_position_menu,
602
status_icon_create_menu (EmpathyStatusIcon *icon)
604
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
608
filename = empathy_file_lookup ("empathy-status-icon.ui", "src");
609
gui = empathy_builder_get_file (filename,
610
"ui_manager", &priv->ui_manager,
611
"menu", &priv->popup_menu,
612
"show_list", &priv->show_window_item,
613
"new_message", &priv->new_message_item,
614
"status", &priv->status_item,
618
empathy_builder_connect (gui, icon,
619
"show_list", "toggled", status_icon_show_hide_window_cb,
620
"new_message", "activate", status_icon_new_message_cb,
621
"new_call", "activate", status_icon_new_call_cb,
622
"quit", "activate", status_icon_quit_cb,
625
g_object_ref (priv->ui_manager);
626
g_object_unref (gui);
630
status_icon_status_changed_cb (TpAccount *account,
631
TpConnectionStatus current,
632
TpConnectionStatus previous,
633
TpConnectionStatusReason reason,
634
gchar *dbus_error_name,
636
EmpathyStatusIcon *icon)
638
EmpathyStatusIconPriv *priv = GET_PRIV (icon);
640
gtk_action_set_sensitive (priv->new_message_item,
641
empathy_account_manager_get_accounts_connected (NULL));
645
status_icon_finalize (GObject *object)
647
EmpathyStatusIconPriv *priv = GET_PRIV (object);
649
if (priv->blink_timeout) {
650
g_source_remove (priv->blink_timeout);
653
if (priv->notification) {
654
notify_notification_close (priv->notification, NULL);
655
g_object_unref (priv->notification);
656
priv->notification = NULL;
659
g_object_unref (priv->icon);
660
g_object_unref (priv->account_manager);
661
g_object_unref (priv->event_manager);
662
g_object_unref (priv->ui_manager);
663
g_object_unref (priv->notify_mgr);
664
g_object_unref (priv->gsettings_ui);
665
g_object_unref (priv->window);
669
empathy_status_icon_class_init (EmpathyStatusIconClass *klass)
671
GObjectClass *object_class = G_OBJECT_CLASS (klass);
673
object_class->finalize = status_icon_finalize;
675
g_type_class_add_private (object_class, sizeof (EmpathyStatusIconPriv));
679
account_manager_prepared_cb (GObject *source_object,
680
GAsyncResult *result,
684
TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
685
EmpathyStatusIcon *icon = user_data;
686
GError *error = NULL;
688
if (!tp_account_manager_prepare_finish (account_manager, result, &error)) {
689
DEBUG ("Failed to prepare account manager: %s", error->message);
690
g_error_free (error);
694
list = tp_account_manager_get_valid_accounts (account_manager);
695
for (l = list; l != NULL; l = l->next) {
696
tp_g_signal_connect_object (l->data, "status-changed",
697
G_CALLBACK (status_icon_status_changed_cb),
702
status_icon_presence_changed_cb (icon);
706
empathy_status_icon_init (EmpathyStatusIcon *icon)
708
EmpathyStatusIconPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (icon,
709
EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconPriv);
712
priv->icon = gtk_status_icon_new ();
713
priv->account_manager = tp_account_manager_dup ();
714
priv->event_manager = empathy_event_manager_dup_singleton ();
716
tp_account_manager_prepare_async (priv->account_manager, NULL,
717
account_manager_prepared_cb, icon);
719
/* make icon listen and respond to MAIN_WINDOW_HIDDEN changes */
720
priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
721
g_signal_connect (priv->gsettings_ui,
722
"changed::" EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN,
723
G_CALLBACK (status_icon_notify_visibility_cb),
726
status_icon_create_menu (icon);
728
g_signal_connect_swapped (priv->account_manager,
729
"most-available-presence-changed",
730
G_CALLBACK (status_icon_presence_changed_cb),
732
g_signal_connect (priv->event_manager, "event-added",
733
G_CALLBACK (status_icon_event_added_cb),
735
g_signal_connect (priv->event_manager, "event-removed",
736
G_CALLBACK (status_icon_event_removed_cb),
738
g_signal_connect (priv->event_manager, "event-updated",
739
G_CALLBACK (status_icon_event_updated_cb),
741
g_signal_connect (priv->icon, "activate",
742
G_CALLBACK (status_icon_activate_cb),
744
g_signal_connect (priv->icon, "popup-menu",
745
G_CALLBACK (status_icon_popup_menu_cb),
748
priv->notification = NULL;
749
priv->notify_mgr = empathy_notify_manager_dup_singleton ();
753
empathy_status_icon_new (GtkWindow *window, gboolean hide_contact_list)
755
EmpathyStatusIconPriv *priv;
756
EmpathyStatusIcon *icon;
757
gboolean should_hide;
759
g_return_val_if_fail (GTK_IS_WINDOW (window), NULL);
761
icon = g_object_new (EMPATHY_TYPE_STATUS_ICON, NULL);
762
priv = GET_PRIV (icon);
764
priv->window = g_object_ref (window);
766
g_signal_connect_after (priv->window, "key-press-event",
767
G_CALLBACK (status_icon_key_press_event_cb),
770
g_signal_connect (priv->window, "delete-event",
771
G_CALLBACK (status_icon_delete_event_cb),
774
if (!hide_contact_list) {
775
should_hide = g_settings_get_boolean (priv->gsettings_ui,
776
EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN);
781
status_icon_set_visibility (icon, !should_hide, FALSE);