| 1 | ## notify.py |
|---|
| 2 | ## |
|---|
| 3 | ## Contributors for this file: |
|---|
| 4 | ## - Yann Le Boulanger <asterix@lagaule.org> |
|---|
| 5 | ## - Nikos Kouremenos <kourem@gmail.com> |
|---|
| 6 | ## - Dimitur Kirov <dkirov@gmail.com> |
|---|
| 7 | ## - Andrew Sayman <lorien420@myrealbox.com> |
|---|
| 8 | ## |
|---|
| 9 | ## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org> |
|---|
| 10 | ## Vincent Hanquez <tab@snarc.org> |
|---|
| 11 | ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org> |
|---|
| 12 | ## Vincent Hanquez <tab@snarc.org> |
|---|
| 13 | ## Nikos Kouremenos <nkour@jabber.org> |
|---|
| 14 | ## Dimitur Kirov <dkirov@gmail.com> |
|---|
| 15 | ## Travis Shirk <travis@pobox.com> |
|---|
| 16 | ## Norman Rasmussen <norman@rasmussen.co.za> |
|---|
| 17 | ## |
|---|
| 18 | ## DBUS/libnotify connection code: |
|---|
| 19 | ## Copyright (C) 2005 by Sebastian Estienne |
|---|
| 20 | ## |
|---|
| 21 | ## This program is free software; you can redistribute it and/or modify |
|---|
| 22 | ## it under the terms of the GNU General Public License as published |
|---|
| 23 | ## by the Free Software Foundation; version 2 only. |
|---|
| 24 | ## |
|---|
| 25 | ## This program is distributed in the hope that it will be useful, |
|---|
| 26 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 27 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 28 | ## GNU General Public License for more details. |
|---|
| 29 | ## |
|---|
| 30 | |
|---|
| 31 | import os |
|---|
| 32 | import sys |
|---|
| 33 | import gajim |
|---|
| 34 | import dialogs |
|---|
| 35 | import gobject |
|---|
| 36 | |
|---|
| 37 | from common import gajim |
|---|
| 38 | from common import exceptions |
|---|
| 39 | from common import i18n |
|---|
| 40 | i18n.init() |
|---|
| 41 | _ = i18n._ |
|---|
| 42 | |
|---|
| 43 | import dbus_support |
|---|
| 44 | if dbus_support.supported: |
|---|
| 45 | import dbus |
|---|
| 46 | if dbus_support.version >= (0, 41, 0): |
|---|
| 47 | import dbus.glib |
|---|
| 48 | import dbus.service |
|---|
| 49 | |
|---|
| 50 | def notify(event_type, jid, account, msg_type = '', file_props = None): |
|---|
| 51 | '''Notifies a user of an event. It first tries to a valid implementation of |
|---|
| 52 | the Desktop Notification Specification. If that fails, then we fall back to |
|---|
| 53 | the older style PopupNotificationWindow method.''' |
|---|
| 54 | if gajim.config.get('use_notif_daemon') and dbus_support.supported: |
|---|
| 55 | try: |
|---|
| 56 | DesktopNotification(event_type, jid, account, msg_type, file_props) |
|---|
| 57 | return |
|---|
| 58 | except dbus.dbus_bindings.DBusException, e: |
|---|
| 59 | # Connection to DBus failed, try popup |
|---|
| 60 | print >> sys.stderr, e |
|---|
| 61 | except TypeError, e: |
|---|
| 62 | # This means that we sent the message incorrectly |
|---|
| 63 | print >> sys.stderr, e |
|---|
| 64 | instance = dialogs.PopupNotificationWindow(event_type, jid, account, |
|---|
| 65 | msg_type, file_props) |
|---|
| 66 | gajim.interface.roster.popup_notification_windows.append(instance) |
|---|
| 67 | |
|---|
| 68 | class NotificationResponseManager: |
|---|
| 69 | '''Collects references to pending DesktopNotifications and manages there |
|---|
| 70 | signalling. This is necessary due to a bug in DBus where you can't remove |
|---|
| 71 | a signal from an interface once it's connected.''' |
|---|
| 72 | def __init__(self): |
|---|
| 73 | self.pending = {} |
|---|
| 74 | self.interface = None |
|---|
| 75 | |
|---|
| 76 | def attach_to_interface(self): |
|---|
| 77 | if self.interface is not None: |
|---|
| 78 | return |
|---|
| 79 | self.interface = dbus_support.get_notifications_interface() |
|---|
| 80 | self.interface.connect_to_signal('ActionInvoked', self.on_action_invoked) |
|---|
| 81 | self.interface.connect_to_signal('NotificationClosed', self.on_closed) |
|---|
| 82 | |
|---|
| 83 | def on_action_invoked(self, id, reason): |
|---|
| 84 | if self.pending.has_key(id): |
|---|
| 85 | notification = self.pending[id] |
|---|
| 86 | notification.on_action_invoked(id, reason) |
|---|
| 87 | del self.pending[id] |
|---|
| 88 | else: |
|---|
| 89 | # This happens in the case of a race condition where the user clicks |
|---|
| 90 | # on a popup before the program finishes registering this callback |
|---|
| 91 | gobject.timeout_add(1000, self.on_action_invoked, id, reason) |
|---|
| 92 | |
|---|
| 93 | def on_closed(self, id, reason): |
|---|
| 94 | if self.pending.has_key(id): |
|---|
| 95 | del self.pending[id] |
|---|
| 96 | |
|---|
| 97 | notification_response_manager = NotificationResponseManager() |
|---|
| 98 | |
|---|
| 99 | class DesktopNotification: |
|---|
| 100 | '''A DesktopNotification that interfaces with DBus via the Desktop |
|---|
| 101 | Notification specification''' |
|---|
| 102 | def __init__(self, event_type, jid, account, msg_type = '', file_props = None): |
|---|
| 103 | self.account = account |
|---|
| 104 | self.jid = jid |
|---|
| 105 | self.msg_type = msg_type |
|---|
| 106 | self.file_props = file_props |
|---|
| 107 | |
|---|
| 108 | if jid in gajim.contacts[account]: |
|---|
| 109 | actor = gajim.get_first_contact_instance_from_jid(account, jid).name |
|---|
| 110 | else: |
|---|
| 111 | actor = jid |
|---|
| 112 | |
|---|
| 113 | txt = actor # default value of txt |
|---|
| 114 | |
|---|
| 115 | if event_type == _('Contact Signed In'): |
|---|
| 116 | img = 'contact_online.png' |
|---|
| 117 | ntype = 'presence.online' |
|---|
| 118 | elif event_type == _('Contact Signed Out'): |
|---|
| 119 | img = 'contact_offline.png' |
|---|
| 120 | ntype = 'presence.offline' |
|---|
| 121 | elif event_type in (_('New Message'), _('New Single Message'), |
|---|
| 122 | _('New Private Message')): |
|---|
| 123 | ntype = 'im.received' |
|---|
| 124 | if event_type == _('New Private Message'): |
|---|
| 125 | room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) |
|---|
| 126 | room_name,t = gajim.get_room_name_and_server_from_room_jid(room_jid) |
|---|
| 127 | txt = _('%(nickname)s in room %(room_name)s has sent you a new message.')\ |
|---|
| 128 | % {'nickname': nick, 'room_name': room_name} |
|---|
| 129 | img = 'priv_msg_recv.png' |
|---|
| 130 | else: |
|---|
| 131 | #we talk about a name here |
|---|
| 132 | txt = _('%s has sent you a new message.') % actor |
|---|
| 133 | if event_type == _('New Message'): |
|---|
| 134 | img = 'chat_msg_recv.png' |
|---|
| 135 | else: # New Single Message |
|---|
| 136 | img = 'single_msg_recv.png' |
|---|
| 137 | elif event_type == _('File Transfer Request'): |
|---|
| 138 | img = 'ft_request.png' |
|---|
| 139 | ntype = 'transfer' |
|---|
| 140 | #we talk about a name here |
|---|
| 141 | txt = _('%s wants to send you a file.') % actor |
|---|
| 142 | elif event_type == _('File Transfer Error'): |
|---|
| 143 | img = 'ft_stopped.png' |
|---|
| 144 | ntype = 'transfer.error' |
|---|
| 145 | elif event_type in (_('File Transfer Completed'), _('File Transfer Stopped')): |
|---|
| 146 | ntype = 'transfer.complete' |
|---|
| 147 | if file_props is not None: |
|---|
| 148 | if file_props['type'] == 'r': |
|---|
| 149 | # get the name of the sender, as it is in the roster |
|---|
| 150 | sender = unicode(file_props['sender']).split('/')[0] |
|---|
| 151 | name = gajim.get_first_contact_instance_from_jid( |
|---|
| 152 | account, sender).name |
|---|
| 153 | filename = os.path.basename(file_props['file-name']) |
|---|
| 154 | if event_type == _('File Transfer Completed'): |
|---|
| 155 | txt = _('You successfully received %(filename)s from %(name)s.')\ |
|---|
| 156 | % {'filename': filename, 'name': name} |
|---|
| 157 | img = 'ft_done.png' |
|---|
| 158 | else: # ft stopped |
|---|
| 159 | txt = _('File transfer of %(filename)s from %(name)s stopped.')\ |
|---|
| 160 | % {'filename': filename, 'name': name} |
|---|
| 161 | img = 'ft_stopped.png' |
|---|
| 162 | else: |
|---|
| 163 | receiver = file_props['receiver'] |
|---|
| 164 | if hasattr(receiver, 'jid'): |
|---|
| 165 | receiver = receiver.jid |
|---|
| 166 | receiver = receiver.split('/')[0] |
|---|
| 167 | # get the name of the contact, as it is in the roster |
|---|
| 168 | name = gajim.get_first_contact_instance_from_jid( |
|---|
| 169 | account, receiver).name |
|---|
| 170 | filename = os.path.basename(file_props['file-name']) |
|---|
| 171 | if event_type == _('File Transfer Completed'): |
|---|
| 172 | txt = _('You successfully sent %(filename)s to %(name)s.')\ |
|---|
| 173 | % {'filename': filename, 'name': name} |
|---|
| 174 | img = 'ft_done.png' |
|---|
| 175 | else: # ft stopped |
|---|
| 176 | txt = _('File transfer of %(filename)s to %(name)s stopped.')\ |
|---|
| 177 | % {'filename': filename, 'name': name} |
|---|
| 178 | img = 'ft_stopped.png' |
|---|
| 179 | else: |
|---|
| 180 | txt = '' |
|---|
| 181 | else: |
|---|
| 182 | # defaul failsafe values |
|---|
| 183 | img = 'chat_msg_recv.png' # img to display |
|---|
| 184 | ntype = 'im' # Notification Type |
|---|
| 185 | |
|---|
| 186 | path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', img) |
|---|
| 187 | path = os.path.abspath(path) |
|---|
| 188 | |
|---|
| 189 | self.notif = dbus_support.get_notifications_interface() |
|---|
| 190 | if self.notif is None: |
|---|
| 191 | raise dbus.dbus_bindings.DBusException() |
|---|
| 192 | self.id = self.notif.Notify(dbus.String(_('Gajim')), |
|---|
| 193 | dbus.String(path), dbus.UInt32(0), ntype, dbus.Byte(0), |
|---|
| 194 | dbus.String(event_type), dbus.String(txt), |
|---|
| 195 | [dbus.String(path)], {'default':0}, [''], True, dbus.UInt32(5)) |
|---|
| 196 | notification_response_manager.attach_to_interface() |
|---|
| 197 | notification_response_manager.pending[self.id] = self |
|---|
| 198 | |
|---|
| 199 | def on_action_invoked(self, id, reason): |
|---|
| 200 | if self.notif is None: |
|---|
| 201 | return |
|---|
| 202 | self.notif.CloseNotification(dbus.UInt32(id)) |
|---|
| 203 | self.notif = None |
|---|
| 204 | if not self.msg_type: |
|---|
| 205 | self.msg_type = 'chat' |
|---|
| 206 | gajim.interface.handle_event(self.account, self.jid, self.msg_type) |
|---|