Philipp Hörist pushed to branch master at gajim / gajim

Commits:

4 changed files:

Changes:

  • gajim/chat_control_base.py
    ... ... @@ -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)
    

  • gajim/common/connection.py
    ... ... @@ -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'):
    

  • gajim/common/modules/chatstates.py
    ... ... @@ -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]:
    

  • gajim/groupchat_control.py
    ... ... @@ -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