Newer
Older
## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
## Copyright (C) 2005-2006 Andrew Sayman <lorien420@myrealbox.com>
##
## DBUS/libnotify connection code:
## Copyright (C) 2005 by Sebastian Estienne
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 2 only.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
import os

Yann Leboulanger
committed
import time
from common import helpers

nkour
committed
import dbus_support
if dbus_support.supported:
import dbus

Yann Leboulanger
committed
def get_show_in_roster(event, account, contact):
'''Return True if this event must be shown in roster, else False'''
num = get_advanced_notification(event, account, contact)
if num != None:
if gajim.config.get_per('notifications', str(num), 'roster') == 'yes':
return True
if gajim.config.get_per('notifications', str(num), 'roster') == 'no':
return False
if event == 'message_received':
chat_control = helpers.get_chat_control(account, contact)
if chat_control:
return False
return True

Yann Leboulanger
committed
def get_show_in_systray(event, account, contact):
'''Return True if this event must be shown in roster, else False'''
num = get_advanced_notification(event, account, contact)
if num != None:
if gajim.config.get_per('notifications', str(num), 'systray') == 'yes':
return True
if gajim.config.get_per('notifications', str(num), 'systray') == 'no':
return False

Yann Leboulanger
committed

Yann Leboulanger
committed
def get_advanced_notification(event, account, contact):

Yann Leboulanger
committed
'''Returns the number of the first advanced notification or None'''

Yann Leboulanger
committed
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
num = 0
notif = gajim.config.get_per('notifications', str(num))
while notif:
recipient_ok = False
status_ok = False
tab_opened_ok = False
# test event
if gajim.config.get_per('notifications', str(num), 'event') == event:
# test recipient
recipient_type = gajim.config.get_per('notifications', str(num),
'recipient_type')
recipients = gajim.config.get_per('notifications', str(num),
'recipients').split()
if recipient_type == 'all':
recipient_ok = True
elif recipient_type == 'contact' and contact.jid in recipients:
recipient_ok = True
elif recipient_type == 'group':
for group in contact.groups:
if group in contact.groups:
recipient_ok = True
break
if recipient_ok:
# test status
our_status = gajim.SHOW_LIST[gajim.connections[account].connected]
status = gajim.config.get_per('notifications', str(num), 'status')
if status == 'all' or our_status in status.split():
status_ok = True
if status_ok:
# test window_opened
tab_opened = gajim.config.get_per('notifications', str(num),
'tab_opened')
if tab_opened == 'both':
tab_opened_ok = True
else:

Yann Leboulanger
committed
chat_control = helper.get_chat_control(account, contact)

Yann Leboulanger
committed
if (chat_control and tab_opened == 'yes') or (not chat_control and \
tab_opened == 'no'):
tab_opened_ok = True
if tab_opened_ok:
return num
num += 1
notif = gajim.config.get_per('notifications', str(num))
def notify(event, jid, account, parameters, advanced_notif_num = None):
'''Check what type of notifications we want, depending on basic configuration
of notifications and advanced one and do these notifications'''
# First, find what notifications we want
do_popup = False
do_sound = False

Yann Leboulanger
committed
do_cmd = False
if (event == 'status_change'):
new_show = parameters[0]
status_message = parameters[1]
# Default : No popup for status change
elif (event == 'contact_connected'):
status_message = parameters

Yann Leboulanger
committed
j = gajim.get_jid_without_resource(jid)
server = gajim.get_server_from_jid(j)
account_server = account + '/' + server
block_transport = False
if account_server in gajim.block_signed_in_notifications and \
gajim.block_signed_in_notifications[account_server]:
block_transport = True

Yann Leboulanger
committed
if helpers.allow_showing_notification(account, 'notify_on_signin') and \
not gajim.block_signed_in_notifications[account] and not block_transport:
do_popup = True
if gajim.config.get_per('soundevents', 'contact_connected',

Yann Leboulanger
committed
'enabled') and not gajim.block_signed_in_notifications[account] and \
not block_transport:
do_sound = True
elif (event == 'contact_disconnected'):
status_message = parameters

Yann Leboulanger
committed
if helpers.allow_showing_notification(account, 'notify_on_signout'):
do_popup = True
if gajim.config.get_per('soundevents', 'contact_disconnected',
'enabled'):
do_sound = True

jimpp
committed
elif (event == 'new_message'):
message_type = parameters[0]
first = parameters[1]
nickname = parameters[2]
message = parameters[3]

Yann Leboulanger
committed
if helpers.allow_showing_notification(account, 'notify_on_new_message',

Yann Leboulanger
committed
advanced_notif_num, first):

jimpp
committed
do_popup = True

Yann Leboulanger
committed
if first and helpers.allow_sound_notification('first_message_received',
advanced_notif_num):

jimpp
committed
do_sound = True

Yann Leboulanger
committed
elif not first and helpers.allow_sound_notification(
'next_message_received', advanced_notif_num):

jimpp
committed
do_sound = True
else:
print '*Event not implemeted yet*'

Yann Leboulanger
committed
if advanced_notif_num != None and gajim.config.get_per('notifications',
str(advanced_notif_num), 'run_command'):
do_cmd = True

jimpp
committed
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# Do the wanted notifications
if (do_popup):
if (event == 'contact_connected' or event == 'contact_disconnected' or \
event == 'status_change'): # Common code for popup for these 3 events
if (event == 'contact_disconnected'):
show_image = 'offline.png'
suffix = '_notif_size_bw.png'
else: #Status Change or Connected
# TODO : for status change, we don't always 'online.png', but we
# first need 48x48 for all status
show_image = 'online.png'
suffix = '_notif_size_colored.png'
transport_name = gajim.get_transport_name_from_jid(jid)
img = None
if transport_name:
img = os.path.join(gajim.DATA_DIR, 'iconsets',
'transports', transport_name, '48x48', show_image)
if not img or not os.path.isfile(img):
iconset = gajim.config.get('iconset')
img = os.path.join(gajim.DATA_DIR, 'iconsets',
iconset, '48x48', show_image)
path = gtkgui_helpers.get_path_to_generic_or_avatar(img,
jid = jid, suffix = suffix)
if (event == 'status_change'):
title = _('%(nick)s Changed Status') % \
{'nick': gajim.get_name_from_jid(account, jid)}
text = _('%(nick)s is now %(status)s') % \
{'nick': gajim.get_name_from_jid(account, jid),\
'status': helpers.get_uf_show(gajim.SHOW_LIST[new_show])}
if status_message:
text = text + " : " + status_message
path_to_image = path, title = title, text = text)
elif (event == 'contact_connected'):
title = _('%(nickname)s Signed In') % \
{'nickname': gajim.get_name_from_jid(account, jid)}
text = ''
if status_message:
text = status_message
popup(_('Contact Signed In'), jid, account,
path_to_image = path, title = title, text = text)
elif (event == 'contact_disconnected'):
title = _('%(nickname)s Signed Out') % \
{'nickname': gajim.get_name_from_jid(account, jid)}
text = ''
if status_message:
text = status_message
popup(_('Contact Signed Out'), jid, account,
path_to_image = path, title = title, text = text)

jimpp
committed
elif (event == 'new_message'):
if message_type == 'normal': # single message
event_type = _('New Single Message')
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'single_msg_recv.png')
title = _('New Single Message from %(nickname)s') % \
{'nickname': nickname}
text = message
elif message_type == 'pm': # private message
event_type = _('New Private Message')
room_name, t = gajim.get_room_name_and_server_from_room_jid(jid)

jimpp
committed
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'priv_msg_recv.png')
title = _('New Private Message from room %s') % room_name
text = _('%(nickname)s: %(message)s') % {'nickname': nickname,
'message': message}
else: # chat message
event_type = _('New Message')
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'chat_msg_recv.png')
title = _('New Message from %(nickname)s') % \
{'nickname': nickname}

jimpp
committed
path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
popup(event_type, jid, account, message_type,
path_to_image = path, title = title, text = text)

Yann Leboulanger
committed
snd_file = None
snd_event = None # If not snd_file, play the event

jimpp
committed
if (event == 'new_message'):

Yann Leboulanger
committed
if advanced_notif_num != None and gajim.config.get_per('notifications',
str(advanced_notif_num), 'sound') == 'yes':
snd_file = gajim.config.get_per('notifications',
str(advanced_notif_num), 'sound_file')
elif advanced_notif_num != None and gajim.config.get_per(
'notifications', str(advanced_notif_num), 'sound') == 'no':
pass # do not set snd_event
elif first:
snd_event = 'first_message_received'

jimpp
committed
else:

Yann Leboulanger
committed
snd_event = 'next_message_received'
elif event in ('contact_connected', 'contact_disconnected'):

Yann Leboulanger
committed
snd_event = event
if snd_file:
helpers.play_sound_file(snd_file)
if snd_event:
helpers.play_sound(snd_event)
if do_cmd:
command = gajim.config.get_per('notifications', str(advanced_notif_num),
'command')
try:
helpers.exec_command(command)
except:
pass
def popup(event_type, jid, account, msg_type = '', path_to_image = None,
title = None, text = None):

nkour
committed
'''Notifies a user of an event. It first tries to a valid implementation of
the Desktop Notification Specification. If that fails, then we fall back to
the older style PopupNotificationWindow method.'''
text = gtkgui_helpers.escape_for_pango_markup(text)
title = gtkgui_helpers.escape_for_pango_markup(title)

nkour
committed
if gajim.config.get('use_notif_daemon') and dbus_support.supported:
DesktopNotification(event_type, jid, account, msg_type, path_to_image,
# Connection to D-Bus failed, try popup
except TypeError, e:
# This means that we sent the message incorrectly
instance = dialogs.PopupNotificationWindow(event_type, jid, account, msg_type, \
path_to_image, title, text)
gajim.interface.roster.popup_notification_windows.append(instance)

nkour
committed
class NotificationResponseManager:
'''Collects references to pending DesktopNotifications and manages there
signalling. This is necessary due to a bug in DBus where you can't remove
a signal from an interface once it's connected.'''
def __init__(self):
self.pending = {}

Yann Leboulanger
committed
self.received = []

nkour
committed
self.interface = None
def attach_to_interface(self):
if self.interface is not None:
return
self.interface = dbus_support.get_notifications_interface()
self.interface.connect_to_signal('ActionInvoked', self.on_action_invoked)
self.interface.connect_to_signal('NotificationClosed', self.on_closed)

nkour
committed
def on_action_invoked(self, id, reason):

Yann Leboulanger
committed
self.received.append((id, time.time(), reason))

nkour
committed
if self.pending.has_key(id):
notification = self.pending[id]
notification.on_action_invoked(id, reason)
del self.pending[id]

Yann Leboulanger
committed
if len(self.received) > 20:
curt = time.time()
for rec in self.received:
diff = curt - rec[1]
if diff > 10:
self.received.remove(rec)

nkour
committed

Yann Leboulanger
committed
def on_closed(self, id, reason = None):

nkour
committed
if self.pending.has_key(id):
del self.pending[id]

Yann Leboulanger
committed
def add_pending(self, id, object):
# Check to make sure that we handle an event immediately if we're adding
# an id that's already been triggered
for rec in self.received:
if rec[0] == id:
object.on_action_invoked(id, rec[2])
self.received.remove(rec)
return
if id not in self.pending:
# Add it
self.pending[id] = object
else:
# We've triggered an event that has a duplicate ID!
gajim.log.debug('Duplicate ID of notification. Can\'t handle this.')

nkour
committed
notification_response_manager = NotificationResponseManager()
class DesktopNotification:
'''A DesktopNotification that interfaces with DBus via the Desktop
Notification specification'''
def __init__(self, event_type, jid, account, msg_type = '',
path_to_image = None, title = None, text = None):

Yann Leboulanger
committed
self.path_to_image = path_to_image
self.event_type = event_type

Yann Leboulanger
committed
self.text = text
'''0.3.1 is the only version of notification daemon that has no way to determine which version it is. If no method exists, it means they're using that one.'''

Yann Leboulanger
committed
self.default_version = '0.3.1'

nkour
committed
self.account = account
self.jid = jid
self.msg_type = msg_type
if not text:
# default value of text
self.text = gajim.get_name_from_jid(account, jid)
if not title:
self.title = event_type # default value

nkour
committed
if event_type == _('Contact Signed In'):
ntype = 'presence.online'
elif event_type == _('Contact Signed Out'):
ntype = 'presence.offline'
elif event_type in (_('New Message'), _('New Single Message'),
_('New Private Message')):
ntype = 'im.received'
elif event_type == _('File Transfer Request'):
ntype = 'transfer'
elif event_type == _('File Transfer Error'):
ntype = 'transfer.error'
elif event_type in (_('File Transfer Completed'), _('File Transfer Stopped')):
ntype = 'transfer.complete'

Yann Leboulanger
committed
ntype = 'email.arrived'
elif event_type == _('Groupchat Invitation'):
ntype = 'im.invitation'
ntype = 'presence.status'

Yann Leboulanger
committed
elif event_type == _('Connection Failed'):
ntype = 'connection.failed'
# default failsafe values

Yann Leboulanger
committed
self.path_to_image = os.path.abspath(
os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'chat_msg_recv.png')) # img to display
ntype = 'im' # Notification Type

nkour
committed
self.notif = dbus_support.get_notifications_interface()
if self.notif is None:

Yann Leboulanger
committed
self.ntype = ntype
self.get_version()
def attempt_notify(self):
version = self.version

Yann Leboulanger
committed
ntype = self.ntype

Yann Leboulanger
committed
if version.startswith('0.2'):
try:

Yann Leboulanger
committed
self.notif.Notify(
dbus.String(_('Gajim')),
dbus.String(self.path_to_image),
dbus.UInt32(0),
ntype,
dbus.Byte(0),
dbus.String(self.title),

Yann Leboulanger
committed
dbus.String(self.text),
[dbus.String(self.path_to_image)],
{'default': 0},
[''],
True,
dbus.UInt32(timeout),
reply_handler=self.attach_by_id,
error_handler=self.notify_another_way)

Yann Leboulanger
committed
except AttributeError:
version = '0.3.1' # we're actually dealing with the newer version
if version.startswith('0.3'):
if version >= ( 0, 3, 2):

Yann Leboulanger
committed
hints = {}
hints['urgency'] = dbus.Byte(0) # Low Urgency

Yann Leboulanger
committed
hints['category'] = dbus.String(ntype)
self.notif.Notify(
dbus.String(_('Gajim')),
dbus.UInt32(0), # this notification does not replace other
dbus.String(self.path_to_image),
dbus.String(self.title),

Yann Leboulanger
committed
dbus.String(self.text),
( dbus.String('default'), dbus.String(self.event_type) ),

Yann Leboulanger
committed
hints,
dbus.UInt32(timeout*1000),
reply_handler=self.attach_by_id,
error_handler=self.notify_another_way)

Yann Leboulanger
committed
self.notif.Notify(
dbus.String(_('Gajim')),
dbus.String(self.path_to_image),
dbus.UInt32(0),
dbus.String(self.title),

Yann Leboulanger
committed
dbus.String(self.text),
dbus.String(''),
{},
dbus.UInt32(timeout*1000),
reply_handler=self.attach_by_id,
error_handler=self.notify_another_way)
def attach_by_id(self, id):
self.id = id

nkour
committed
notification_response_manager.attach_to_interface()

Yann Leboulanger
committed
notification_response_manager.add_pending(self.id, self)
def notify_another_way(self,e):
gajim.log.debug(str(e))
gajim.log.debug('Need to implement a new way of falling back')

nkour
committed
def on_action_invoked(self, id, reason):
if self.notif is None:
return
self.notif.CloseNotification(dbus.UInt32(id))
self.notif = None
if not self.msg_type:
self.msg_type = 'chat'
gajim.interface.handle_event(self.account, self.jid, self.msg_type)

Yann Leboulanger
committed
def version_reply_handler(self, name, vendor, version, spec_version = None):
self.version = version
self.attempt_notify()
def get_version(self):
self.notif.GetServerInfo(
reply_handler=self.version_reply_handler,
error_handler=self.version_error_handler_2_x_try)
def version_error_handler_2_x_try(self, e):
self.notif.GetServerInformation(reply_handler=self.version_reply_handler,
error_handler=self.version_error_handler_3_x_try)

Yann Leboulanger
committed
def version_error_handler_3_x_try(self, e):
self.version = self.default_version
self.attempt_notify()