add XEP-0224 support (/attention command, persistant popup and special sound). F

ixes #3465
This commit is contained in:
Yann Leboulanger 2012-04-09 13:38:28 +02:00
parent 51cfe177a1
commit 75c495979c
12 changed files with 86 additions and 44 deletions

BIN
data/sounds/attention.wav Normal file

Binary file not shown.

View File

@ -863,7 +863,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
def send_message(self, message, keyID='', type_='chat', chatstate=None,
msg_id=None, resource=None, xhtml=None, callback=None, callback_args=[],
process_commands=True):
process_commands=True, attention=False):
"""
Send the given message to the active tab. Doesn't return None if error
"""
@ -880,7 +880,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id,
resource=resource, user_nick=self.user_nick, xhtml=xhtml,
label=label, callback=callback, callback_args=callback_args,
control=self))
control=self, attention=attention))
# Record the history of sent messages
self.save_message(message, 'sent')
@ -2235,7 +2235,7 @@ class ChatControl(ChatControlBase):
dialogs.ESessionInfoWindow(self.session)
def send_message(self, message, keyID='', chatstate=None, xhtml=None,
process_commands=True):
process_commands=True, attention=False):
"""
Send a message to contact
"""
@ -2286,7 +2286,8 @@ class ChatControl(ChatControlBase):
ChatControlBase.send_message(self, message, keyID, type_='chat',
chatstate=chatstate_to_send, xhtml=xhtml, callback=_on_sent,
callback_args=[contact, message, encrypted, xhtml,
self.get_seclabel()], process_commands=process_commands)
self.get_seclabel()], process_commands=process_commands,
attention=attention)
def check_for_possible_paused_chatstate(self, arg):
"""

View File

@ -230,6 +230,11 @@ class StandardCommonChatCommands(CommandContainer):
state = self._video_button.get_active()
self._video_button.set_active(not state)
@command(raw=True)
@doc(_("Send a message to the contact that will attract his (her) attention"))
def attention(self, message):
self.send_message(message, process_commands=False, attention=True)
class StandardChatCommands(CommandContainer):
"""
This command container contains standard commands which are unique

View File

@ -300,6 +300,7 @@ class Config:
'stun_server': [opt_str, '', _('STUN server to use when using jingle')],
'show_affiliation_in_groupchat': [opt_bool, True, _('If True, Gajim will show affiliation of groupchat occupants by adding a colored square to the status icon')],
'global_proxy': [opt_str, '', _('Proxy used for all outgoing connections if the account does not have a specific proxy configured')],
'ignore_incoming_attention': [opt_bool, False, _('If True, Gajim will ignore incoming attention requestd ("wizz").')],
}
__options_per_key = {
@ -497,6 +498,7 @@ class Config:
}
soundevents_default = {
'attention_received': [True, 'attention.wav'],
'first_message_received': [ True, 'message1.wav' ],
'next_message_received_focused': [ True, 'message2.wav' ],
'next_message_received_unfocused': [ True, 'message2.wav' ],

View File

@ -253,7 +253,7 @@ class CommonConnection:
def _prepare_message(self, jid, msg, keyID, type_='chat', subject='',
chatstate=None, msg_id=None, resource=None, user_nick=None, xhtml=None,
session=None, forward_from=None, form_node=None, label=None,
original_message=None, delayed=None, callback=None):
original_message=None, delayed=None, attention=False, callback=None):
if not self.connection or self.connected < 2:
return 1
try:
@ -304,7 +304,8 @@ class CommonConnection:
msgtxt, original_message, fjid, resource,
jid, xhtml, subject, chatstate, msg_id,
label, forward_from, delayed, session,
form_node, user_nick, keyID, callback)
form_node, user_nick, keyID, attention,
callback)
gajim.nec.push_incoming_event(GPGTrustKeyEvent(None,
conn=self, callback=_on_always_trust))
else:
@ -312,7 +313,7 @@ class CommonConnection:
original_message, fjid, resource, jid, xhtml,
subject, chatstate, msg_id, label, forward_from,
delayed, session, form_node, user_nick, keyID,
callback)
attention, callback)
gajim.thread_interface(encrypt_thread, [msg, keyID, False],
_on_encrypted, [])
return
@ -320,18 +321,18 @@ class CommonConnection:
self._message_encrypted_cb(('', error), type_, msg, msgtxt,
original_message, fjid, resource, jid, xhtml, subject,
chatstate, msg_id, label, forward_from, delayed, session,
form_node, user_nick, keyID, callback)
form_node, user_nick, keyID, attention, callback)
return
self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id,
label, forward_from, delayed, session, form_node, user_nick,
callback)
attention, callback)
def _message_encrypted_cb(self, output, type_, msg, msgtxt,
original_message, fjid, resource, jid, xhtml, subject, chatstate, msg_id,
label, forward_from, delayed, session, form_node, user_nick, keyID,
callback):
attention, callback):
msgenc, error = output
if msgenc and not error:
@ -344,7 +345,7 @@ class CommonConnection:
self._on_continue_message(type_, msg, msgtxt, original_message,
fjid, resource, jid, xhtml, subject, msgenc, keyID,
chatstate, msg_id, label, forward_from, delayed, session,
form_node, user_nick, callback)
form_node, user_nick, attention, callback)
return
# Encryption failed, do not send message
tim = localtime()
@ -353,7 +354,8 @@ class CommonConnection:
def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid,
resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id,
label, forward_from, delayed, session, form_node, user_nick, callback):
label, forward_from, delayed, session, form_node, user_nick, attention,
callback):
if type_ == 'chat':
msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_,
xhtml=xhtml)
@ -424,6 +426,10 @@ class CommonConnection:
if session.enable_encryption:
msg_iq = session.encrypt_stanza(msg_iq)
# XEP-0224
if attention:
msg_iq.setTag('attention', namespace=common.xmpp.NS_ATTENTION)
if callback:
callback(jid, msg, keyID, forward_from, session, original_message,
subject, type_, msg_iq, xhtml)
@ -1794,8 +1800,8 @@ class Connection(CommonConnection, ConnectionHandlers):
def send_message(self, jid, msg, keyID=None, type_='chat', subject='',
chatstate=None, msg_id=None, resource=None, user_nick=None, xhtml=None,
label=None, session=None, forward_from=None, form_node=None,
original_message=None, delayed=None, callback=None, callback_args=[],
now=False):
original_message=None, delayed=None, attention=False, callback=None,
callback_args=[], now=False):
def cb(jid, msg, keyID, forward_from, session, original_message,
subject, type_, msg_iq, xhtml):
@ -1813,7 +1819,8 @@ class Connection(CommonConnection, ConnectionHandlers):
chatstate=chatstate, msg_id=msg_id, resource=resource,
user_nick=user_nick, xhtml=xhtml, label=label, session=session,
forward_from=forward_from, form_node=form_node,
original_message=original_message, delayed=delayed, callback=cb)
original_message=original_message, delayed=delayed,
attention=attention, callback=cb)
def _nec_message_outgoing(self, obj):
if obj.account != self.name:
@ -1838,7 +1845,7 @@ class Connection(CommonConnection, ConnectionHandlers):
resource=obj.resource, user_nick=obj.user_nick, xhtml=obj.xhtml,
label=obj.label, session=obj.session, forward_from=obj.forward_from,
form_node=obj.form_node, original_message=obj.original_message,
delayed=obj.delayed, callback=cb)
delayed=obj.delayed, attention=obj.attention, callback=cb)
def send_contacts(self, contacts, jid):
"""

View File

@ -1192,6 +1192,7 @@ class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.sent = self.msg_obj.sent
self.popup = False
self.msg_id = None # id in log database
self.attention = False # XEP-0224
self.receipt_request_tag = self.stanza.getTag('request',
namespace=xmpp.NS_RECEIPTS)
@ -1206,6 +1207,9 @@ class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
if self.seclabel:
self.displaymarking = self.seclabel.getTag('displaymarking')
if self.stanza.getTag('attention', namespace=xmpp.NS_ATTENTION):
self.attention = True
self.form_node = self.stanza.getTag('x', namespace=xmpp.NS_DATA)
if gajim.config.get('ignore_incoming_xhtml'):
@ -2062,7 +2066,19 @@ class NotificationEvent(nec.NetworkIncomingEvent):
# we're online or chat
self.do_popup = True
if self.first_unread and helpers.allow_sound_notification(
if msg_obj.attention and not gajim.config.get(
'ignore_incoming_attention'):
self.popup_timeout = 0
self.do_popup = True
else:
self.popup_timeout = gajim.config.get('notification_timeout')
if msg_obj.attention and not gajim.config.get(
'ignore_incoming_attention') and gajim.config.get_per('soundevents',
'attention_received', 'enabled'):
self.sound_event = 'attention_received'
self.do_sound = True
elif self.first_unread and helpers.allow_sound_notification(
self.conn.name, 'first_message_received'):
self.do_sound = True
elif not self.first_unread and self.control_focused and \
@ -2175,6 +2191,8 @@ class NotificationEvent(nec.NetworkIncomingEvent):
self.popup_image = gtkgui_helpers.get_path_to_generic_or_avatar(
img_path, jid=self.jid, suffix=suffix)
self.popup_timeout = gajim.config.get('notification_timeout')
if event == 'status_change':
self.popup_title = _('%(nick)s Changed Status') % \
{'nick': gajim.get_name_from_jid(account, self.jid)}
@ -2219,6 +2237,7 @@ class NotificationEvent(nec.NetworkIncomingEvent):
self.popup_event_type = ''
self.popup_msg_type = ''
self.popup_image = ''
self.popup_timeout = -1
self.do_command = False
self.command = ''
@ -2261,6 +2280,7 @@ class MessageOutgoingEvent(nec.NetworkOutgoingEvent):
self.now = False
self.is_loggable = True
self.control = None
self.attention = False
def generate(self):
return True

View File

@ -40,6 +40,7 @@ NS_ARCHIVE_MANAGE = NS_ARCHIVE + ':manage' # XEP-0136
NS_ARCHIVE_MANUAL = NS_ARCHIVE + ':manual' # XEP-0136
NS_ARCHIVE_PREF = NS_ARCHIVE + ':pref'
NS_ATOM = 'http://www.w3.org/2005/Atom'
NS_ATTENTION = 'urn:xmpp:attention:0' # XEP-0224
NS_AUTH = 'jabber:iq:auth'
NS_AVATAR = 'http://www.xmpp.org/extensions/xep-0084.html#ns-metadata'
NS_BIND = 'urn:ietf:params:xml:ns:xmpp-bind'

View File

@ -336,8 +336,8 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
def send_message(self, jid, msg, keyID, type_='chat', subject='',
chatstate=None, msg_id=None, resource=None, user_nick=None, xhtml=None,
label=None, session=None, forward_from=None, form_node=None,
original_message=None, delayed=None, callback=None, callback_args=[],
now=True):
original_message=None, delayed=None, attention=False, callback=None,
callback_args=[], now=True):
def on_send_ok(msg_id):
gajim.nec.push_incoming_event(MessageSentEvent(None, conn=self,
@ -370,7 +370,8 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
chatstate=chatstate, msg_id=msg_id, resource=resource,
user_nick=user_nick, xhtml=xhtml, session=session,
forward_from=forward_from, form_node=form_node,
original_message=original_message, delayed=delayed, callback=cb)
original_message=original_message, delayed=delayed,
attention=attention, callback=cb)
def _nec_message_outgoing(self, obj):
if obj.account != self.name:
@ -411,7 +412,7 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
resource=obj.resource, user_nick=obj.user_nick, xhtml=obj.xhtml,
label=obj.label, session=obj.session, forward_from=obj.forward_from,
form_node=obj.form_node, original_message=obj.original_message,
delayed=obj.delayed, callback=cb)
delayed=obj.delayed, attention=obj.attention, callback=cb)
def send_stanza(self, stanza):
# send a stanza untouched

View File

@ -4188,6 +4188,7 @@ class ManageSoundsWindow:
# NOTE: sounds_ui_names MUST have all items of
# sounds = gajim.config.get_per('soundevents') as keys
sounds_dict = {
'attention_received': _('Attention Message Received'),
'first_message_received': _('First Message Received'),
'next_message_received_focused': _('Next Message Received Focused'),
'next_message_received_unfocused':

View File

@ -2745,7 +2745,7 @@ class ChangePasswordDialog:
class PopupNotificationWindow:
def __init__(self, event_type, jid, account, msg_type='',
path_to_image=None, title=None, text=None):
path_to_image=None, title=None, text=None, timeout=-1):
self.account = account
self.jid = jid
self.msg_type = msg_type
@ -2765,8 +2765,8 @@ class PopupNotificationWindow:
title = ''
event_type_label.set_markup(
'<span foreground="black" weight="bold">%s</span>' %
gobject.markup_escape_text(title))
'<span foreground="black" weight="bold">%s</span>' %
gobject.markup_escape_text(title))
# set colors [ http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html ]
self.window.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('black'))
@ -2787,7 +2787,7 @@ class PopupNotificationWindow:
elif event_type == _('File Transfer Error'):
bg_color = gajim.config.get('notif_fterror_color')
elif event_type in (_('File Transfer Completed'),
_('File Transfer Stopped')):
_('File Transfer Stopped')):
bg_color = gajim.config.get('notif_ftcomplete_color')
elif event_type == _('Groupchat Invitation'):
bg_color = gajim.config.get('notif_invite_color')
@ -2813,13 +2813,13 @@ class PopupNotificationWindow:
pos_y = gajim.config.get('notification_position_y')
if pos_y < 0:
pos_y = gtk.gdk.screen_height() - \
gajim.interface.roster.popups_notification_height + pos_y + 1
gajim.interface.roster.popups_notification_height + pos_y + 1
self.window.move(pos_x, pos_y)
xml.connect_signals(self)
self.window.show_all()
timeout = gajim.config.get('notification_timeout')
gobject.timeout_add_seconds(timeout, self.on_timeout)
if timeout > 0:
gobject.timeout_add_seconds(timeout, self.on_timeout)
def on_close_button_clicked(self, widget):
self.adjust_height_and_move_popup_notification_windows()

View File

@ -212,7 +212,8 @@ class PrivateChatControl(ChatControl):
self.parent_win.redraw_tab(self)
self.update_ui()
def send_message(self, message, xhtml=None, process_commands=True):
def send_message(self, message, xhtml=None, process_commands=True,
attention=False):
"""
Call this method to send the message
"""
@ -237,7 +238,7 @@ class PrivateChatControl(ChatControl):
return
ChatControl.send_message(self, message, xhtml=xhtml,
process_commands=process_commands)
process_commands=process_commands, attention=attention)
def update_ui(self):
if self.contact.show == 'offline':

View File

@ -73,7 +73,7 @@ def get_show_in_systray(event, account, contact, type_=None):
return gajim.config.get('trayicon_notification_on_events')
def popup(event_type, jid, account, msg_type='', path_to_image=None, title=None,
text=None):
text=None, timeout=-1):
"""
Notify 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
@ -83,11 +83,14 @@ text=None):
if not path_to_image:
path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48)
if timeout < 0:
timeout = gajim.config.get('notification_timeout')
# Try to show our popup via D-Bus and notification daemon
if gajim.config.get('use_notif_daemon') and dbus_support.supported:
try:
DesktopNotification(event_type, jid, account, msg_type,
path_to_image, title, gobject.markup_escape_text(text))
path_to_image, title, gobject.markup_escape_text(text), timeout)
return # sucessfully did D-Bus Notification procedure!
except dbus.DBusException, e:
# Connection to D-Bus failed
@ -112,8 +115,7 @@ text=None):
_title = title
notification = pynotify.Notification(_title, _text)
timeout = gajim.config.get('notification_timeout') * 1000 # make it ms
notification.set_timeout(timeout)
notification.set_timeout(timeout*1000)
notification.set_category(event_type)
notification.set_data('event_type', event_type)
@ -134,7 +136,7 @@ text=None):
# Either nothing succeeded or the user wants old-style notifications
instance = PopupNotificationWindow(event_type, jid, account, msg_type,
path_to_image, title, text)
path_to_image, title, text, timeout)
gajim.interface.roster.popup_notification_windows.append(instance)
def on_pynotify_notification_clicked(notification, action):
@ -157,7 +159,8 @@ class Notification:
if obj.do_popup:
popup(obj.popup_event_type, obj.jid, obj.conn.name,
obj.popup_msg_type, path_to_image=obj.popup_image,
title=obj.popup_title, text=obj.popup_text)
title=obj.popup_title, text=obj.popup_text,
timeout=obj.popup_timeout)
if obj.do_sound:
if obj.sound_file:
@ -235,11 +238,12 @@ class DesktopNotification:
"""
def __init__(self, event_type, jid, account, msg_type='',
path_to_image=None, title=None, text=None):
path_to_image=None, title=None, text=None, timeout=-1):
self.path_to_image = os.path.abspath(path_to_image)
self.event_type = event_type
self.title = title
self.text = text
self.timeout = timeout
# 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.
@ -302,7 +306,6 @@ class DesktopNotification:
self.get_version()
def attempt_notify(self):
timeout = gajim.config.get('notification_timeout') # in seconds
ntype = self.ntype
if self.kde_notifications:
notification_text = ('<html><img src="%(image)s" align=left />' \
@ -320,8 +323,8 @@ class DesktopNotification:
# actions (stringlist)
(dbus.String('default'), dbus.String(self.event_type),
dbus.String('ignore'), dbus.String(_('Ignore'))),
[], # hints (not used in KDE yet)
dbus.UInt32(timeout*1000), # timeout (int), in ms
[], # hints (not used in KDE yet)
dbus.UInt32(self.timeout*1000), # timeout (int), in ms
reply_handler=self.attach_by_id,
error_handler=self.notify_another_way)
return
@ -345,7 +348,7 @@ class DesktopNotification:
actions,
[''],
True,
dbus.UInt32(timeout),
dbus.UInt32(self.timeout),
reply_handler=self.attach_by_id,
error_handler=self.notify_another_way)
except AttributeError:
@ -392,7 +395,7 @@ class DesktopNotification:
dbus.String(text),
actions,
hints,
dbus.UInt32(timeout*1000),
dbus.UInt32(self.timeout*1000),
reply_handler=self.attach_by_id,
error_handler=self.notify_another_way)
except Exception, e:
@ -407,7 +410,7 @@ class DesktopNotification:
dbus.String(self.text),
dbus.String(''),
hints,
dbus.UInt32(timeout*1000),
dbus.UInt32(self.timeout*1000),
reply_handler=self.attach_by_id,
error_handler=self.notify_another_way)
except Exception, e:
@ -422,7 +425,7 @@ class DesktopNotification:
str(e))
instance = PopupNotificationWindow(self.event_type, self.jid,
self.account, self.msg_type, self.path_to_image, self.title,
self.text)
self.text, self.timeout)
gajim.interface.roster.popup_notification_windows.append(instance)
def on_action_invoked(self, id_, reason):