Skip to content
Snippets Groups Projects
Commit 4aca2eea authored by Philipp Hörist's avatar Philipp Hörist
Browse files

Dont send chatstates when cycling MUC nicks

- Add ability to enable/disable the whole module so it doesnt try to send chatstates when we are offline
parent 4bd14bc5
No related branches found
No related tags found
No related merge requests found
......@@ -823,8 +823,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
con = app.connections[self.account]
con.get_module('Chatstate').set_keyboard_activity(self.contact)
if not textview.has_text():
con.get_module('Chatstate').set_chatstate(self.contact,
Chatstate.ACTIVE)
con.get_module('Chatstate').set_chatstate_delayed(self.contact,
Chatstate.ACTIVE)
return
con.get_module('Chatstate').set_chatstate(self.contact,
Chatstate.COMPOSING)
......
......@@ -622,6 +622,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self.get_module('Ping').remove_timeout()
if self.connection is None:
if not reconnect:
self.get_module('Chatstate').enabled = False
self._sm_resume_data = {}
self._disconnect()
app.nec.push_incoming_event(OurShowEvent(
......@@ -668,6 +669,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self._set_reconnect_timer()
else:
self.get_module('Chatstate').enabled = False
self._sm_resume_data = {}
self._disconnect()
app.nec.push_incoming_event(OurShowEvent(
......@@ -1388,6 +1390,7 @@ class Connection(CommonConnection, ConnectionHandlers):
# state of all contacts
app.nec.push_incoming_event(OurShowEvent(
None, conn=self, show='offline'))
self.get_module('Chatstate').enabled = False
def _on_resume_successful(self):
# Connection was successful, reset sm resume data
......@@ -1422,6 +1425,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self.retrycount = 0
self._discover_server()
self._set_send_timeouts()
self.get_module('Chatstate').enabled = True
def _set_send_timeouts(self):
if app.config.get_per('accounts', self.name, 'keep_alives_enabled'):
......
......@@ -16,11 +16,13 @@
from typing import Any
from typing import Dict # pylint: disable=unused-import
from typing import List # pylint: disable=unused-import
from typing import Optional
from typing import Tuple
import time
import logging
from functools import wraps
import nbxmpp
from gi.repository import GLib
......@@ -41,6 +43,15 @@ INACTIVE_AFTER = 60
PAUSED_AFTER = 5
def ensure_enabled(func):
@wraps(func)
def func_wrapper(self, *args, **kwargs):
if not self.enabled:
return
return func(self, *args, **kwargs)
return func_wrapper
def parse_chatstate(stanza: nbxmpp.Message) -> Optional[str]:
if parse_delay(stanza) is not None:
return None
......@@ -60,13 +71,39 @@ class Chatstate:
self.handlers = [
('presence', self._presence_received),
]
# Our current chatstate with a specific contact
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 = None
self._delay_timeout_ids = {} # type: Dict[str, str]
self._blocked = [] # type: List[str]
self._enabled = False
@property
def enabled(self):
return self._enabled
@enabled.setter
def enabled(self, value):
if self._enabled == value:
return
log.info('Chatstate module %s', 'enabled' if value else 'disabled')
self._enabled = value
self._timeout_id = GLib.timeout_add_seconds(
2, self._check_last_interaction)
if value:
self._timeout_id = GLib.timeout_add_seconds(
2, self._check_last_interaction)
else:
self.cleanup()
self._chatstates = {}
self._last_keyboard_activity = {}
self._last_mouse_activity = {}
self._blocked = []
@ensure_enabled
def _presence_received(self,
_con: ConnectionT,
stanza: nbxmpp.Presence) -> None:
......@@ -135,6 +172,7 @@ class Chatstate:
account=self._account,
contact=contact))
@ensure_enabled
def _check_last_interaction(self) -> GLib.SOURCE_CONTINUE:
setting = app.config.get('outgoing_chat_state_notifications')
if setting in ('composing_only', 'disabled'):
......@@ -183,6 +221,7 @@ class Chatstate:
return GLib.SOURCE_CONTINUE
@ensure_enabled
def set_active(self, jid: str) -> None:
self._last_mouse_activity[jid] = time.time()
setting = app.config.get('outgoing_chat_state_notifications')
......@@ -207,7 +246,36 @@ class Chatstate:
self.set_active(contact.jid)
return 'active'
@ensure_enabled
def block_chatstates(self, contact: ContactT, block: bool) -> None:
# Block sending chatstates to a contact
# Used for example if we cycle through the MUC nick list, which
# produces a lot of text-changed signals from the textview. This
# Would lead to sending ACTIVE -> COMPOSING -> ACTIVE ...
if block:
self._blocked.append(contact.jid)
else:
self._blocked.remove(contact.jid)
@ensure_enabled
def set_chatstate_delayed(self, contact: ContactT, state: State) -> None:
# Used when we go from Composing -> Active after deleting all text
# from the Textview. We delay the Active state because maybe the
# User starts writing again.
self.remove_delay_timeout(contact)
self._delay_timeout_ids[contact.jid] = GLib.timeout_add_seconds(
2, self.set_chatstate, contact, state)
@ensure_enabled
def set_chatstate(self, contact: ContactT, state: State) -> None:
# Dont send chatstates to ourself
if self._con.get_own_jid().bareMatch(contact.jid):
return
if contact.jid in self._blocked:
return
self.remove_delay_timeout(contact)
current_state = self._chatstates.get(contact.jid)
setting = app.config.get('outgoing_chat_state_notifications')
if setting == 'disabled':
......@@ -255,10 +323,6 @@ class Chatstate:
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,
......@@ -275,6 +339,7 @@ class Chatstate:
self._chatstates[contact.jid] = state
@ensure_enabled
def set_mouse_activity(self, contact: ContactT) -> None:
self._last_mouse_activity[contact.jid] = time.time()
setting = app.config.get('outgoing_chat_state_notifications')
......@@ -283,11 +348,25 @@ class Chatstate:
if self._chatstates.get(contact.jid) == State.INACTIVE:
self.set_chatstate(contact, State.ACTIVE)
@ensure_enabled
def set_keyboard_activity(self, contact: ContactT) -> None:
self._last_keyboard_activity[contact.jid] = time.time()
def remove_delay_timeout(self, contact):
timeout = self._delay_timeout_ids.get(contact.jid)
if timeout is not None:
GLib.source_remove(timeout)
del self._delay_timeout_ids[contact.jid]
def remove_all_delay_timeouts(self):
for timeout in self._delay_timeout_ids.values():
GLib.source_remove(timeout)
self._delay_timeout_ids = {}
def cleanup(self):
GLib.source_remove(self._timeout_id)
self.remove_all_delay_timeouts()
if self._timeout_id is not None:
GLib.source_remove(self._timeout_id)
def get_instance(*args: Any, **kwargs: Any) -> Tuple[Chatstate, str]:
......
......@@ -1620,6 +1620,9 @@ class GroupchatControl(ChatControlBase):
self.is_connected = False
ChatControlBase.got_disconnected(self)
con = app.connections[self.account]
con.get_module('Chatstate').remove_delay_timeout(self.contact)
contact = app.contacts.get_groupchat_contact(self.account,
self.room_jid)
if contact is not None:
......@@ -2550,6 +2553,9 @@ class GroupchatControl(ChatControlBase):
else:
start_iter.backward_chars(len(begin))
con = app.connections[self.account]
con.get_module('Chatstate').block_chatstates(self.contact, True)
message_buffer.delete(start_iter, end_iter)
# get a shell-like completion
# if there's more than one nick for this completion, complete
......@@ -2579,6 +2585,9 @@ class GroupchatControl(ChatControlBase):
else:
completion = self.nick_hits[0]
message_buffer.insert_at_cursor(completion + add)
con.get_module('Chatstate').block_chatstates(self.contact, False)
self.last_key_tabs = True
return True
self.last_key_tabs = False
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment