add XEP-0224 support (/attention command, persistant popup and special sound). F
ixes #3465
This commit is contained in:
parent
51cfe177a1
commit
75c495979c
Binary file not shown.
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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' ],
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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
|
||||
|
@ -2818,7 +2818,7 @@ class PopupNotificationWindow:
|
|||
|
||||
xml.connect_signals(self)
|
||||
self.window.show_all()
|
||||
timeout = gajim.config.get('notification_timeout')
|
||||
if timeout > 0:
|
||||
gobject.timeout_add_seconds(timeout, self.on_timeout)
|
||||
|
||||
def on_close_button_clicked(self, widget):
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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 />' \
|
||||
|
@ -321,7 +324,7 @@ class DesktopNotification:
|
|||
(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
|
||||
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):
|
||||
|
|
Loading…
Reference in New Issue