Philipp Hörist pushed to branch master at gajim / gajim
Commits: 25b8fd98 by lovetox at 2020-05-28T22:31:07+02:00 Refactor idle status
- - - - - 6d445bd1 by lovetox at 2020-05-28T22:33:31+02:00 Caps: Fix EntityCapsTask precondition check
- - - - -
13 changed files:
- gajim/application.py - gajim/common/app.py - gajim/common/client.py - gajim/common/config.py - gajim/common/const.py - gajim/common/helpers.py - gajim/common/idle.py - gajim/common/modules/caps.py - gajim/common/modules/muc.py - gajim/common/modules/vcard_avatars.py - gajim/gui_interface.py - gajim/roster_window.py - test/lib/gajim_mocks.py
Changes:
===================================== gajim/application.py ===================================== @@ -173,8 +173,6 @@ class GajimApplication(Gtk.Application): self.connect('command-line', self._handle_remote_options) self.connect('startup', self._startup) self.connect('activate', self._activate) - if sys.platform not in ('win32', 'darwin'): - self.connect("notify::screensaver-active", self._screensaver_active)
self.interface = None
@@ -604,46 +602,3 @@ class GajimApplication(Gtk.Application): elif event.feature == Namespace.BLOCKING: action = '%s-blocking' % event.account self.lookup_action(action).set_enabled(True) - - def _screensaver_active(self, _application, _param): - '''Signal handler for screensaver active change''' - - active = self.get_property('screensaver-active') - - roster = app.interface.roster - - if not active: - for account in app.connections: - if app.account_is_available(account) and \ - app.sleeper_state[account] == 'autoaway-forced': - # We came back online after screensaver autoaway - roster.send_status(account, 'online', - app.status_before_autoaway[account]) - app.status_before_autoaway[account] = '' - app.sleeper_state[account] = 'online' - return - if not app.config.get('autoaway'): - # Don't go auto away if user disabled the option - return - for account in app.connections: - if (account not in app.sleeper_state or - not app.sleeper_state[account]): - continue - if app.sleeper_state[account] == 'online': - if not app.account_is_available(account): - continue - # we save our online status - app.status_before_autoaway[account] = \ - app.connections[account].status - # we go away (no auto status) [we pass True to auto param] - auto_message = app.config.get('autoaway_message') - if not auto_message: - auto_message = app.connections[account].status - else: - auto_message = auto_message.replace('$S', '%(status)s') - auto_message = auto_message.replace('$T', '%(time)s') - auto_message = auto_message % { - 'status': app.status_before_autoaway[account], - 'time': app.config.get('autoxatime')} - roster.send_status(account, 'away', auto_message, auto=True) - app.sleeper_state[account] = 'autoaway-forced'
===================================== gajim/common/app.py ===================================== @@ -118,15 +118,6 @@ block_signed_in_notifications = {} # type: Dict[str, bool] # type of each connection (ssl, tls, tcp, ...) con_types = {} # type: Dict[str, Optional[str]]
-# whether we pass auto away / xa or not -#'off': don't use sleeper for this account -#'online': online and use sleeper -#'autoaway': autoaway and use sleeper -#'autoxa': autoxa and use sleeper -sleeper_state = {} # type: Dict[str, str] - -status_before_autoaway = {} # type: Dict[str, str] - proxy65_manager = None
cert_store = None
===================================== gajim/common/client.py ===================================== @@ -12,6 +12,7 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see http://www.gnu.org/licenses/.
+import sys import logging
import nbxmpp @@ -34,6 +35,8 @@ from gajim.common.helpers import get_user_proxy from gajim.common.helpers import warn_about_plain_connection from gajim.common.helpers import get_resource from gajim.common.helpers import get_ignored_ssl_errors +from gajim.common.helpers import get_idle_status_message +from gajim.common.idle import Monitor from gajim.common.i18n import _
from gajim.common.connection_handlers import ConnectionHandlers @@ -68,6 +71,9 @@ class Client(ConnectionHandlers): self._state = ClientState.DISCONNECTED self._status = 'online' self._status_message = '' + self._idle_status = 'online' + self._idle_status_enabled = True + self._idle_status_message = ''
self._reconnect = True self._reconnect_timer_source = None @@ -84,6 +90,13 @@ class Client(ConnectionHandlers):
self._create_client()
+ self._idle_handler_id = Monitor.connect('state-changed', + self._idle_state_changed) + + if sys.platform not in ('win32', 'darwin'): + self._screensaver_handler_id = app.app.connect( + 'notify::screensaver-active', self._screensaver_state_changed) + ConnectionHandlers.__init__(self)
def _set_state(self, state): @@ -100,6 +113,8 @@ class Client(ConnectionHandlers):
@property def status_message(self): + if self._idle_status_active(): + return self._idle_status_message return self._status_message
@property @@ -328,13 +343,15 @@ class Client(ConnectionHandlers): # This returns the bare jid return nbxmpp.JID(app.get_jid_from_account(self._account))
- def change_status(self, show, msg, auto=False): - if not msg: - msg = '' + def change_status(self, show, message): + if not message: + message = '' + + self._idle_status_enabled = show == 'online' + self._status_message = message
if show != 'offline': self._status = show - self._status_message = msg
if self._state.is_disconnecting: log.warning('Can't change status while ' @@ -367,7 +384,7 @@ class Client(ConnectionHandlers): if show == 'offline': presence = self.get_module('Presence').get_presence( typ='unavailable', - status=msg, + status=message, caps=False)
self.send_stanza(presence) @@ -376,18 +393,21 @@ class Client(ConnectionHandlers): destroy_client=True) return
- self._priority = app.get_priority(self._account, show) + self.update_presence()
+ def update_presence(self): + status, message, idle = self.get_presence_state() + self._priority = app.get_priority(self._account, status) self.get_module('Presence').send_presence( priority=self._priority, - show=show, - status=msg, - idle_time=auto) + show=status, + status=message, + idle_time=idle)
- self.get_module('MUC').update_presence(auto=auto) + self.get_module('MUC').update_presence()
app.nec.push_incoming_event( - OurShowEvent(None, conn=self, show=show)) + OurShowEvent(None, conn=self, show=status))
def get_module(self, name): return modules.get(self._account, name) @@ -407,10 +427,13 @@ class Client(ConnectionHandlers): def _send_first_presence(self): self._priority = app.get_priority(self._account, self._status)
+ self._status, message, idle = self.get_presence_state() + self.get_module('Presence').send_presence( priority=self._priority, show=self._status, - status=self._status_message) + status=message, + idle_time=idle)
self._set_client_available()
@@ -507,12 +530,6 @@ class Client(ConnectionHandlers):
def _schedule_reconnect(self): self._set_state(ClientState.RECONNECT_SCHEDULED) - if app.status_before_autoaway[self._account]: - # We were auto away. So go back online - self._status_message = app.status_before_autoaway[self._account] - app.status_before_autoaway[self._account] = '' - self._status = 'online' - log.info("Reconnect to %s in 3s", self._account) self._reconnect_timer_source = GLib.timeout_add_seconds( 3, self.connect) @@ -534,8 +551,53 @@ class Client(ConnectionHandlers): GLib.source_remove(self._reconnect_timer_source) self._reconnect_timer_source = None
+ def _idle_state_changed(self, monitor): + state = monitor.state.value + + if monitor.is_awake(): + self._idle_status = state + self._idle_status_message = '' + if self._state.is_available and self._idle_status_enabled: + self._status = state + self.update_presence() + return + + if not app.config.get(f'auto{state}'): + return + + if (state in ('away', 'xa') and self._status == 'online' or + state == 'xa' and self._idle_status == 'away'): + + self._idle_status = state + self._idle_status_message = get_idle_status_message( + state, self._status_message) + if self._state.is_available and self._idle_status_enabled: + self._status = state + self.update_presence() + + def _idle_status_active(self): + if not Monitor.is_available(): + return False + + if not self._idle_status_enabled: + return False + + return self._idle_status != 'online' + + def get_presence_state(self): + if self._idle_status_active(): + return self._idle_status, self._idle_status_message, True + return self._status, self._status_message, False + + @staticmethod + def _screensaver_state_changed(application, _param): + active = application.get_property('screensaver-active') + Monitor.set_extended_away(active) + def cleanup(self): self._destroyed = True + Monitor.disconnect(self._idle_handler_id) + app.app.disconnect(self._screensaver_handler_id) if self._client is not None: # cleanup() is called before nbmxpp.Client has disconnected, # when we disable the account. So we need to unregister
===================================== gajim/common/config.py ===================================== @@ -213,7 +213,6 @@ class Config: 'scroll_roster_to_last_message': [opt_bool, True, _('If enabled, Gajim will scroll and select the contact who sent you the last message, if the chat window is not already opened.')], 'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')], 'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')], - 'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')], 'uri_schemes': [opt_str, 'aaa:// aaas:// acap:// cap:// cid: crid:// data: dav: dict:// dns: fax: file:/ ftp:// geo: go: gopher:// h323: http:// https:// iax: icap:// im: imap:// info: ipp:// iris: iris.beep: iris.xpc: iris.xpcs: iris.lwz: ldap:// mid: modem: msrp:// msrps:// mtqp:// mupdate:// news: nfs:// nntp:// opaquelocktoken: pop:// pres: prospero:// rtsp:// service: sip: sips: sms: snmp:// soap.beep:// soap.beeps:// tag: tel: telnet:// tftp:// thismessage:/ tip:// tv: urn:// vemmi:// xmlrpc.beep:// xmlrpc.beeps:// z39.50r:// z39.50s:// about: apt: cvs:// daap:// ed2k:// feed: fish:// git:// iax2: irc:// ircs:// ldaps:// magnet: mms:// rsync:// ssh:// svn:// sftp:// smb:// webcal:// aesgcm://', _('Valid URI schemes. Only schemes in this list will be accepted as 'real' URI (mailto and xmpp are handled separately).'), True], 'shell_like_completion': [opt_bool, False, _('If enabled, completion in group chats will be like a shell auto-completion.')], 'audio_input_device': [opt_str, 'autoaudiosrc ! volume name=gajim_vol'],
===================================== gajim/common/const.py ===================================== @@ -120,11 +120,11 @@ class ButtonAction(Enum): SUGGESTED = 'suggested-action'
@unique -class IdleState(IntEnum): - UNKNOWN = 0 - XA = 1 - AWAY = 2 - AWAKE = 3 +class IdleState(Enum): + UNKNOWN = 'unknown' + XA = 'xa' + AWAY = 'away' + AWAKE = 'online'
@unique
===================================== gajim/common/helpers.py ===================================== @@ -1659,3 +1659,17 @@ def get_ignored_ssl_errors(account): ignore_ssl_errors = app.config.get_per( 'accounts', account, 'ignore_ssl_errors').split() return {Gio.TlsCertificateFlags(int(err)) for err in ignore_ssl_errors} + + +def get_idle_status_message(state, status_message): + message = app.config.get(f'auto{state}_message') + if not message: + message = status_message + else: + message = message.replace('$S', '%(status)s') + message = message.replace('$T', '%(time)s') + message = message % { + 'status': status_message, + 'time': app.config.get(f'auto{state}time') + } + return message
===================================== gajim/common/idle.py ===================================== @@ -26,6 +26,7 @@ import ctypes.util import logging from gi.repository import Gio from gi.repository import GLib +from gi.repository import GObject
from gajim.common.const import IdleState
@@ -36,6 +37,7 @@ class DBusGnomeIdleMonitor:
def __init__(self): self.last_idle_time = 0 + self._extended_away = False
log.debug('Connecting to D-Bus') self.dbus_gnome_proxy = Gio.DBusProxy.new_for_bus_sync( @@ -75,13 +77,18 @@ class DBusGnomeIdleMonitor:
return self.last_idle_time
+ def set_extended_away(self, state): + self._extended_away = state + def is_extended_away(self): - return False + return self._extended_away
class XssIdleMonitor: def __init__(self):
+ self._extended_away = False + class XScreenSaverInfo(ctypes.Structure): _fields_ = [ ('window', ctypes.c_ulong), @@ -140,6 +147,9 @@ class XssIdleMonitor: return info return int(self.xss_info_p.contents.idle / 1000)
+ def set_extended_away(self, state): + self._extended_away = state + def is_extended_away(self): return False
@@ -193,18 +203,33 @@ class WindowsIdleMonitor: return True
-class IdleMonitor: +class IdleMonitor(GObject.GObject): + + __gsignals__ = { + 'state-changed': ( + GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, + None, # return value + () # arguments + )} + def __init__(self): + GObject.GObject.__init__(self) self.set_interval() self._state = IdleState.AWAKE self._idle_monitor = self._get_idle_monitor()
+ if self.is_available(): + GLib.timeout_add_seconds(1, self._poll) + def set_interval(self, away_interval=60, xa_interval=120): log.info('Set interval: away: %s, xa: %s', away_interval, xa_interval) self._away_interval = away_interval self._xa_interval = xa_interval
+ def set_extended_away(self, state): + self._idle_monitor.set_extended_away(state) + def is_available(self): return self._idle_monitor is not None
@@ -226,7 +251,8 @@ class IdleMonitor: def is_unknown(self): return self.state == IdleState.UNKNOWN
- def _get_idle_monitor(self): + @staticmethod + def _get_idle_monitor(): if sys.platform == 'win32': return WindowsIdleMonitor()
@@ -244,28 +270,33 @@ class IdleMonitor: def get_idle_sec(self): return self._idle_monitor.get_idle_sec()
- def poll(self): + def _poll(self): """ Check to see if we should change state """ - if not self.is_available(): - return False - if self._idle_monitor.is_extended_away(): log.info('Extended Away: Screensaver or Locked Screen') - self._state = IdleState.XA + self._set_state(IdleState.XA) return True
idle_time = self.get_idle_sec()
# xa is stronger than away so check for xa first if idle_time > self._xa_interval: - self._state = IdleState.XA + self._set_state(IdleState.XA) elif idle_time > self._away_interval: - self._state = IdleState.AWAY + self._set_state(IdleState.AWAY) else: - self._state = IdleState.AWAKE + self._set_state(IdleState.AWAKE) return True
+ def _set_state(self, state): + if self._state == state: + return + + self._state = state + log.info('State changed: %s', state) + self.emit('state-changed') +
Monitor = IdleMonitor()
===================================== gajim/common/modules/caps.py ===================================== @@ -214,7 +214,6 @@ class EntityCapsTask(Task): if muc is None or not muc.state.is_joined: self.set_obsolete() return False - return True
return client.state.is_available
===================================== gajim/common/modules/muc.py ===================================== @@ -297,21 +297,24 @@ class MUC(BaseModule): account=self._account, room_jid=result.jid))
- def update_presence(self, auto=False): + def update_presence(self): mucs = self._manager.get_mucs_with_state([MUCJoinedState.JOINED, MUCJoinedState.JOINING]) + + status, message, idle = self._con.get_presence_state() for muc_data in mucs: self._con.get_module('Presence').send_presence( muc_data.occupant_jid, - show=self._con.status, - status=self._con.status_message, - idle_time=auto) + show=status, + status=message, + idle_time=idle)
def change_nick(self, room_jid, new_nick): + status, message, _idle = self._con.get_presence_state() self._con.get_module('Presence').send_presence( '%s/%s' % (room_jid, new_nick), - show=self._con.status, - status=self._con.status_message) + show=status, + status=message)
def _on_error_presence(self, _con, _stanza, properties): room_jid = properties.jid.getBare()
===================================== gajim/common/modules/vcard_avatars.py ===================================== @@ -220,11 +220,7 @@ class VCardAvatars(BaseModule): self._log.debug('Avatar already advertised') return
- self._con.get_module('Presence').send_presence( - priority=self._con.priority, - show=self._con.status, - status=self._con.status_message) - + self._con.update_presence() self.avatar_advertised = True
def add_update_node(self, node):
===================================== gajim/gui_interface.py ===================================== @@ -823,16 +823,6 @@ class Interface:
pep_supported = obj.conn.get_module('PEP').supported
- if not idle.Monitor.is_unknown() and obj.conn.status in ('online', 'chat'): - # we go online or free for chat, so we activate auto status - app.sleeper_state[account] = 'online' - elif not ((idle.Monitor.is_away() and obj.conn.status == 'away') or \ - (idle.Monitor.is_xa() and obj.conn.status == 'xa')): - # If we are autoaway/xa and come back after a disconnection, do - # nothing - # Else disable autoaway - app.sleeper_state[account] = 'off' - if obj.conn.get_module('MAM').available: obj.conn.get_module('MAM').request_archive_on_signin()
@@ -1535,77 +1525,6 @@ class Interface: ### Other Methods ################################################################################
- def read_sleepy(self): - """ - Check idle status and change that status if needed - """ - if not idle.Monitor.poll(): - # idle detection is not supported in that OS - return False # stop looping in vain - - for account in app.connections: - if account not in app.sleeper_state or \ - not app.sleeper_state[account]: - continue - if idle.Monitor.is_awake(): - if app.sleeper_state[account] in ('autoaway', 'autoxa'): - # we go online - self.roster.send_status(account, 'online', - app.status_before_autoaway[account]) - app.status_before_autoaway[account] = '' - app.sleeper_state[account] = 'online' - if app.sleeper_state[account] == 'idle': - # we go to the previous state - status = app.connections[account].status - self.roster.send_status(account, status, - app.status_before_autoaway[account]) - app.status_before_autoaway[account] = '' - app.sleeper_state[account] = 'off' - elif idle.Monitor.is_away() and app.config.get('autoaway'): - if app.sleeper_state[account] == 'online': - # we save out online status - app.status_before_autoaway[account] = \ - app.connections[account].status_message - # we go away (no auto status) [we pass True to auto param] - auto_message = app.config.get('autoaway_message') - if not auto_message: - auto_message = app.connections[account].status - else: - auto_message = auto_message.replace('$S', '%(status)s') - auto_message = auto_message.replace('$T', '%(time)s') - auto_message = auto_message % { - 'status': app.status_before_autoaway[account], - 'time': app.config.get('autoawaytime') - } - self.roster.send_status(account, 'away', auto_message, - auto=True) - app.sleeper_state[account] = 'autoaway' - elif app.sleeper_state[account] == 'off': - # we save out online status - app.status_before_autoaway[account] = \ - app.connections[account].status - status = app.connections[account].status - self.roster.send_status(account, status, - app.status_before_autoaway[account], auto=True) - app.sleeper_state[account] = 'idle' - elif idle.Monitor.is_xa() and \ - app.sleeper_state[account] in ('online', 'autoaway', - 'autoaway-forced') and app.config.get('autoxa'): - # we go extended away [we pass True to auto param] - auto_message = app.config.get('autoxa_message') - if not auto_message: - auto_message = app.connections[account].status - else: - auto_message = auto_message.replace('$S', '%(status)s') - auto_message = auto_message.replace('$T', '%(time)s') - auto_message = auto_message % { - 'status': app.status_before_autoaway[account], - 'time': app.config.get('autoxatime') - } - self.roster.send_status(account, 'xa', auto_message, auto=True) - app.sleeper_state[account] = 'autoxa' - return True # renew timeout (loop for ever) - @staticmethod def create_account(account, username, @@ -1685,9 +1604,7 @@ class Interface: app.nicks[account] = app.config.get_per( 'accounts', account, 'name') app.block_signed_in_notifications[account] = True - app.sleeper_state[account] = 'off' app.last_message_time[account] = {} - app.status_before_autoaway[account] = '' # refresh roster if len(app.connections) >= 2: # Do not merge accounts if only one exists @@ -1727,9 +1644,7 @@ class Interface: del app.automatic_rooms[account] del app.to_be_removed[account] del app.newly_added[account] - del app.sleeper_state[account] del app.last_message_time[account] - del app.status_before_autoaway[account] if len(app.connections) >= 2: # Do not merge accounts if only one exists self.roster.regroup = app.config.get('mergeaccounts') @@ -1980,8 +1895,6 @@ class Interface: GLib.timeout_add_seconds(timeout, self.process_connections) else: GLib.timeout_add(timeout, self.process_connections) - GLib.timeout_add_seconds(app.config.get( - 'check_idle_every_foo_seconds'), self.read_sleepy)
def remote_init(): if app.config.get('remote_control'): @@ -2107,9 +2020,7 @@ class Interface: app.to_be_removed[a] = [] app.nicks[a] = app.config.get_per('accounts', a, 'name') app.block_signed_in_notifications[a] = True - app.sleeper_state[a] = 0 app.last_message_time[a] = {} - app.status_before_autoaway[a] = ''
if sys.platform not in ('win32', 'darwin'): logind.enable()
===================================== gajim/roster_window.py ===================================== @@ -52,7 +52,6 @@ from gajim import gui_menu_builder
from gajim.common import app from gajim.common import helpers -from gajim.common import idle from gajim.common.exceptions import GajimGeneralException from gajim.common import i18n from gajim.common.helpers import save_roster_position @@ -2058,7 +2057,7 @@ class RosterWindow: def set_connecting_state(self, account): self.set_state(account, 'connecting')
- def send_status(self, account, status, txt, auto=False): + def send_status(self, account, status, txt): if status != 'offline': app.config.set_per('accounts', account, 'last_status', status) app.config.set_per('accounts', account, 'last_status_msg', @@ -2066,7 +2065,7 @@ class RosterWindow: if not app.account_is_available(account): self.set_connecting_state(account)
- self.send_status_continue(account, status, txt, auto) + self.send_status_continue(account, status, txt)
def send_pep(self, account, pep_dict): connection = app.connections[account] @@ -2101,18 +2100,11 @@ class RosterWindow: if ctrl: ctrl.update_all_pep_types()
- def send_status_continue(self, account, status, txt, auto): - if app.account_is_available(account): - if status == 'online' and not idle.Monitor.is_unknown(): - app.sleeper_state[account] = 'online' - elif app.sleeper_state[account] not in ('autoaway', 'autoxa') or \ - status == 'offline': - app.sleeper_state[account] = 'off' - + def send_status_continue(self, account, status, txt): if status == 'offline': self.delete_pep(app.get_jid_from_account(account), account)
- app.connections[account].change_status(status, txt, auto) + app.connections[account].change_status(status, txt)
def chg_contact_status(self, contact, show, status, account): """
===================================== test/lib/gajim_mocks.py ===================================== @@ -34,9 +34,7 @@ class MockConnection(Mock, ConnectionHandlers): app.to_be_removed[account] = [] app.nicks[account] = app.config.get_per('accounts', account, 'name') app.block_signed_in_notifications[account] = True - app.sleeper_state[account] = 0 app.last_message_time[account] = {} - app.status_before_autoaway[account] = ''
app.connections[account] = self
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/f7c0f2d3f98b7b4d5be7c6254731ac42...