Refactor Chat State Notifications
- Move code into chatstate module - Refactor most of the code, make it much simpler
This commit is contained in:
parent
07b175d541
commit
460d390795
|
@ -36,7 +36,6 @@ from gi.repository import GLib
|
|||
from nbxmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
|
||||
from nbxmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO
|
||||
from nbxmpp.protocol import NS_JINGLE_ICE_UDP, NS_JINGLE_FILE_TRANSFER_5
|
||||
from nbxmpp.protocol import NS_CHATSTATES
|
||||
|
||||
from gajim import gtkgui_helpers
|
||||
from gajim import gui_menu_builder
|
||||
|
@ -50,8 +49,9 @@ from gajim.common import helpers
|
|||
from gajim.common import ged
|
||||
from gajim.common import i18n
|
||||
from gajim.common.contacts import GC_Contact
|
||||
from gajim.common.connection_handlers_events import MessageOutgoingEvent
|
||||
from gajim.common.const import AvatarSize, KindConstant
|
||||
from gajim.common.const import AvatarSize
|
||||
from gajim.common.const import KindConstant
|
||||
from gajim.common.const import Chatstate
|
||||
|
||||
from gajim.command_system.implementation.hosts import ChatCommands
|
||||
from gajim.command_system.framework import CommandHost # pylint: disable=unused-import
|
||||
|
@ -678,7 +678,7 @@ class ChatControl(ChatControlBase):
|
|||
status_escaped = GLib.markup_escape_text(status_reduced)
|
||||
|
||||
st = app.config.get('displayed_chat_state_notifications')
|
||||
cs = contact.chatstate
|
||||
cs = app.contacts.get_combined_chatstate(self.account, self.contact.jid)
|
||||
if cs and st in ('composing_only', 'all'):
|
||||
if contact.show == 'offline':
|
||||
chatstate = ''
|
||||
|
@ -882,8 +882,8 @@ class ChatControl(ChatControlBase):
|
|||
correct_id=obj.correct_id,
|
||||
additional_data=obj.additional_data)
|
||||
|
||||
def send_message(self, message, keyID='', chatstate=None, xhtml=None,
|
||||
process_commands=True, attention=False):
|
||||
def send_message(self, message, keyID='', xhtml=None,
|
||||
process_commands=True, attention=False):
|
||||
"""
|
||||
Send a message to contact
|
||||
"""
|
||||
|
@ -902,18 +902,13 @@ class ChatControl(ChatControlBase):
|
|||
contact = self.contact
|
||||
keyID = contact.keyID
|
||||
|
||||
chatstate_to_send = None
|
||||
if contact is not None:
|
||||
if contact.supports(NS_CHATSTATES):
|
||||
# send active chatstate on every message (as XEP says)
|
||||
chatstate_to_send = 'active'
|
||||
contact.our_chatstate = 'active'
|
||||
|
||||
self._schedule_activity_timers()
|
||||
|
||||
ChatControlBase.send_message(self, message, keyID, type_='chat',
|
||||
chatstate=chatstate_to_send, xhtml=xhtml,
|
||||
process_commands=process_commands, attention=attention)
|
||||
ChatControlBase.send_message(self,
|
||||
message,
|
||||
keyID,
|
||||
type_='chat',
|
||||
xhtml=xhtml,
|
||||
process_commands=process_commands,
|
||||
attention=attention)
|
||||
|
||||
def get_our_nick(self):
|
||||
return app.nicks[self.account]
|
||||
|
@ -1059,79 +1054,6 @@ class ChatControl(ChatControlBase):
|
|||
show_buttonbar_items=not hide_buttonbar_items)
|
||||
return menu
|
||||
|
||||
def send_chatstate(self, state, contact=None):
|
||||
"""
|
||||
Send OUR chatstate as STANDLONE chat state message (eg. no body)
|
||||
to contact only if new chatstate is different from the previous one
|
||||
if jid is not specified, send to active tab
|
||||
"""
|
||||
# JEP 85 does not allow resending the same chatstate
|
||||
# this function checks for that and just returns so it's safe to call it
|
||||
# with same state.
|
||||
|
||||
# This functions also checks for violation in state transitions
|
||||
# and raises RuntimeException with appropriate message
|
||||
# more on that http://xmpp.org/extensions/xep-0085.html#statechart
|
||||
|
||||
# do not send if we have chat state notifications disabled
|
||||
# that means we won't reply to the <active/> from other peer
|
||||
# so we do not broadcast jep85 capabalities
|
||||
chatstate_setting = app.config.get('outgoing_chat_state_notifications')
|
||||
if chatstate_setting == 'disabled':
|
||||
return
|
||||
|
||||
# Dont leak presence to contacts
|
||||
# which are not allowed to see our status
|
||||
if contact and contact.sub in ('to', 'none'):
|
||||
return
|
||||
|
||||
if self.contact.jid == app.get_jid_from_account(self.account):
|
||||
return
|
||||
|
||||
if chatstate_setting == 'composing_only' and state != 'active' and\
|
||||
state != 'composing':
|
||||
return
|
||||
|
||||
if contact is None:
|
||||
contact = self.parent_win.get_active_contact()
|
||||
if contact is None:
|
||||
# contact was from pm in MUC, and left the room so contact is None
|
||||
# so we cannot send chatstate anymore
|
||||
return
|
||||
|
||||
# Don't send chatstates to offline contacts
|
||||
if contact.show == 'offline':
|
||||
return
|
||||
|
||||
if not contact.supports(NS_CHATSTATES):
|
||||
return
|
||||
if contact.our_chatstate is False:
|
||||
return
|
||||
|
||||
# if the new state we wanna send (state) equals
|
||||
# the current state (contact.our_chatstate) then return
|
||||
if contact.our_chatstate == state:
|
||||
return
|
||||
|
||||
# if wel're inactive prevent composing (XEP violation)
|
||||
if contact.our_chatstate == 'inactive' and state == 'composing':
|
||||
# go active before
|
||||
app.log('chatstates').info('%-10s - %s', 'active', self.contact.jid)
|
||||
app.nec.push_outgoing_event(MessageOutgoingEvent(None,
|
||||
account=self.account, jid=self.contact.jid, chatstate='active',
|
||||
control=self))
|
||||
contact.our_chatstate = 'active'
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
|
||||
app.log('chatstates').info('%-10s - %s', state, self.contact.jid)
|
||||
app.nec.push_outgoing_event(MessageOutgoingEvent(None,
|
||||
account=self.account, jid=self.contact.jid, chatstate=state,
|
||||
control=self))
|
||||
|
||||
contact.our_chatstate = state
|
||||
if state == 'active':
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
|
||||
def shutdown(self):
|
||||
# PluginSystem: removing GUI extension points connected with ChatControl
|
||||
# instance object
|
||||
|
@ -1161,9 +1083,8 @@ class ChatControl(ChatControlBase):
|
|||
self.unsubscribe_events()
|
||||
|
||||
# Send 'gone' chatstate
|
||||
self.send_chatstate('gone', self.contact)
|
||||
self.contact.chatstate = None
|
||||
self.contact.our_chatstate = None
|
||||
con = app.connections[self.account]
|
||||
con.get_module('Chatstate').set_chatstate(self.contact, Chatstate.GONE)
|
||||
|
||||
for jingle_type in ('audio', 'video'):
|
||||
self.close_jingle_content(jingle_type)
|
||||
|
@ -1225,13 +1146,18 @@ class ChatControl(ChatControlBase):
|
|||
return
|
||||
on_yes(self)
|
||||
|
||||
def _nec_chatstate_received(self, obj):
|
||||
"""
|
||||
Handle incoming chatstate that jid SENT TO us
|
||||
"""
|
||||
def _nec_chatstate_received(self, event):
|
||||
if event.account != self.account:
|
||||
return
|
||||
|
||||
if event.jid != self.contact.jid:
|
||||
return
|
||||
|
||||
self.draw_banner_text()
|
||||
# update chatstate in tab for this chat
|
||||
self.parent_win.redraw_tab(self, self.contact.chatstate)
|
||||
chatstate = app.contacts.get_combined_chatstate(
|
||||
self.account, self.contact.jid)
|
||||
self.parent_win.redraw_tab(self, chatstate)
|
||||
|
||||
def _nec_caps_received(self, obj):
|
||||
if obj.conn.name != self.account:
|
||||
|
|
|
@ -49,6 +49,7 @@ from gajim.message_textview import MessageTextView
|
|||
from gajim.common.contacts import GC_Contact
|
||||
from gajim.common.connection_handlers_events import MessageOutgoingEvent
|
||||
from gajim.common.const import StyleAttr
|
||||
from gajim.common.const import Chatstate
|
||||
|
||||
from gajim.command_system.implementation.middleware import ChatCommandProcessor
|
||||
from gajim.command_system.implementation.middleware import CommandTools
|
||||
|
@ -325,10 +326,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
# Security Labels
|
||||
self.seclabel_combo = self.xml.get_object('label_selector')
|
||||
|
||||
# chatstate timers and state
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
self.possible_paused_timeout_id = None
|
||||
self.possible_inactive_timeout_id = None
|
||||
con = app.connections[self.account]
|
||||
con.get_module('Chatstate').set_active(self.contact.jid)
|
||||
|
||||
message_tv_buffer = self.msg_textview.get_buffer()
|
||||
id_ = message_tv_buffer.connect('changed',
|
||||
self._on_message_tv_buffer_changed)
|
||||
|
@ -337,7 +337,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
id_ = parent_win.window.connect('motion-notify-event',
|
||||
self._on_window_motion_notify)
|
||||
self.handlers[id_] = parent_win.window
|
||||
self._schedule_activity_timers()
|
||||
|
||||
self.encryption = self.get_encryption_state()
|
||||
self.conv_textview.encryption_enabled = self.encryption is not None
|
||||
|
@ -520,11 +519,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
|
||||
def shutdown(self):
|
||||
super(ChatControlBase, self).shutdown()
|
||||
# Disconnect timer callbacks
|
||||
if self.possible_paused_timeout_id:
|
||||
GLib.source_remove(self.possible_paused_timeout_id)
|
||||
if self.possible_inactive_timeout_id:
|
||||
GLib.source_remove(self.possible_inactive_timeout_id)
|
||||
# PluginSystem: removing GUI extension points connected with ChatControlBase
|
||||
# instance object
|
||||
app.plugin_manager.remove_gui_extension_point('chat_control_base',
|
||||
|
@ -777,7 +771,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
label = labels[lname]
|
||||
return label
|
||||
|
||||
def send_message(self, message, keyID='', type_='chat', chatstate=None,
|
||||
def send_message(self, message, keyID='', type_='chat',
|
||||
resource=None, xhtml=None, process_commands=True, attention=False):
|
||||
"""
|
||||
Send the given message to the active tab. Doesn't return None if error
|
||||
|
@ -788,14 +782,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
if process_commands and self.process_as_command(message):
|
||||
return
|
||||
|
||||
# refresh timers
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
|
||||
notifications = app.config.get('outgoing_chat_state_notifications')
|
||||
if (self.contact.jid == app.get_jid_from_account(self.account) or
|
||||
notifications == 'disabled'):
|
||||
chatstate = None
|
||||
|
||||
label = self.get_seclabel()
|
||||
|
||||
if self.correcting and self.last_sent_msg:
|
||||
|
@ -803,6 +789,10 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
else:
|
||||
correct_id = None
|
||||
|
||||
con = app.connections[self.account]
|
||||
chatstate = con.get_module('Chatstate').get_active_chatstate(
|
||||
self.contact)
|
||||
|
||||
app.nec.push_outgoing_event(MessageOutgoingEvent(None,
|
||||
account=self.account, jid=self.contact.jid, message=message,
|
||||
keyID=keyID, type_=type_, chatstate=chatstate,
|
||||
|
@ -820,76 +810,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
message_buffer = self.msg_textview.get_buffer()
|
||||
message_buffer.set_text('') # clear message buffer (and tv of course)
|
||||
|
||||
def check_for_possible_paused_chatstate(self, arg):
|
||||
"""
|
||||
Did we move mouse of that window or write something in message textview
|
||||
in the last 5 seconds? If yes - we go active for mouse, composing for
|
||||
kbd. If not - we go paused if we were previously composing
|
||||
"""
|
||||
contact = self.contact
|
||||
jid = contact.jid
|
||||
current_state = contact.our_chatstate
|
||||
if current_state is False: # jid doesn't support chatstates
|
||||
self.possible_paused_timeout_id = None
|
||||
return False # stop looping
|
||||
|
||||
if current_state == 'composing':
|
||||
if not self.kbd_activity_in_last_5_secs:
|
||||
if self.msg_textview.has_text():
|
||||
self.send_chatstate('paused', self.contact)
|
||||
else:
|
||||
self.send_chatstate('active', self.contact)
|
||||
elif current_state == 'inactive':
|
||||
if (self.mouse_over_in_last_5_secs and
|
||||
jid == self.parent_win.get_active_jid()):
|
||||
self.send_chatstate('active', self.contact)
|
||||
|
||||
# assume no activity and let the motion-notify or 'insert-text' make them
|
||||
# True refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds!
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
return True # loop forever
|
||||
|
||||
def check_for_possible_inactive_chatstate(self, arg):
|
||||
"""
|
||||
Did we move mouse over that window or wrote something in message textview
|
||||
in the last 30 seconds? if yes - we go active. If no - we go inactive
|
||||
"""
|
||||
contact = self.contact
|
||||
|
||||
current_state = contact.our_chatstate
|
||||
if current_state is False: # jid doesn't support chatstates
|
||||
self.possible_inactive_timeout_id = None
|
||||
return False # stop looping
|
||||
|
||||
if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs:
|
||||
return True # loop forever
|
||||
|
||||
if not self.mouse_over_in_last_30_secs or \
|
||||
self.kbd_activity_in_last_30_secs:
|
||||
self.send_chatstate('inactive', contact)
|
||||
|
||||
# assume no activity and let the motion-notify or 'insert-text' make them
|
||||
# True refresh 30 seconds too or else it's 30 - 5 = 25 seconds!
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
return True # loop forever
|
||||
|
||||
def _schedule_activity_timers(self):
|
||||
if self.possible_paused_timeout_id:
|
||||
GLib.source_remove(self.possible_paused_timeout_id)
|
||||
if self.possible_inactive_timeout_id:
|
||||
GLib.source_remove(self.possible_inactive_timeout_id)
|
||||
self.possible_paused_timeout_id = GLib.timeout_add_seconds(5,
|
||||
self.check_for_possible_paused_chatstate, None)
|
||||
self.possible_inactive_timeout_id = GLib.timeout_add_seconds(30,
|
||||
self.check_for_possible_inactive_chatstate, None)
|
||||
|
||||
def reset_kbd_mouse_timeout_vars(self):
|
||||
self.kbd_activity_in_last_5_secs = False
|
||||
self.mouse_over_in_last_5_secs = False
|
||||
self.mouse_over_in_last_30_secs = False
|
||||
self.kbd_activity_in_last_30_secs = False
|
||||
|
||||
def _on_window_motion_notify(self, widget, event):
|
||||
def _on_window_motion_notify(self, *args):
|
||||
"""
|
||||
It gets called no matter if it is the active window or not
|
||||
"""
|
||||
|
@ -897,16 +818,19 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
# when a groupchat is minimized there is no parent window
|
||||
return
|
||||
if self.parent_win.get_active_jid() == self.contact.jid:
|
||||
# if window is the active one, change vars assisting chatstate
|
||||
self.mouse_over_in_last_5_secs = True
|
||||
self.mouse_over_in_last_30_secs = True
|
||||
# if window is the active one, set last interaction
|
||||
con = app.connections[self.account]
|
||||
con.get_module('Chatstate').set_mouse_activity(self.contact)
|
||||
|
||||
def _on_message_tv_buffer_changed(self, textbuffer):
|
||||
self.kbd_activity_in_last_5_secs = True
|
||||
self.kbd_activity_in_last_30_secs = True
|
||||
def _on_message_tv_buffer_changed(self, *args):
|
||||
con = app.connections[self.account]
|
||||
con.get_module('Chatstate').set_keyboard_activity(self.contact)
|
||||
if not self.msg_textview.has_text():
|
||||
con.get_module('Chatstate').set_chatstate(self.contact,
|
||||
Chatstate.ACTIVE)
|
||||
return
|
||||
self.send_chatstate('composing', self.contact)
|
||||
con.get_module('Chatstate').set_chatstate(self.contact,
|
||||
Chatstate.COMPOSING)
|
||||
|
||||
def save_message(self, message, msg_type):
|
||||
# save the message, so user can scroll though the list with key up/down
|
||||
|
@ -1183,6 +1107,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
widget.get_active())
|
||||
|
||||
def set_control_active(self, state):
|
||||
con = app.connections[self.account]
|
||||
if state:
|
||||
self.set_emoticon_popover()
|
||||
jid = self.contact.jid
|
||||
|
@ -1198,13 +1123,14 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
# send chatstate inactive to the one we're leaving
|
||||
# and active to the one we visit
|
||||
if self.msg_textview.has_text():
|
||||
self.send_chatstate('paused', self.contact)
|
||||
con.get_module('Chatstate').set_chatstate(self.contact,
|
||||
Chatstate.PAUSED)
|
||||
else:
|
||||
self.send_chatstate('active', self.contact)
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
self._schedule_activity_timers()
|
||||
con.get_module('Chatstate').set_chatstate(self.contact,
|
||||
Chatstate.ACTIVE)
|
||||
else:
|
||||
self.send_chatstate('inactive', self.contact)
|
||||
con.get_module('Chatstate').set_chatstate(self.contact,
|
||||
Chatstate.INACTIVE)
|
||||
|
||||
def scroll_to_end(self, force=False):
|
||||
self.conv_textview.scroll_to_end(force)
|
||||
|
|
|
@ -320,7 +320,7 @@ class CommonConnection:
|
|||
# chatstates - if peer supports xep85, send chatstates
|
||||
# please note that the only valid tag inside a message containing a
|
||||
# <body> tag is the active event
|
||||
if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES):
|
||||
if obj.chatstate is not None:
|
||||
msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
|
||||
if not obj.message:
|
||||
msg_iq.setTag('no-store',
|
||||
|
@ -1727,7 +1727,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
msg_iq.setTag('replace', attrs={'id': obj.correct_id},
|
||||
namespace=nbxmpp.NS_CORRECT)
|
||||
|
||||
if obj.chatstate:
|
||||
if obj.chatstate is not None:
|
||||
msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
|
||||
if not obj.message:
|
||||
msg_iq.setTag('no-store', namespace=nbxmpp.NS_MSG_HINTS)
|
||||
|
@ -1754,7 +1754,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
obj.stanza_id = self.connection.send(obj.msg_iq)
|
||||
app.nec.push_incoming_event(MessageSentEvent(
|
||||
None, conn=self, jid=obj.jid, message=obj.message, keyID=None,
|
||||
chatstate=None, automatic_message=obj.automatic_message,
|
||||
automatic_message=obj.automatic_message,
|
||||
stanza_id=obj.stanza_id, additional_data=obj.additional_data))
|
||||
|
||||
def send_gc_status(self, nick, jid, show, status, auto=False):
|
||||
|
|
|
@ -180,11 +180,8 @@ class ConnectionHandlersBase:
|
|||
return
|
||||
|
||||
# It isn't an agent
|
||||
# reset chatstate if needed:
|
||||
# (when contact signs out or has errors)
|
||||
if obj.show in ('offline', 'error'):
|
||||
obj.contact.our_chatstate = obj.contact.chatstate = None
|
||||
|
||||
# TODO: This causes problems when another
|
||||
# resource signs off!
|
||||
self.stop_all_active_file_transfers(obj.contact)
|
||||
|
|
|
@ -23,7 +23,6 @@ from time import time as time_time
|
|||
|
||||
import OpenSSL.crypto
|
||||
import nbxmpp
|
||||
from nbxmpp.protocol import NS_CHATSTATES
|
||||
|
||||
from gajim.common import nec
|
||||
from gajim.common import helpers
|
||||
|
@ -99,22 +98,6 @@ class HelperEvent:
|
|||
log.error('wrong timestamp, ignoring it: %s', tag)
|
||||
self.timestamp = time_time()
|
||||
|
||||
def get_chatstate(self):
|
||||
"""
|
||||
Extract chatstate from a <message/> stanza
|
||||
Requires self.stanza and self.msgtxt
|
||||
"""
|
||||
self.chatstate = None
|
||||
|
||||
# chatstates - look for chatstate tags in a message if not delayed
|
||||
delayed = self.stanza.getTag('x', namespace=nbxmpp.NS_DELAY) is not None
|
||||
if not delayed:
|
||||
children = self.stanza.getChildren()
|
||||
for child in children:
|
||||
if child.getNamespace() == NS_CHATSTATES:
|
||||
self.chatstate = child.getName()
|
||||
break
|
||||
|
||||
def get_oob_data(self, stanza):
|
||||
oob_node = stanza.getTag('x', namespace=nbxmpp.NS_X_OOB)
|
||||
if oob_node is not None:
|
||||
|
@ -434,17 +417,6 @@ class OurShowEvent(nec.NetworkIncomingEvent):
|
|||
class BeforeChangeShowEvent(nec.NetworkIncomingEvent):
|
||||
name = 'before-change-show'
|
||||
|
||||
class ChatstateReceivedEvent(nec.NetworkIncomingEvent):
|
||||
name = 'chatstate-received'
|
||||
|
||||
def generate(self):
|
||||
self.stanza = self.msg_obj.stanza
|
||||
self.jid = self.msg_obj.jid
|
||||
self.fjid = self.msg_obj.fjid
|
||||
self.resource = self.msg_obj.resource
|
||||
self.chatstate = self.msg_obj.chatstate
|
||||
return True
|
||||
|
||||
class GcMessageReceivedEvent(nec.NetworkIncomingEvent):
|
||||
name = 'gc-message-received'
|
||||
|
||||
|
|
|
@ -174,6 +174,18 @@ class PEPEventType(IntEnum):
|
|||
ATOM = 7
|
||||
|
||||
|
||||
@unique
|
||||
class Chatstate(IntEnum):
|
||||
COMPOSING = 0
|
||||
PAUSED = 1
|
||||
ACTIVE = 2
|
||||
INACTIVE = 3
|
||||
GONE = 4
|
||||
|
||||
def __str__(self):
|
||||
return self.name.lower()
|
||||
|
||||
|
||||
ACTIVITIES = {
|
||||
'doing_chores': {
|
||||
'category': _('Doing Chores'),
|
||||
|
|
|
@ -28,6 +28,7 @@ try:
|
|||
from gajim.common import caps_cache
|
||||
from gajim.common.account import Account
|
||||
from gajim import common
|
||||
from gajim.common.const import Chatstate
|
||||
except ImportError as e:
|
||||
if __name__ != "__main__":
|
||||
raise ImportError(str(e))
|
||||
|
@ -45,7 +46,7 @@ class XMPPEntity:
|
|||
class CommonContact(XMPPEntity):
|
||||
|
||||
def __init__(self, jid, account, resource, show, status, name,
|
||||
our_chatstate, chatstate, client_caps=None):
|
||||
chatstate, client_caps=None):
|
||||
|
||||
XMPPEntity.__init__(self, jid, account, resource)
|
||||
|
||||
|
@ -55,11 +56,8 @@ class CommonContact(XMPPEntity):
|
|||
|
||||
self.client_caps = client_caps or caps_cache.NullClientCaps()
|
||||
|
||||
# please read xep-85 http://www.xmpp.org/extensions/xep-0085.html
|
||||
# this holds what WE SEND to contact (our current chatstate)
|
||||
self.our_chatstate = our_chatstate
|
||||
# this is contact's chatstate
|
||||
self.chatstate = chatstate
|
||||
self._chatstate = chatstate
|
||||
|
||||
@property
|
||||
def show(self):
|
||||
|
@ -71,6 +69,27 @@ class CommonContact(XMPPEntity):
|
|||
raise TypeError('show must be a string')
|
||||
self._show = value
|
||||
|
||||
@property
|
||||
def chatstate_enum(self):
|
||||
return self._chatstate
|
||||
|
||||
@property
|
||||
def chatstate(self):
|
||||
if self._chatstate is None:
|
||||
return
|
||||
return str(self._chatstate)
|
||||
|
||||
@chatstate.setter
|
||||
def chatstate(self, value):
|
||||
if value is None:
|
||||
self._chatstate = value
|
||||
else:
|
||||
self._chatstate = Chatstate[value.upper()]
|
||||
|
||||
@property
|
||||
def is_gc_contact(self):
|
||||
return isinstance(self, GC_Contact)
|
||||
|
||||
def get_full_jid(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -97,14 +116,14 @@ class Contact(CommonContact):
|
|||
"""
|
||||
def __init__(self, jid, account, name='', groups=None, show='', status='',
|
||||
sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
|
||||
our_chatstate=None, chatstate=None, idle_time=None, avatar_sha=None, groupchat=False):
|
||||
chatstate=None, idle_time=None, avatar_sha=None, groupchat=False):
|
||||
if not isinstance(jid, str):
|
||||
print('no str')
|
||||
if groups is None:
|
||||
groups = []
|
||||
|
||||
CommonContact.__init__(self, jid, account, resource, show, status, name,
|
||||
our_chatstate, chatstate, client_caps=client_caps)
|
||||
chatstate, client_caps=client_caps)
|
||||
|
||||
self.contact_name = '' # nick choosen by contact
|
||||
self.groups = [i if i else _('General') for i in set(groups)] # filter duplicate values
|
||||
|
@ -182,11 +201,10 @@ class GC_Contact(CommonContact):
|
|||
"""
|
||||
|
||||
def __init__(self, room_jid, account, name='', show='', status='', role='',
|
||||
affiliation='', jid='', resource='', our_chatstate=None,
|
||||
chatstate=None, avatar_sha=None):
|
||||
affiliation='', jid='', resource='', chatstate=None, avatar_sha=None):
|
||||
|
||||
CommonContact.__init__(self, jid, account, resource, show, status, name,
|
||||
our_chatstate, chatstate)
|
||||
chatstate)
|
||||
|
||||
self.room_jid = room_jid
|
||||
self.role = role
|
||||
|
@ -254,7 +272,7 @@ class LegacyContactsAPI:
|
|||
|
||||
def create_contact(self, jid, account, name='', groups=None, show='',
|
||||
status='', sub='', ask='', resource='', priority=0, keyID='',
|
||||
client_caps=None, our_chatstate=None, chatstate=None, idle_time=None,
|
||||
client_caps=None, chatstate=None, idle_time=None,
|
||||
avatar_sha=None, groupchat=False):
|
||||
if groups is None:
|
||||
groups = []
|
||||
|
@ -263,8 +281,8 @@ class LegacyContactsAPI:
|
|||
return Contact(jid=jid, account=account, name=name, groups=groups,
|
||||
show=show, status=status, sub=sub, ask=ask, resource=resource,
|
||||
priority=priority, keyID=keyID, client_caps=client_caps,
|
||||
our_chatstate=our_chatstate, chatstate=chatstate,
|
||||
idle_time=idle_time, avatar_sha=avatar_sha, groupchat=groupchat)
|
||||
chatstate=chatstate, idle_time=idle_time, avatar_sha=avatar_sha,
|
||||
groupchat=groupchat)
|
||||
|
||||
def create_self_contact(self, jid, account, resource, show, status, priority,
|
||||
name='', keyID=''):
|
||||
|
@ -292,7 +310,7 @@ class LegacyContactsAPI:
|
|||
status=contact.status, sub=contact.sub, ask=contact.ask,
|
||||
resource=contact.resource, priority=contact.priority,
|
||||
keyID=contact.keyID, client_caps=contact.client_caps,
|
||||
our_chatstate=contact.our_chatstate, chatstate=contact.chatstate,
|
||||
chatstate=contact.chatstate,
|
||||
idle_time=contact.idle_time, avatar_sha=contact.avatar_sha)
|
||||
|
||||
def add_contact(self, account, contact):
|
||||
|
@ -451,6 +469,9 @@ class LegacyContactsAPI:
|
|||
return
|
||||
contact.avatar_sha = sha
|
||||
|
||||
def get_combined_chatstate(self, account, jid):
|
||||
return self._accounts[account].contacts.get_combined_chatstate(jid)
|
||||
|
||||
|
||||
class Contacts():
|
||||
"""
|
||||
|
@ -603,6 +624,18 @@ class Contacts():
|
|||
self._contacts[new_jid].append(_contact)
|
||||
del self._contacts[old_jid]
|
||||
|
||||
def get_combined_chatstate(self, jid):
|
||||
if jid not in self._contacts:
|
||||
return
|
||||
contacts = self._contacts[jid]
|
||||
states = []
|
||||
for contact in contacts:
|
||||
if contact.chatstate_enum is None:
|
||||
continue
|
||||
states.append(contact.chatstate_enum)
|
||||
|
||||
return str(min(states)) if states else None
|
||||
|
||||
|
||||
class GC_Contacts():
|
||||
|
||||
|
|
|
@ -14,20 +14,239 @@
|
|||
|
||||
# XEP-0085: Chat State Notifications
|
||||
|
||||
from typing import Any
|
||||
from typing import Dict # pylint: disable=unused-import
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
import time
|
||||
import logging
|
||||
|
||||
import nbxmpp
|
||||
from gi.repository import GLib
|
||||
|
||||
from gajim.common import app
|
||||
from gajim.common.nec import NetworkEvent
|
||||
from gajim.common.const import Chatstate as State
|
||||
from gajim.common.modules.misc import parse_delay
|
||||
from gajim.common.connection_handlers_events import MessageOutgoingEvent
|
||||
from gajim.common.connection_handlers_events import GcMessageOutgoingEvent
|
||||
|
||||
from gajim.common.types import ContactT
|
||||
from gajim.common.types import ConnectionT
|
||||
|
||||
log = logging.getLogger('gajim.c.m.chatstates')
|
||||
|
||||
INACTIVE_AFTER = 60
|
||||
PAUSED_AFTER = 5
|
||||
|
||||
def parse_chatstate(stanza):
|
||||
|
||||
def parse_chatstate(stanza: nbxmpp.Message) -> Optional[str]:
|
||||
if parse_delay(stanza) is not None:
|
||||
return
|
||||
return None
|
||||
|
||||
children = stanza.getChildren()
|
||||
for child in children:
|
||||
if child.getNamespace() == nbxmpp.NS_CHATSTATES:
|
||||
return child.getName()
|
||||
return None
|
||||
|
||||
|
||||
class Chatstate:
|
||||
def __init__(self, con: ConnectionT) -> None:
|
||||
self._con = con
|
||||
self._account = con.name
|
||||
|
||||
self.handlers = [
|
||||
('presence', self._presence_received),
|
||||
]
|
||||
self._chatstates = {} # type: Dict[str, State]
|
||||
self._last_keyboard_activity = {} # type: Dict[str, float]
|
||||
self._last_mouse_activity = {} # type: Dict[str, float]
|
||||
|
||||
self._timeout_id = GLib.timeout_add_seconds(
|
||||
2, self._check_last_interaction)
|
||||
|
||||
def _presence_received(self,
|
||||
_con: ConnectionT,
|
||||
stanza: nbxmpp.Presence) -> None:
|
||||
if stanza.getType() not in ('unavailable', 'error'):
|
||||
return
|
||||
|
||||
full_jid = stanza.getFrom()
|
||||
jid = full_jid.getStripped()
|
||||
|
||||
if self._con.get_own_jid().bareMatch(full_jid):
|
||||
return
|
||||
|
||||
contact = app.contacts.get_contact_from_full_jid(
|
||||
self._account, str(full_jid))
|
||||
if contact is None or contact.is_gc_contact:
|
||||
return
|
||||
|
||||
if contact.chatstate is None:
|
||||
return
|
||||
|
||||
contact.chatstate = None
|
||||
self._chatstates.pop(contact.jid, None)
|
||||
self._last_mouse_activity.pop(contact.jid, None)
|
||||
|
||||
log.info('Reset chatstate for %s', jid)
|
||||
|
||||
app.nec.push_outgoing_event(
|
||||
NetworkEvent('chatstate-received',
|
||||
account=self._account,
|
||||
jid=jid))
|
||||
|
||||
def delegate(self, event: Any) -> None:
|
||||
if self._con.get_own_jid().bareMatch(event.jid) or event.sent:
|
||||
# Dont show chatstates from our own resources
|
||||
return
|
||||
|
||||
chatstate = parse_chatstate(event.stanza)
|
||||
if chatstate is None:
|
||||
return
|
||||
|
||||
contact = app.contacts.get_contact_from_full_jid(
|
||||
self._account, event.fjid)
|
||||
if contact is None or contact.is_gc_contact:
|
||||
return
|
||||
|
||||
contact.chatstate = chatstate
|
||||
log.info('Recv: %-10s - %s', chatstate, event.fjid)
|
||||
app.nec.push_outgoing_event(
|
||||
NetworkEvent('chatstate-received',
|
||||
account=self._account,
|
||||
jid=event.jid))
|
||||
|
||||
def _check_last_interaction(self) -> GLib.SOURCE_CONTINUE:
|
||||
setting = app.config.get('outgoing_chat_state_notifications')
|
||||
if setting in ('composing_only', 'disabled'):
|
||||
return GLib.SOURCE_CONTINUE
|
||||
|
||||
now = time.time()
|
||||
for jid, time_ in self._last_mouse_activity.items():
|
||||
current_state = self._chatstates.get(jid)
|
||||
if current_state is None:
|
||||
self._last_mouse_activity.pop(jid, None)
|
||||
return GLib.SOURCE_CONTINUE
|
||||
|
||||
if current_state in (State.GONE, State.INACTIVE):
|
||||
return GLib.SOURCE_CONTINUE
|
||||
|
||||
new_chatstate = None
|
||||
if now - time_ > INACTIVE_AFTER:
|
||||
new_chatstate = State.INACTIVE
|
||||
|
||||
elif current_state == State.COMPOSING:
|
||||
key_time = self._last_keyboard_activity[jid]
|
||||
if now - key_time > PAUSED_AFTER:
|
||||
new_chatstate = State.PAUSED
|
||||
|
||||
if new_chatstate is not None:
|
||||
if self._chatstates.get(jid) != new_chatstate:
|
||||
contact = app.contacts.get_contact(self._account, jid)
|
||||
if contact is None:
|
||||
self._last_mouse_activity.pop(jid, None)
|
||||
return GLib.SOURCE_CONTINUE
|
||||
self.set_chatstate(contact, new_chatstate)
|
||||
|
||||
return GLib.SOURCE_CONTINUE
|
||||
|
||||
def set_active(self, jid: str) -> None:
|
||||
self._last_mouse_activity[jid] = time.time()
|
||||
setting = app.config.get('outgoing_chat_state_notifications')
|
||||
if setting == 'disabled':
|
||||
return
|
||||
self._chatstates[jid] = State.ACTIVE
|
||||
|
||||
def get_active_chatstate(self, contact: ContactT) -> Optional[str]:
|
||||
# determines if we add 'active' on outgoing messages
|
||||
setting = app.config.get('outgoing_chat_state_notifications')
|
||||
if setting == 'disabled':
|
||||
return None
|
||||
|
||||
# Dont send chatstates to ourself
|
||||
if self._con.get_own_jid().bareMatch(contact.jid):
|
||||
return None
|
||||
|
||||
if not contact.supports(nbxmpp.NS_CHATSTATES):
|
||||
return None
|
||||
|
||||
self.set_active(contact.jid)
|
||||
return 'active'
|
||||
|
||||
def set_chatstate(self, contact: ContactT, state: State) -> None:
|
||||
current_state = self._chatstates.get(contact.jid)
|
||||
setting = app.config.get('outgoing_chat_state_notifications')
|
||||
if setting == 'disabled':
|
||||
# Send a last 'gone' state after user disabled chatstates
|
||||
if current_state is not None:
|
||||
log.info('Send: %-10s - %s', State.GONE, contact.jid)
|
||||
app.nec.push_outgoing_event(
|
||||
MessageOutgoingEvent(None,
|
||||
account=self._account,
|
||||
jid=contact.jid,
|
||||
chatstate=str(State.GONE)))
|
||||
self._chatstates.pop(contact.jid, None)
|
||||
self._last_mouse_activity.pop(contact.jid, None)
|
||||
return
|
||||
|
||||
if not contact.is_groupchat():
|
||||
# Dont leak presence to contacts
|
||||
# which are not allowed to see our status
|
||||
if contact and contact.sub in ('to', 'none'):
|
||||
return
|
||||
|
||||
if contact.show == 'offline':
|
||||
return
|
||||
|
||||
if not contact.supports(nbxmpp.NS_CHATSTATES):
|
||||
return
|
||||
|
||||
if state in (State.ACTIVE, State.COMPOSING):
|
||||
self._last_mouse_activity[contact.jid] = time.time()
|
||||
|
||||
if setting == 'composing_only':
|
||||
if state in (State.INACTIVE, State.GONE, State.PAUSED):
|
||||
state = State.ACTIVE
|
||||
|
||||
if current_state == state:
|
||||
return
|
||||
|
||||
# Dont send chatstates to ourself
|
||||
if self._con.get_own_jid().bareMatch(contact.jid):
|
||||
return
|
||||
|
||||
log.info('Send: %-10s - %s', state, contact.jid)
|
||||
|
||||
event_attrs = {'account': self._account,
|
||||
'jid': contact.jid,
|
||||
'chatstate': str(state)}
|
||||
|
||||
if contact.is_groupchat():
|
||||
app.nec.push_outgoing_event(
|
||||
GcMessageOutgoingEvent(None, **event_attrs))
|
||||
else:
|
||||
app.nec.push_outgoing_event(
|
||||
MessageOutgoingEvent(None, **event_attrs))
|
||||
|
||||
self._chatstates[contact.jid] = state
|
||||
|
||||
def set_mouse_activity(self, contact: ContactT) -> None:
|
||||
self._last_mouse_activity[contact.jid] = time.time()
|
||||
setting = app.config.get('outgoing_chat_state_notifications')
|
||||
if setting == 'disabled':
|
||||
return
|
||||
if self._chatstates.get(contact.jid) == State.INACTIVE:
|
||||
self.set_chatstate(contact, State.ACTIVE)
|
||||
|
||||
def set_keyboard_activity(self, contact: ContactT) -> None:
|
||||
self._last_keyboard_activity[contact.jid] = time.time()
|
||||
|
||||
def cleanup(self):
|
||||
GLib.source_remove(self._timeout_id)
|
||||
|
||||
|
||||
def get_instance(*args: Any, **kwargs: Any) -> Tuple[Chatstate, str]:
|
||||
return Chatstate(*args, **kwargs), 'Chatstate'
|
||||
|
|
|
@ -24,7 +24,6 @@ from gajim.common import helpers
|
|||
from gajim.common.nec import NetworkIncomingEvent, NetworkEvent
|
||||
from gajim.common.modules.security_labels import parse_securitylabel
|
||||
from gajim.common.modules.user_nickname import parse_nickname
|
||||
from gajim.common.modules.chatstates import parse_chatstate
|
||||
from gajim.common.modules.carbons import parse_carbon
|
||||
from gajim.common.modules.misc import parse_delay
|
||||
from gajim.common.modules.misc import parse_eme
|
||||
|
@ -218,6 +217,7 @@ class Message:
|
|||
def _on_message_decrypted(self, event):
|
||||
try:
|
||||
self._con.get_module('Receipts').delegate(event)
|
||||
self._con.get_module('Chatstate').delegate(event)
|
||||
except nbxmpp.NodeProcessed:
|
||||
return
|
||||
|
||||
|
@ -236,7 +236,6 @@ class Message:
|
|||
'user_nick': '' if event.sent else parse_nickname(event.stanza),
|
||||
'form_node': parse_form(event.stanza),
|
||||
'xhtml': parse_xhtml(event.stanza),
|
||||
'chatstate': parse_chatstate(event.stanza),
|
||||
'timestamp': timestamp,
|
||||
'delayed': delayed,
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import nbxmpp
|
|||
from gajim.common import app
|
||||
from gajim.common.nec import NetworkIncomingEvent
|
||||
from gajim.common.types import ConnectionT
|
||||
from gajim.common.types import ContactT
|
||||
from gajim.common.types import ContactsT
|
||||
|
||||
log = logging.getLogger('gajim.c.m.ping')
|
||||
|
||||
|
@ -73,7 +73,7 @@ class Ping:
|
|||
log.warning('No reply received for keepalive ping. Reconnecting...')
|
||||
self._con.disconnectedReconnCB()
|
||||
|
||||
def send_ping(self, contact: ContactT) -> None:
|
||||
def send_ping(self, contact: ContactsT) -> None:
|
||||
if not app.account_is_connected(self._account):
|
||||
return
|
||||
|
||||
|
@ -93,7 +93,7 @@ class Ping:
|
|||
_con: ConnectionT,
|
||||
stanza: nbxmpp.Iq,
|
||||
ping_time: int,
|
||||
contact: ContactT) -> None:
|
||||
contact: ContactsT) -> None:
|
||||
if not nbxmpp.isResultNode(stanza):
|
||||
log.info('Error: %s', stanza.getError())
|
||||
app.nec.push_incoming_event(
|
||||
|
|
|
@ -44,7 +44,8 @@ InterfaceT = Union['Interface']
|
|||
LoggerT = Union['Logger']
|
||||
|
||||
ConnectionT = Union['Connection', 'ConnectionZeroconf']
|
||||
ContactT = Union['Contact', 'GC_Contact']
|
||||
ContactsT = Union['Contact', 'GC_Contact']
|
||||
ContactT = Union['Contact']
|
||||
|
||||
UserTuneDataT = Optional[Tuple[str, str, str, str, str]]
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
import logging
|
||||
|
||||
import nbxmpp
|
||||
|
||||
|
@ -30,14 +31,13 @@ from gajim.common.zeroconf.zeroconf import Constant
|
|||
from gajim.common import connection_handlers
|
||||
from gajim.common.nec import NetworkIncomingEvent, NetworkEvent
|
||||
from gajim.common.modules.user_nickname import parse_nickname
|
||||
from gajim.common.modules.chatstates import parse_chatstate
|
||||
from gajim.common.modules.misc import parse_eme
|
||||
from gajim.common.modules.misc import parse_correction
|
||||
from gajim.common.modules.misc import parse_attention
|
||||
from gajim.common.modules.misc import parse_oob
|
||||
from gajim.common.modules.misc import parse_xhtml
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf')
|
||||
|
||||
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
|
||||
|
@ -147,6 +147,7 @@ connection_handlers.ConnectionJingle):
|
|||
def _on_message_decrypted(self, event):
|
||||
try:
|
||||
self.get_module('Receipts').delegate(event)
|
||||
self.get_module('Chatstate').delegate(event)
|
||||
except nbxmpp.NodeProcessed:
|
||||
return
|
||||
|
||||
|
@ -160,7 +161,6 @@ connection_handlers.ConnectionJingle):
|
|||
'correct_id': parse_correction(event.stanza),
|
||||
'user_nick': parse_nickname(event.stanza),
|
||||
'xhtml': parse_xhtml(event.stanza),
|
||||
'chatstate': parse_chatstate(event.stanza),
|
||||
'stanza_id': event.unique_id
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ from gajim.common import ged
|
|||
from gajim.common import i18n
|
||||
from gajim.common import contacts
|
||||
from gajim.common.const import StyleAttr
|
||||
from gajim.common.const import Chatstate
|
||||
from gajim.chat_control import ChatControl
|
||||
from gajim.chat_control_base import ChatControlBase
|
||||
|
||||
|
@ -794,7 +795,6 @@ class GroupchatControl(ChatControlBase):
|
|||
self.add_actions()
|
||||
self.update_actions()
|
||||
self.set_lock_image()
|
||||
self._schedule_activity_timers()
|
||||
self._connect_window_state_change(self.parent_win)
|
||||
|
||||
def set_tooltip(self):
|
||||
|
@ -2187,13 +2187,9 @@ class GroupchatControl(ChatControlBase):
|
|||
correct_id = self.last_sent_msg
|
||||
else:
|
||||
correct_id = None
|
||||
|
||||
# Set chatstate
|
||||
chatstate = None
|
||||
if app.config.get('outgoing_chat_state_notifications') != 'disabled':
|
||||
chatstate = 'active'
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
self.contact.our_chatstate = chatstate
|
||||
con = app.connections[self.account]
|
||||
chatstate = con.get_module('Chatstate').get_active_chatstate(
|
||||
self.contact.jid)
|
||||
|
||||
# Send the message
|
||||
app.nec.push_outgoing_event(GcMessageOutgoingEvent(
|
||||
|
@ -2228,69 +2224,16 @@ class GroupchatControl(ChatControlBase):
|
|||
control = win.notebook.get_nth_page(ctrl_page)
|
||||
|
||||
win.notebook.remove_page(ctrl_page)
|
||||
if self.possible_paused_timeout_id:
|
||||
GLib.source_remove(self.possible_paused_timeout_id)
|
||||
self.possible_paused_timeout_id = None
|
||||
if self.possible_inactive_timeout_id:
|
||||
GLib.source_remove(self.possible_inactive_timeout_id)
|
||||
self.possible_inactive_timeout_id = None
|
||||
control.unparent()
|
||||
ctrl.parent_win = None
|
||||
self.send_chatstate('inactive', self.contact)
|
||||
con = app.connections[self.account]
|
||||
con.get_module('Chatstate').set_chatstate(self.contact, Chatstate.INACTIVE)
|
||||
|
||||
app.interface.roster.minimize_groupchat(
|
||||
self.account, self.contact.jid, status=self.subject)
|
||||
|
||||
del win._controls[self.account][self.contact.jid]
|
||||
|
||||
def send_chatstate(self, state, contact):
|
||||
"""
|
||||
Send OUR chatstate as STANDLONE chat state message (eg. no body)
|
||||
to contact only if new chatstate is different from the previous one
|
||||
if jid is not specified, send to active tab
|
||||
"""
|
||||
# JEP 85 does not allow resending the same chatstate
|
||||
# this function checks for that and just returns so it's safe to call it
|
||||
# with same state.
|
||||
|
||||
# This functions also checks for violation in state transitions
|
||||
# and raises RuntimeException with appropriate message
|
||||
# more on that http://xmpp.org/extensions/xep-0085.html#statechart
|
||||
|
||||
# do not send if we have chat state notifications disabled
|
||||
# that means we won't reply to the <active/> from other peer
|
||||
# so we do not broadcast jep85 capabalities
|
||||
chatstate_setting = app.config.get('outgoing_chat_state_notifications')
|
||||
if chatstate_setting == 'disabled':
|
||||
return
|
||||
|
||||
if (chatstate_setting == 'composing_only' and
|
||||
state != 'active' and
|
||||
state != 'composing'):
|
||||
return
|
||||
|
||||
# if the new state we wanna send (state) equals
|
||||
# the current state (contact.our_chatstate) then return
|
||||
if contact.our_chatstate == state:
|
||||
return
|
||||
|
||||
# if we're inactive prevent composing (XEP violation)
|
||||
if contact.our_chatstate == 'inactive' and state == 'composing':
|
||||
# go active before
|
||||
app.nec.push_outgoing_event(GcMessageOutgoingEvent(None,
|
||||
account=self.account, jid=self.contact.jid, chatstate='active',
|
||||
control=self))
|
||||
contact.our_chatstate = 'active'
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
|
||||
app.nec.push_outgoing_event(GcMessageOutgoingEvent(None,
|
||||
account=self.account, jid=self.contact.jid, chatstate=state,
|
||||
control=self))
|
||||
|
||||
contact.our_chatstate = state
|
||||
if state == 'active':
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
|
||||
def shutdown(self, status='offline'):
|
||||
# PluginSystem: calling shutdown of super class (ChatControlBase)
|
||||
# to let it remove it's GUI extension points
|
||||
|
|
|
@ -444,7 +444,6 @@ class Interface:
|
|||
account=account, name=nick, show=show)
|
||||
ctrl = self.new_private_chat(gc_c, account, session)
|
||||
|
||||
ctrl.contact.our_chatstate = False
|
||||
ctrl.print_conversation(_('Error %(code)s: %(msg)s') % {
|
||||
'code': obj.error_code, 'msg': obj.error_msg}, 'status')
|
||||
return
|
||||
|
|
|
@ -21,14 +21,12 @@ import string
|
|||
import random
|
||||
import itertools
|
||||
|
||||
from gajim import message_control
|
||||
from gajim import notify
|
||||
from gajim.common import helpers
|
||||
from gajim.common import events
|
||||
from gajim.common import app
|
||||
from gajim.common import contacts
|
||||
from gajim.common import ged
|
||||
from gajim.common.connection_handlers_events import ChatstateReceivedEvent
|
||||
from gajim.common.const import KindConstant
|
||||
from gajim.gtk.single_message import SingleMessageWindow
|
||||
|
||||
|
@ -97,7 +95,7 @@ class ChatControlSession:
|
|||
self.control.change_resource(self.resource)
|
||||
|
||||
if obj.mtype == 'chat':
|
||||
if not obj.msgtxt and obj.chatstate is None:
|
||||
if not obj.msgtxt:
|
||||
return
|
||||
|
||||
log_type = KindConstant.CHAT_MSG_RECV
|
||||
|
@ -142,27 +140,6 @@ class ChatControlSession:
|
|||
# joined. We log it silently without notification.
|
||||
return True
|
||||
|
||||
# Handle chat states
|
||||
if contact and (not obj.forwarded or not obj.sent):
|
||||
if self.control and self.control.type_id == \
|
||||
message_control.TYPE_CHAT:
|
||||
if obj.chatstate is not None:
|
||||
# other peer sent us reply, so he supports jep85 or jep22
|
||||
contact.chatstate = obj.chatstate
|
||||
if contact.our_chatstate == 'ask': # we were jep85 disco?
|
||||
contact.our_chatstate = 'active' # no more
|
||||
app.nec.push_incoming_event(ChatstateReceivedEvent(None,
|
||||
conn=obj.conn, msg_obj=obj))
|
||||
elif contact.chatstate != 'active':
|
||||
# got no valid jep85 answer, peer does not support it
|
||||
contact.chatstate = False
|
||||
elif obj.chatstate == 'active':
|
||||
# Brand new message, incoming.
|
||||
contact.our_chatstate = obj.chatstate
|
||||
contact.chatstate = obj.chatstate
|
||||
|
||||
# THIS MUST BE AFTER chatstates handling
|
||||
# AND BEFORE playsound (else we hear sounding on chatstates!)
|
||||
if not obj.msgtxt: # empty message text
|
||||
return True
|
||||
|
||||
|
@ -189,7 +166,7 @@ class ChatControlSession:
|
|||
if app.interface.remote_ctrl:
|
||||
app.interface.remote_ctrl.raise_signal('NewMessage', (
|
||||
self.conn.name, [obj.fjid, obj.msgtxt, obj.timestamp,
|
||||
obj.encrypted, obj.mtype, obj.subject, obj.chatstate,
|
||||
obj.encrypted, obj.mtype, obj.subject,
|
||||
obj.msg_log_id, obj.user_nick, obj.xhtml, obj.form_node]))
|
||||
|
||||
def roster_message2(self, obj):
|
||||
|
|
|
@ -14,9 +14,9 @@ from gajim.common import caps_cache
|
|||
class TestCommonContact(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.contact = CommonContact(jid='', account="", resource='', show='',
|
||||
status='', name='', our_chatstate=None, chatstate=None,
|
||||
client_caps=None)
|
||||
self.contact = CommonContact(
|
||||
jid='', account="", resource='', show='',
|
||||
status='', name='', chatstate=None, client_caps=None)
|
||||
|
||||
def test_default_client_supports(self):
|
||||
'''
|
||||
|
@ -43,8 +43,8 @@ class TestContact(TestCommonContact):
|
|||
'''This test supports the migration from the old to the new contact
|
||||
domain model by smoke testing that no attribute values are lost'''
|
||||
|
||||
attributes = ["jid", "resource", "show", "status", "name", "our_chatstate",
|
||||
"chatstate", "client_caps", "priority", "sub"]
|
||||
attributes = ["jid", "resource", "show", "status", "name",
|
||||
"chatstate", "client_caps", "priority", "sub"]
|
||||
for attr in attributes:
|
||||
self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr)
|
||||
|
||||
|
@ -59,8 +59,8 @@ class TestGC_Contact(TestCommonContact):
|
|||
'''This test supports the migration from the old to the new contact
|
||||
domain model by asserting no attributes have been lost'''
|
||||
|
||||
attributes = ["jid", "resource", "show", "status", "name", "our_chatstate",
|
||||
"chatstate", "client_caps", "role", "room_jid"]
|
||||
attributes = ["jid", "resource", "show", "status", "name",
|
||||
"chatstate", "client_caps", "role", "room_jid"]
|
||||
for attr in attributes:
|
||||
self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr)
|
||||
|
||||
|
|
Loading…
Reference in New Issue