Philipp Hörist pushed to branch master at gajim / gajim
Commits:
-
4aca2eea
by Philipp Hörist at 2019-01-04T14:21:55Z
4 changed files:
- gajim/chat_control_base.py
- gajim/common/connection.py
- gajim/common/modules/chatstates.py
- gajim/groupchat_control.py
Changes:
| ... | ... | @@ -823,8 +823,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): |
| 823 | 823 |
con = app.connections[self.account]
|
| 824 | 824 |
con.get_module('Chatstate').set_keyboard_activity(self.contact)
|
| 825 | 825 |
if not textview.has_text():
|
| 826 |
- con.get_module('Chatstate').set_chatstate(self.contact,
|
|
| 827 |
- Chatstate.ACTIVE)
|
|
| 826 |
+ con.get_module('Chatstate').set_chatstate_delayed(self.contact,
|
|
| 827 |
+ Chatstate.ACTIVE)
|
|
| 828 | 828 |
return
|
| 829 | 829 |
con.get_module('Chatstate').set_chatstate(self.contact,
|
| 830 | 830 |
Chatstate.COMPOSING)
|
| ... | ... | @@ -622,6 +622,7 @@ class Connection(CommonConnection, ConnectionHandlers): |
| 622 | 622 |
self.get_module('Ping').remove_timeout()
|
| 623 | 623 |
if self.connection is None:
|
| 624 | 624 |
if not reconnect:
|
| 625 |
+ self.get_module('Chatstate').enabled = False
|
|
| 625 | 626 |
self._sm_resume_data = {}
|
| 626 | 627 |
self._disconnect()
|
| 627 | 628 |
app.nec.push_incoming_event(OurShowEvent(
|
| ... | ... | @@ -668,6 +669,7 @@ class Connection(CommonConnection, ConnectionHandlers): |
| 668 | 669 |
self._set_reconnect_timer()
|
| 669 | 670 |
|
| 670 | 671 |
else:
|
| 672 |
+ self.get_module('Chatstate').enabled = False
|
|
| 671 | 673 |
self._sm_resume_data = {}
|
| 672 | 674 |
self._disconnect()
|
| 673 | 675 |
app.nec.push_incoming_event(OurShowEvent(
|
| ... | ... | @@ -1388,6 +1390,7 @@ class Connection(CommonConnection, ConnectionHandlers): |
| 1388 | 1390 |
# state of all contacts
|
| 1389 | 1391 |
app.nec.push_incoming_event(OurShowEvent(
|
| 1390 | 1392 |
None, conn=self, show='offline'))
|
| 1393 |
+ self.get_module('Chatstate').enabled = False
|
|
| 1391 | 1394 |
|
| 1392 | 1395 |
def _on_resume_successful(self):
|
| 1393 | 1396 |
# Connection was successful, reset sm resume data
|
| ... | ... | @@ -1422,6 +1425,7 @@ class Connection(CommonConnection, ConnectionHandlers): |
| 1422 | 1425 |
self.retrycount = 0
|
| 1423 | 1426 |
self._discover_server()
|
| 1424 | 1427 |
self._set_send_timeouts()
|
| 1428 |
+ self.get_module('Chatstate').enabled = True
|
|
| 1425 | 1429 |
|
| 1426 | 1430 |
def _set_send_timeouts(self):
|
| 1427 | 1431 |
if app.config.get_per('accounts', self.name, 'keep_alives_enabled'):
|
| ... | ... | @@ -16,11 +16,13 @@ |
| 16 | 16 |
|
| 17 | 17 |
from typing import Any
|
| 18 | 18 |
from typing import Dict # pylint: disable=unused-import
|
| 19 |
+from typing import List # pylint: disable=unused-import
|
|
| 19 | 20 |
from typing import Optional
|
| 20 | 21 |
from typing import Tuple
|
| 21 | 22 |
|
| 22 | 23 |
import time
|
| 23 | 24 |
import logging
|
| 25 |
+from functools import wraps
|
|
| 24 | 26 |
|
| 25 | 27 |
import nbxmpp
|
| 26 | 28 |
from gi.repository import GLib
|
| ... | ... | @@ -41,6 +43,15 @@ INACTIVE_AFTER = 60 |
| 41 | 43 |
PAUSED_AFTER = 5
|
| 42 | 44 |
|
| 43 | 45 |
|
| 46 |
+def ensure_enabled(func):
|
|
| 47 |
+ @wraps(func)
|
|
| 48 |
+ def func_wrapper(self, *args, **kwargs):
|
|
| 49 |
+ if not self.enabled:
|
|
| 50 |
+ return
|
|
| 51 |
+ return func(self, *args, **kwargs)
|
|
| 52 |
+ return func_wrapper
|
|
| 53 |
+ |
|
| 54 |
+ |
|
| 44 | 55 |
def parse_chatstate(stanza: nbxmpp.Message) -> Optional[str]:
|
| 45 | 56 |
if parse_delay(stanza) is not None:
|
| 46 | 57 |
return None
|
| ... | ... | @@ -60,13 +71,39 @@ class Chatstate: |
| 60 | 71 |
self.handlers = [
|
| 61 | 72 |
('presence', self._presence_received),
|
| 62 | 73 |
]
|
| 74 |
+ |
|
| 75 |
+ # Our current chatstate with a specific contact
|
|
| 63 | 76 |
self._chatstates = {} # type: Dict[str, State]
|
| 77 |
+ |
|
| 64 | 78 |
self._last_keyboard_activity = {} # type: Dict[str, float]
|
| 65 | 79 |
self._last_mouse_activity = {} # type: Dict[str, float]
|
| 80 |
+ self._timeout_id = None
|
|
| 81 |
+ self._delay_timeout_ids = {} # type: Dict[str, str]
|
|
| 82 |
+ self._blocked = [] # type: List[str]
|
|
| 83 |
+ self._enabled = False
|
|
| 84 |
+ |
|
| 85 |
+ @property
|
|
| 86 |
+ def enabled(self):
|
|
| 87 |
+ return self._enabled
|
|
| 88 |
+ |
|
| 89 |
+ @enabled.setter
|
|
| 90 |
+ def enabled(self, value):
|
|
| 91 |
+ if self._enabled == value:
|
|
| 92 |
+ return
|
|
| 93 |
+ log.info('Chatstate module %s', 'enabled' if value else 'disabled')
|
|
| 94 |
+ self._enabled = value
|
|
| 66 | 95 |
|
| 67 |
- self._timeout_id = GLib.timeout_add_seconds(
|
|
| 68 |
- 2, self._check_last_interaction)
|
|
| 96 |
+ if value:
|
|
| 97 |
+ self._timeout_id = GLib.timeout_add_seconds(
|
|
| 98 |
+ 2, self._check_last_interaction)
|
|
| 99 |
+ else:
|
|
| 100 |
+ self.cleanup()
|
|
| 101 |
+ self._chatstates = {}
|
|
| 102 |
+ self._last_keyboard_activity = {}
|
|
| 103 |
+ self._last_mouse_activity = {}
|
|
| 104 |
+ self._blocked = []
|
|
| 69 | 105 |
|
| 106 |
+ @ensure_enabled
|
|
| 70 | 107 |
def _presence_received(self,
|
| 71 | 108 |
_con: ConnectionT,
|
| 72 | 109 |
stanza: nbxmpp.Presence) -> None:
|
| ... | ... | @@ -135,6 +172,7 @@ class Chatstate: |
| 135 | 172 |
account=self._account,
|
| 136 | 173 |
contact=contact))
|
| 137 | 174 |
|
| 175 |
+ @ensure_enabled
|
|
| 138 | 176 |
def _check_last_interaction(self) -> GLib.SOURCE_CONTINUE:
|
| 139 | 177 |
setting = app.config.get('outgoing_chat_state_notifications')
|
| 140 | 178 |
if setting in ('composing_only', 'disabled'):
|
| ... | ... | @@ -183,6 +221,7 @@ class Chatstate: |
| 183 | 221 |
|
| 184 | 222 |
return GLib.SOURCE_CONTINUE
|
| 185 | 223 |
|
| 224 |
+ @ensure_enabled
|
|
| 186 | 225 |
def set_active(self, jid: str) -> None:
|
| 187 | 226 |
self._last_mouse_activity[jid] = time.time()
|
| 188 | 227 |
setting = app.config.get('outgoing_chat_state_notifications')
|
| ... | ... | @@ -207,7 +246,36 @@ class Chatstate: |
| 207 | 246 |
self.set_active(contact.jid)
|
| 208 | 247 |
return 'active'
|
| 209 | 248 |
|
| 249 |
+ @ensure_enabled
|
|
| 250 |
+ def block_chatstates(self, contact: ContactT, block: bool) -> None:
|
|
| 251 |
+ # Block sending chatstates to a contact
|
|
| 252 |
+ # Used for example if we cycle through the MUC nick list, which
|
|
| 253 |
+ # produces a lot of text-changed signals from the textview. This
|
|
| 254 |
+ # Would lead to sending ACTIVE -> COMPOSING -> ACTIVE ...
|
|
| 255 |
+ if block:
|
|
| 256 |
+ self._blocked.append(contact.jid)
|
|
| 257 |
+ else:
|
|
| 258 |
+ self._blocked.remove(contact.jid)
|
|
| 259 |
+ |
|
| 260 |
+ @ensure_enabled
|
|
| 261 |
+ def set_chatstate_delayed(self, contact: ContactT, state: State) -> None:
|
|
| 262 |
+ # Used when we go from Composing -> Active after deleting all text
|
|
| 263 |
+ # from the Textview. We delay the Active state because maybe the
|
|
| 264 |
+ # User starts writing again.
|
|
| 265 |
+ self.remove_delay_timeout(contact)
|
|
| 266 |
+ self._delay_timeout_ids[contact.jid] = GLib.timeout_add_seconds(
|
|
| 267 |
+ 2, self.set_chatstate, contact, state)
|
|
| 268 |
+ |
|
| 269 |
+ @ensure_enabled
|
|
| 210 | 270 |
def set_chatstate(self, contact: ContactT, state: State) -> None:
|
| 271 |
+ # Dont send chatstates to ourself
|
|
| 272 |
+ if self._con.get_own_jid().bareMatch(contact.jid):
|
|
| 273 |
+ return
|
|
| 274 |
+ |
|
| 275 |
+ if contact.jid in self._blocked:
|
|
| 276 |
+ return
|
|
| 277 |
+ |
|
| 278 |
+ self.remove_delay_timeout(contact)
|
|
| 211 | 279 |
current_state = self._chatstates.get(contact.jid)
|
| 212 | 280 |
setting = app.config.get('outgoing_chat_state_notifications')
|
| 213 | 281 |
if setting == 'disabled':
|
| ... | ... | @@ -255,10 +323,6 @@ class Chatstate: |
| 255 | 323 |
if current_state == state:
|
| 256 | 324 |
return
|
| 257 | 325 |
|
| 258 |
- # Dont send chatstates to ourself
|
|
| 259 |
- if self._con.get_own_jid().bareMatch(contact.jid):
|
|
| 260 |
- return
|
|
| 261 |
- |
|
| 262 | 326 |
log.info('Send: %-10s - %s', state, contact.jid)
|
| 263 | 327 |
|
| 264 | 328 |
event_attrs = {'account': self._account,
|
| ... | ... | @@ -275,6 +339,7 @@ class Chatstate: |
| 275 | 339 |
|
| 276 | 340 |
self._chatstates[contact.jid] = state
|
| 277 | 341 |
|
| 342 |
+ @ensure_enabled
|
|
| 278 | 343 |
def set_mouse_activity(self, contact: ContactT) -> None:
|
| 279 | 344 |
self._last_mouse_activity[contact.jid] = time.time()
|
| 280 | 345 |
setting = app.config.get('outgoing_chat_state_notifications')
|
| ... | ... | @@ -283,11 +348,25 @@ class Chatstate: |
| 283 | 348 |
if self._chatstates.get(contact.jid) == State.INACTIVE:
|
| 284 | 349 |
self.set_chatstate(contact, State.ACTIVE)
|
| 285 | 350 |
|
| 351 |
+ @ensure_enabled
|
|
| 286 | 352 |
def set_keyboard_activity(self, contact: ContactT) -> None:
|
| 287 | 353 |
self._last_keyboard_activity[contact.jid] = time.time()
|
| 288 | 354 |
|
| 355 |
+ def remove_delay_timeout(self, contact):
|
|
| 356 |
+ timeout = self._delay_timeout_ids.get(contact.jid)
|
|
| 357 |
+ if timeout is not None:
|
|
| 358 |
+ GLib.source_remove(timeout)
|
|
| 359 |
+ del self._delay_timeout_ids[contact.jid]
|
|
| 360 |
+ |
|
| 361 |
+ def remove_all_delay_timeouts(self):
|
|
| 362 |
+ for timeout in self._delay_timeout_ids.values():
|
|
| 363 |
+ GLib.source_remove(timeout)
|
|
| 364 |
+ self._delay_timeout_ids = {}
|
|
| 365 |
+ |
|
| 289 | 366 |
def cleanup(self):
|
| 290 |
- GLib.source_remove(self._timeout_id)
|
|
| 367 |
+ self.remove_all_delay_timeouts()
|
|
| 368 |
+ if self._timeout_id is not None:
|
|
| 369 |
+ GLib.source_remove(self._timeout_id)
|
|
| 291 | 370 |
|
| 292 | 371 |
|
| 293 | 372 |
def get_instance(*args: Any, **kwargs: Any) -> Tuple[Chatstate, str]:
|
| ... | ... | @@ -1620,6 +1620,9 @@ class GroupchatControl(ChatControlBase): |
| 1620 | 1620 |
self.is_connected = False
|
| 1621 | 1621 |
ChatControlBase.got_disconnected(self)
|
| 1622 | 1622 |
|
| 1623 |
+ con = app.connections[self.account]
|
|
| 1624 |
+ con.get_module('Chatstate').remove_delay_timeout(self.contact)
|
|
| 1625 |
+ |
|
| 1623 | 1626 |
contact = app.contacts.get_groupchat_contact(self.account,
|
| 1624 | 1627 |
self.room_jid)
|
| 1625 | 1628 |
if contact is not None:
|
| ... | ... | @@ -2550,6 +2553,9 @@ class GroupchatControl(ChatControlBase): |
| 2550 | 2553 |
else:
|
| 2551 | 2554 |
start_iter.backward_chars(len(begin))
|
| 2552 | 2555 |
|
| 2556 |
+ con = app.connections[self.account]
|
|
| 2557 |
+ con.get_module('Chatstate').block_chatstates(self.contact, True)
|
|
| 2558 |
+ |
|
| 2553 | 2559 |
message_buffer.delete(start_iter, end_iter)
|
| 2554 | 2560 |
# get a shell-like completion
|
| 2555 | 2561 |
# if there's more than one nick for this completion, complete
|
| ... | ... | @@ -2579,6 +2585,9 @@ class GroupchatControl(ChatControlBase): |
| 2579 | 2585 |
else:
|
| 2580 | 2586 |
completion = self.nick_hits[0]
|
| 2581 | 2587 |
message_buffer.insert_at_cursor(completion + add)
|
| 2588 |
+ |
|
| 2589 |
+ con.get_module('Chatstate').block_chatstates(self.contact, False)
|
|
| 2590 |
+ |
|
| 2582 | 2591 |
self.last_key_tabs = True
|
| 2583 | 2592 |
return True
|
| 2584 | 2593 |
self.last_key_tabs = False
|