[lorien420] Notification Daemon Popups are now clickable! CONGRAAAAAAATS Andrew Sayman

This commit is contained in:
Nikos Kouremenos 2005-12-10 00:56:38 +00:00
parent 612716e2db
commit 6e413e7f5a
3 changed files with 325 additions and 164 deletions

117
src/dbus_support.py Normal file
View file

@ -0,0 +1,117 @@
## dbus_support.py
##
## Contributors for this file:
## - Andrew Sayman <lorien420@myrealbox.com>
##
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
## Vincent Hanquez <tab@snarc.org>
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
## Vincent Hanquez <tab@snarc.org>
## Nikos Kouremenos <nkour@jabber.org>
## Dimitur Kirov <dkirov@gmail.com>
## Travis Shirk <travis@pobox.com>
## Norman Rasmussen <norman@rasmussen.co.za>
##
## 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
import sys
from common import exceptions
from common import i18n
_ = i18n._
try:
import dbus
version = getattr(dbus, 'version', (0, 20, 0))
except ImportError:
version = (0, 0, 0)
if version >= (0, 41, 0):
import dbus.service
import dbus.glib # cause dbus 0.35+ doesn't return signal replies without it
supported = True
if 'dbus' not in globals() and not os.name == 'nt':
print _('D-Bus python bindings are missing in this computer')
print _('D-Bus capabilities of Gajim cannot be used')
supported = False
# dbus 0.23 leads to segfault with threads_init()
if sys.version[:4] >= '2.4' and version[1] < 30:
supported = False
class SessionBus:
'''A Singleton for the DBus SessionBus'''
def __init__(self):
self.session_bus = None
def SessionBus(self):
if not supported:
raise exceptions.DbusNotSupported
if not self.present():
raise exceptions.SessionBusNotPresent
return self.session_bus
def bus(self):
return self.SessionBus()
def present(self):
if not supported:
return False
if self.session_bus is None:
try:
self.session_bus = dbus.SessionBus()
except dbus.DBusException:
self.session_bus = None
return False
if self.session_bus is None:
return False
return True
session_bus = SessionBus()
def get_interface(interface, path):
'''Returns an interface on the current SessionBus. If the interface isn't
running, it tries to start it first.'''
if not supported:
return None
if session_bus.present():
bus = session_bus.SessionBus()
else:
return None
try:
obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus')
running_services = dbus_iface.ListNames()
started = True
if interface not in running_services:
# try to start the service
if dbus_iface.StartServiceByName(interface, dbus.UInt32(0)) == 1:
started = True
else:
started = False
if not started:
return None
obj = bus.get_object(interface, path)
return dbus.Interface(obj, interface)
except Exception, e:
print >> sys.stderr, e
return None
except dbus.DBusException, e:
# This exception could give useful info about why notification breaks
print >> sys.stderr, e
return None
def get_notifications_interface():
'''Returns the notifications interface.'''
return get_interface('org.freedesktop.Notifications','/org/freedesktop/Notifications')

View file

@ -28,152 +28,36 @@
## GNU General Public License for more details. ## GNU General Public License for more details.
## ##
HAS_DBUS = True
try:
import dbus
except ImportError:
HAS_DBUS = False
import os import os
import sys import sys
import gajim import gajim
import dialogs import dialogs
import gobject
from common import gajim from common import gajim
from common import exceptions
from common import i18n from common import i18n
i18n.init() i18n.init()
_ = i18n._ _ = i18n._
import dbus_support
def dbus_get_interface(): if dbus_support.supported:
try: import dbus
interface = 'org.freedesktop.Notifications' if dbus_support.version >= (0, 41, 0):
path = '/org/freedesktop/Notifications' import dbus.glib
bus = dbus.SessionBus() import dbus.service
obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus')
running_services = dbus_iface.ListNames()
started = True
if interface not in running_services:
# try to start the service (notif-daemon)
if dbus_iface.StartServiceByName(interface, dbus.UInt32(0)) == 1:
started = True
else:
started = False
if not started:
return None
obj = bus.get_object(interface, path)
return dbus.Interface(obj, interface)
except Exception, e:
return None
except dbus.DBusException, e:
# This exception could give useful info about why notification breaks
print >> sys.stderr, e
return None
def dbus_available():
if not HAS_DBUS:
return False
if dbus_get_interface() is None:
return False
else:
return True
def dbus_notify(event_type, jid, account, msg_type = '', file_props = None):
if jid in gajim.contacts[account]:
actor = gajim.get_first_contact_instance_from_jid(account, jid).name
else:
actor = jid
# default value of txt
txt = actor
img = 'chat.png' # img to display
ntype = 'im' # Notification Type
if event_type == _('Contact Signed In'):
img = 'online.png'
ntype = 'presence.online'
elif event_type == _('Contact Signed Out'):
img = 'offline.png'
ntype = 'presence.offline'
elif event_type in (_('New Message'), _('New Single Message'),
_('New Private Message')):
img = 'chat.png' # FIXME: better img and split events
ntype = 'im.received'
if event_type == _('New Private Message'):
room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
room_name,t = gajim.get_room_name_and_server_from_room_jid(room_jid)
txt = _('%(nickname)s in room %(room_name)s has sent you a new message.')\
% {'nickname': nick, 'room_name': room_name}
else:
#we talk about a name here
txt = _('%s has sent you a new message.') % actor
elif event_type == _('File Transfer Request'):
img = 'requested.png' # FIXME: better img
ntype = 'transfer'
#we talk about a name here
txt = _('%s wants to send you a file.') % actor
elif event_type == _('File Transfer Error'):
img = 'error.png' # FIXME: better img
ntype = 'transfer.error'
elif event_type in (_('File Transfer Completed'), _('File Transfer Stopped')):
img = 'closed.png' # # FIXME: better img and split events
ntype = 'transfer.complete'
if file_props is not None:
if file_props['type'] == 'r':
# get the name of the sender, as it is in the roster
sender = unicode(file_props['sender']).split('/')[0]
name = gajim.get_first_contact_instance_from_jid(
account, sender).name
filename = os.path.basename(file_props['file-name'])
if event_type == _('File Transfer Completed'):
txt = _('You successfully received %(filename)s from %(name)s.')\
% {'filename': filename, 'name': name}
else: # ft stopped
txt = _('File transfer of %(filename)s from %(name)s stopped.')\
% {'filename': filename, 'name': name}
else:
receiver = file_props['receiver']
if hasattr(receiver, 'jid'):
receiver = receiver.jid
receiver = receiver.split('/')[0]
# get the name of the contact, as it is in the roster
name = gajim.get_first_contact_instance_from_jid(
account, receiver).name
filename = os.path.basename(file_props['file-name'])
if event_type == _('File Transfer Completed'):
txt = _('You successfully sent %(filename)s to %(name)s.')\
% {'filename': filename, 'name': name}
else: # ft stopped
txt = _('File transfer of %(filename)s to %(name)s stopped.')\
% {'filename': filename, 'name': name}
else:
txt = ''
iconset = gajim.config.get('iconset')
if not iconset:
iconset = 'sun'
# FIXME: use 32x32 or 48x48 someday
path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16', img)
path = os.path.abspath(path)
notif = dbus_get_interface()
if notif is None:
raise dbus.DBusException()
notif.Notify(dbus.String(_('Gajim')),
dbus.String(path), dbus.UInt32(0), ntype, dbus.Byte(0),
dbus.String(event_type), dbus.String(txt),
[dbus.String(path)], [''], [''], True, dbus.UInt32(5))
def notify(event_type, jid, account, msg_type = '', file_props = None): def notify(event_type, jid, account, msg_type = '', file_props = None):
if dbus_available() and gajim.config.get('use_notif_daemon'): '''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.'''
if gajim.config.get('use_notif_daemon') and dbus_support.supported:
try: try:
dbus_notify(event_type, jid, account, msg_type, file_props) DesktopNotification(event_type, jid, account, msg_type, file_props)
return return
except dbus.DBusException, e: except dbus.DBusException, e:
# Connection to DBus failed, try popup # Connection to DBus failed, try popup
pass print >> sys.stderr, e
except TypeError, e: except TypeError, e:
# This means that we sent the message incorrectly # This means that we sent the message incorrectly
print >> sys.stderr, e print >> sys.stderr, e
@ -181,4 +65,177 @@ def notify(event_type, jid, account, msg_type = '', file_props = None):
msg_type, file_props) msg_type, file_props)
gajim.interface.roster.popup_notification_windows.append(instance) gajim.interface.roster.popup_notification_windows.append(instance)
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 = {}
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)
def on_action_invoked(self, id, reason):
if self.pending.has_key(id):
notification = self.pending[id]
notification.on_action_invoked(id, reason)
del self.pending[id]
else:
# This happens in the case of a race condition where the user clicks
# on a popup before the program finishes registering this callback
gobject.timeout_add(1000, self.on_action_invoked, id, reason)
def on_closed(self, id, reason):
if self.pending.has_key(id):
del self.pending[id]
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 = '', file_props = None):
self.account = account
self.jid = jid
self.msg_type = msg_type
self.file_props = file_props
if jid in gajim.contacts[account]:
actor = gajim.get_first_contact_instance_from_jid(account, jid).name
else:
actor = jid
# default value of txt
txt = actor
img = 'chat.png' # img to display
ntype = 'im' # Notification Type
if event_type == _('Contact Signed In'):
img = 'online.png'
ntype = 'presence.online'
elif event_type == _('Contact Signed Out'):
img = 'offline.png'
ntype = 'presence.offline'
elif event_type in (_('New Message'), _('New Single Message'),
_('New Private Message')):
img = 'chat.png' # FIXME: better img and split events
ntype = 'im.received'
if event_type == _('New Private Message'):
room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
room_name,t = gajim.get_room_name_and_server_from_room_jid(room_jid)
txt = _('%(nickname)s in room %(room_name)s has sent you a new message.')\
% {'nickname': nick, 'room_name': room_name}
else:
#we talk about a name here
txt = _('%s has sent you a new message.') % actor
elif event_type == _('File Transfer Request'):
img = 'requested.png' # FIXME: better img
ntype = 'transfer'
#we talk about a name here
txt = _('%s wants to send you a file.') % actor
elif event_type == _('File Transfer Error'):
img = 'error.png' # FIXME: better img
ntype = 'transfer.error'
elif event_type in (_('File Transfer Completed'), _('File Transfer Stopped')):
img = 'closed.png' # # FIXME: better img and split events
ntype = 'transfer.complete'
if file_props is not None:
if file_props['type'] == 'r':
# get the name of the sender, as it is in the roster
sender = unicode(file_props['sender']).split('/')[0]
name = gajim.get_first_contact_instance_from_jid(
account, sender).name
filename = os.path.basename(file_props['file-name'])
if event_type == _('File Transfer Completed'):
txt = _('You successfully received %(filename)s from %(name)s.')\
% {'filename': filename, 'name': name}
else: # ft stopped
txt = _('File transfer of %(filename)s from %(name)s stopped.')\
% {'filename': filename, 'name': name}
else:
receiver = file_props['receiver']
if hasattr(receiver, 'jid'):
receiver = receiver.jid
receiver = receiver.split('/')[0]
# get the name of the contact, as it is in the roster
name = gajim.get_first_contact_instance_from_jid(
account, receiver).name
filename = os.path.basename(file_props['file-name'])
if event_type == _('File Transfer Completed'):
txt = _('You successfully sent %(filename)s to %(name)s.')\
% {'filename': filename, 'name': name}
else: # ft stopped
txt = _('File transfer of %(filename)s to %(name)s stopped.')\
% {'filename': filename, 'name': name}
else:
txt = ''
iconset = gajim.config.get('iconset')
if not iconset:
iconset = 'sun'
# FIXME: use 32x32 or 48x48 someday
path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16', img)
path = os.path.abspath(path)
self.notif = dbus_support.get_notifications_interface()
if self.notif is None:
raise dbus.DBusException()
self.id = self.notif.Notify(dbus.String(_('Gajim')),
dbus.String(path), dbus.UInt32(0), ntype, dbus.Byte(0),
dbus.String(event_type), dbus.String(txt),
[dbus.String(path)], {'default':0}, [''], True, dbus.UInt32(5))
notification_response_manager.attach_to_interface()
notification_response_manager.pending[self.id] = self
def on_action_invoked(self, id, reason):
if self.notif is None:
return
self.notif.CloseNotification(dbus.UInt32(id))
self.notif = None
# use Contact class, new_chat expects it that way
# is it in the roster?
if gajim.contacts[self.account].has_key(self.jid):
contact = gajim.get_contact_instance_with_highest_priority(
self.account, self.jid)
else:
from gajim import Contact
keyID = ''
attached_keys = gajim.config.get_per('accounts', self.account,
'attached_gpg_keys').split()
if self.jid in attached_keys:
keyID = attached_keys[attached_keys.index(jid) + 1]
if self.msg_type.find('file') != 0:
if self.msg_type == 'pm':
room_jid, nick = self.jid.split('/', 1)
show = gajim.gc_contacts[self.account][room_jid][nick].show
contact = Contact(jid = self.jid, name = nick, groups = ['none'],
show = show, sub = 'none')
else:
contact = Contact(jid = self.jid, name = self.jid.split('@')[0],
groups = [_('not in the roster')], show = 'not in the roster',
status = _('not in the roster'), sub = 'none', keyID = keyID)
gajim.contacts[self.account][self.jid] = [contact]
gajim.interface.roster.add_contact_to_roster(contact.jid,
self.account)
if self.msg_type == 'pm': # It's a private message
gajim.interface.roster.new_chat(contact, self.account)
chats_window = gajim.interface.instances[self.account]['chats'][self.jid]
chats_window.set_active_tab(self.jid)
chats_window.window.present()
elif self.msg_type in ('normal', 'file-request', 'file-request-error',
'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
# Get the first single message event
ev = gajim.get_first_event(self.account, self.jid, self.msg_type)
gajim.interface.roster.open_event(self.account, self.jid, ev)
else: # 'chat'
gajim.interface.roster.new_chat(contact, self.account)
chats_window = gajim.interface.instances[self.account]['chats'][self.jid]
chats_window.set_active_tab(self.jid)
chats_window.window.present()

View file

@ -4,6 +4,7 @@
## - Yann Le Boulanger <asterix@lagaule.org> ## - Yann Le Boulanger <asterix@lagaule.org>
## - Nikos Kouremenos <kourem@gmail.com> ## - Nikos Kouremenos <kourem@gmail.com>
## - Dimitur Kirov <dkirov@gmail.com> ## - Dimitur Kirov <dkirov@gmail.com>
## - Andrew Sayman <lorien420@myrealbox.com>
## ##
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org> ## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
## Vincent Hanquez <tab@snarc.org> ## Vincent Hanquez <tab@snarc.org>
@ -38,20 +39,17 @@ from common import i18n
from dialogs import AddNewContactWindow from dialogs import AddNewContactWindow
_ = i18n._ _ = i18n._
try: import dbus_support
if dbus_support.supported:
import dbus import dbus
_version = getattr(dbus, 'version', (0, 20, 0)) if dbus_support.version >= (0, 41, 0):
except ImportError: import dbus.service
_version = (0, 0, 0) import dbus.glib # cause dbus 0.35+ doesn't return signal replies without it
DbusPrototype = dbus.service.Object
if _version >= (0, 41, 0): elif dbus_support.version >= (0, 20, 0):
import dbus.service DbusPrototype = dbus.Object
import dbus.glib # cause dbus 0.35+ doesn't return signal replies without it else: #dbus is not defined
DbusPrototype = dbus.service.Object DbusPrototype = str
elif _version >= (0, 20, 0):
DbusPrototype = dbus.Object
else: #dbus is not defined
DbusPrototype = str
INTERFACE = 'org.gajim.dbus.RemoteInterface' INTERFACE = 'org.gajim.dbus.RemoteInterface'
OBJ_PATH = '/org/gajim/dbus/RemoteObject' OBJ_PATH = '/org/gajim/dbus/RemoteObject'
@ -63,23 +61,12 @@ STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
class Remote: class Remote:
def __init__(self): def __init__(self):
self.signal_object = None self.signal_object = None
if 'dbus' not in globals() and not os.name == 'nt': session_bus = dbus_support.session_bus.SessionBus()
print _('D-Bus python bindings are missing in this computer')
print _('D-Bus capabilities of Gajim cannot be used')
raise exceptions.DbusNotSupported
# dbus 0.23 leads to segfault with threads_init()
if sys.version[:4] >= '2.4' and _version[1] < 30:
raise exceptions.DbusNotSupported
try:
session_bus = dbus.SessionBus()
except:
raise exceptions.SessionBusNotPresent
if _version[1] >= 41: if dbus_support.version[1] >= 41:
service = dbus.service.BusName(SERVICE, bus=session_bus) service = dbus.service.BusName(SERVICE, bus=session_bus)
self.signal_object = SignalObject(service) self.signal_object = SignalObject(service)
elif _version[1] <= 40 and _version[1] >= 20: elif dbus_support.version[1] <= 40 and dbus_support.version[1] >= 20:
service=dbus.Service(SERVICE, session_bus) service=dbus.Service(SERVICE, session_bus)
self.signal_object = SignalObject(service) self.signal_object = SignalObject(service)
@ -97,9 +84,9 @@ class SignalObject(DbusPrototype):
self.vcard_account = None self.vcard_account = None
# register our dbus API # register our dbus API
if _version[1] >= 41: if dbus_support.version[1] >= 41:
DbusPrototype.__init__(self, service, OBJ_PATH) DbusPrototype.__init__(self, service, OBJ_PATH)
elif _version[1] >= 30: elif dbus_support.version[1] >= 30:
DbusPrototype.__init__(self, OBJ_PATH, service) DbusPrototype.__init__(self, OBJ_PATH, service)
else: else:
DbusPrototype.__init__(self, OBJ_PATH, service, DbusPrototype.__init__(self, OBJ_PATH, service,
@ -123,7 +110,7 @@ class SignalObject(DbusPrototype):
def raise_signal(self, signal, arg): def raise_signal(self, signal, arg):
''' raise a signal, with a single string message ''' ''' raise a signal, with a single string message '''
if _version[1] >= 30: if dbus_support.version[1] >= 30:
from dbus import dbus_bindings from dbus import dbus_bindings
message = dbus_bindings.Signal(OBJ_PATH, INTERFACE, signal) message = dbus_bindings.Signal(OBJ_PATH, INTERFACE, signal)
i = message.get_iter(True) i = message.get_iter(True)
@ -437,7 +424,7 @@ class SignalObject(DbusPrototype):
def _get_real_arguments(self, args, desired_length): def _get_real_arguments(self, args, desired_length):
# supresses the first 'message' argument, which is set in dbus 0.23 # supresses the first 'message' argument, which is set in dbus 0.23
if _version[1] == 20: if dbus_support.version[1] == 20:
args=args[1:] args=args[1:]
if desired_length > 0: if desired_length > 0:
args = list(args) args = list(args)
@ -473,14 +460,14 @@ class SignalObject(DbusPrototype):
return repr(contact_dict) return repr(contact_dict)
if _version[1] >= 30 and _version[1] <= 40: if dbus_support.version[1] >= 30 and dbus_support.version[1] <= 40:
method = dbus.method method = dbus.method
signal = dbus.signal signal = dbus.signal
elif _version[1] >= 41: elif dbus_support.version[1] >= 41:
method = dbus.service.method method = dbus.service.method
signal = dbus.service.signal signal = dbus.service.signal
if _version[1] >= 30: if dbus_support.version[1] >= 30:
# prevent using decorators, because they are not supported # prevent using decorators, because they are not supported
# on python < 2.4 # on python < 2.4
# FIXME: use decorators when python2.3 (and dbus 0.23) is OOOOOOLD # FIXME: use decorators when python2.3 (and dbus 0.23) is OOOOOOLD