Refactor idle module

- Get rid of sleepy.py, handle everything in idle.py
- Introduce one Monitor class that handles everything
This commit is contained in:
Philipp Hörist 2018-05-21 02:20:30 +02:00
parent 2e5d966f1d
commit c5df74c509
10 changed files with 212 additions and 257 deletions

View File

@ -276,8 +276,8 @@ def detect_dependencies():
# IDLE
try:
from gajim.common import sleepy
if sleepy.SUPPORTED:
from gajim.common import idle
if idle.Monitor.is_available():
_dependencies['IDLE'] = True
except Exception:
pass

View File

@ -61,6 +61,7 @@ from gajim.common import app
from gajim.common import gpg
from gajim.common import passwords
from gajim.common import i18n
from gajim.common import idle
from gajim.common.connection_handlers import *
from gajim.common.contacts import GC_Contact
from gajim.gtkgui_helpers import get_action
@ -596,7 +597,7 @@ class CommonConnection:
idle_time = None
if auto:
if app.is_installed('IDLE') and app.config.get('autoaway'):
idle_sec = int(app.interface.sleeper.getIdleSec())
idle_sec = idle.Monitor.get_idle_sec()
idle_time = time.strftime('%Y-%m-%dT%H:%M:%SZ',
time.gmtime(time.time() - idle_sec))
app.nec.push_incoming_event(BeforeChangeShowEvent(None,
@ -2025,8 +2026,8 @@ class Connection(CommonConnection, ConnectionHandlers):
if signed:
p.setTag(nbxmpp.NS_SIGNED + ' x').setData(signed)
if idle_time:
idle = p.setTag('idle', namespace=nbxmpp.NS_IDLE)
idle.setAttr('since', idle_time)
idle_node = p.setTag('idle', namespace=nbxmpp.NS_IDLE)
idle_node.setAttr('since', idle_time)
if self.connection:
self.connection.send(p)
self.priority = priority
@ -2761,11 +2762,11 @@ class Connection(CommonConnection, ConnectionHandlers):
self.add_lang(p)
if auto:
if app.is_installed('IDLE') and app.config.get('autoaway'):
idle_sec = int(app.interface.sleeper.getIdleSec())
idle_sec = idle.Monitor.get_idle_sec()
idle_time = time.strftime('%Y-%m-%dT%H:%M:%SZ',
time.gmtime(time.time() - idle_sec))
idle = p.setTag('idle', namespace=nbxmpp.NS_IDLE)
idle.setAttr('since', idle_time)
idle_node = p.setTag('idle', namespace=nbxmpp.NS_IDLE)
idle_node.setAttr('since', idle_time)
# send instantly so when we go offline, status is sent to gc before we
# disconnect from jabber server
self.connection.send(p)

View File

@ -48,6 +48,7 @@ from gajim.common import app
from gajim.common import dataforms
from gajim.common import jingle_xtls
from gajim.common import configpaths
from gajim.common import idle
from gajim.common.caps_cache import muc_caps_cache
from gajim.common.commands import ConnectionCommands
from gajim.common.pubsub import ConnectionPubSub
@ -1730,7 +1731,7 @@ ConnectionHTTPUpload):
'send_idle_time'):
iq_obj = obj.stanza.buildReply('result')
qp = iq_obj.setQuery()
qp.attrs['seconds'] = int(app.interface.sleeper.getIdleSec())
qp.attrs['seconds'] = idle.Monitor.get_idle_sec()
else:
iq_obj = obj.stanza.buildReply('error')
err = nbxmpp.ErrorNode(name=nbxmpp.NS_STANZAS + \

View File

@ -106,6 +106,12 @@ class JIDConstant(IntEnum):
NORMAL_TYPE = 0
ROOM_TYPE = 1
@unique
class IdleState(IntEnum):
UNKNOWN = 0
XA = 1
AWAY = 2
AWAKE = 3
SSLError = {
2: _("Unable to get issuer certificate"),

View File

@ -1,30 +1,35 @@
## src/common/idle.py
##
## (C) 2008 Thorsten P. 'dGhvcnN0ZW5wIEFUIHltYWlsIGNvbQ==\n'.decode("base64")
##
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
#
# Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
# Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
# Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
# Copyright (C) 2008 Mateusz Biliński <mateusz AT bilinski.it>
# Copyright (C) 2008 Thorsten P. 'dGhvcnN0ZW5wIEFUIHltYWlsIGNvbQ==\n'.decode("base64")
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
#
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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 ctypes
import ctypes.util
import logging
from gi.repository import Gio
from gi.repository import GLib
log = logging.getLogger('gajim.c.idle')
from gajim.common.const import IdleState
idle_monitor = None
log = logging.getLogger('gajim.c.idle')
class DBusGnomeIdleMonitor:
@ -63,13 +68,19 @@ class DBusGnomeIdleMonitor:
def get_idle_sec(self):
try:
self.last_idle_time = self._get_idle_sec_fail()
except GLib.Error as e:
except GLib.Error as error:
log.warning(
'org.gnome.Mutter.IdleMonitor.GetIdletime() failed: %s',
repr(e))
error)
return self.last_idle_time
def is_extended_away(self):
return False
class XssIdleMonitor:
def __init__(self):
class XScreenSaverInfo(ctypes.Structure):
_fields_ = [
@ -81,9 +92,6 @@ class XScreenSaverInfo(ctypes.Structure):
('eventMask', ctypes.c_ulong)
]
class XssIdleMonitor:
def __init__(self):
XScreenSaverInfo_p = ctypes.POINTER(XScreenSaverInfo)
display_p = ctypes.c_void_p
@ -91,7 +99,7 @@ class XssIdleMonitor:
c_int_p = ctypes.POINTER(ctypes.c_int)
libX11path = ctypes.util.find_library('X11')
if libX11path == None:
if libX11path is None:
raise OSError('libX11 could not be found.')
libX11 = ctypes.cdll.LoadLibrary(libX11path)
libX11.XOpenDisplay.restype = display_p
@ -100,60 +108,151 @@ class XssIdleMonitor:
libX11.XDefaultRootWindow.argtypes = display_p,
libXsspath = ctypes.util.find_library('Xss')
if libXsspath == None:
if libXsspath is None:
raise OSError('libXss could not be found.')
self.libXss = ctypes.cdll.LoadLibrary(libXsspath)
self.libXss.XScreenSaverQueryExtension.argtypes = display_p, c_int_p, c_int_p
self.libXss.XScreenSaverAllocInfo.restype = XScreenSaverInfo_p
self.libXss.XScreenSaverQueryInfo.argtypes = (display_p, xid, XScreenSaverInfo_p)
self.libXss.XScreenSaverQueryInfo.argtypes = (
display_p, xid, XScreenSaverInfo_p)
self.dpy_p = libX11.XOpenDisplay(None)
if self.dpy_p == None:
if self.dpy_p is None:
raise OSError('Could not open X Display.')
_event_basep = ctypes.c_int()
_error_basep = ctypes.c_int()
if self.libXss.XScreenSaverQueryExtension(self.dpy_p, ctypes.byref(_event_basep),
ctypes.byref(_error_basep)) == 0:
extension = self.libXss.XScreenSaverQueryExtension(
self.dpy_p, ctypes.byref(_event_basep), ctypes.byref(_error_basep))
if extension == 0:
raise OSError('XScreenSaver Extension not available on display.')
self.xss_info_p = self.libXss.XScreenSaverAllocInfo()
if self.xss_info_p == None:
if self.xss_info_p is None:
raise OSError('XScreenSaverAllocInfo: Out of Memory.')
self.rootwindow = libX11.XDefaultRootWindow(self.dpy_p)
def get_idle_sec(self):
if self.libXss.XScreenSaverQueryInfo(
self.dpy_p,
self.rootwindow,
self.xss_info_p) == 0:
return 0
else:
info = self.libXss.XScreenSaverQueryInfo(
self.dpy_p, self.rootwindow, self.xss_info_p)
if info == 0:
return info
return int(self.xss_info_p.contents.idle / 1000)
def is_extended_away(self):
return False
def getIdleSec():
class WindowsIdleMonitor:
OpenInputDesktop = ctypes.windll.user32.OpenInputDesktop
CloseDesktop = ctypes.windll.user32.CloseDesktop
SystemParametersInfo = ctypes.windll.user32.SystemParametersInfoW
GetTickCount = ctypes.windll.kernel32.GetTickCount
GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo
def __init__(self):
class LASTINPUTINFO(ctypes.Structure):
_fields_ = [('cbSize', ctypes.c_uint), ('dwTime', ctypes.c_uint)]
self.lastInputInfo = LASTINPUTINFO()
self.lastInputInfo.cbSize = ctypes.sizeof(self.lastInputInfo)
def get_idle_sec(self):
self.GetLastInputInfo(ctypes.byref(self.lastInputInfo))
return float(self.GetTickCount() - self.lastInputInfo.dwTime) / 1000
def is_extended_away(self):
# Check if Screen Saver is running
# 0x72 is SPI_GETSCREENSAVERRUNNING
saver_runing = ctypes.c_int(0)
info = self.SystemParametersInfo(0x72, 0, ctypes.byref(saver_runing), 0)
if info and saver_runing.value:
return True
# Check if Screen is locked
desk = self.OpenInputDesktop(0, False, 0)
if not desk:
return True
self.CloseDesktop(desk)
return False
class IdleMonitor:
def __init__(self):
self.set_interval()
self._state = IdleState.AWAKE
self._idle_monitor = self._get_idle_monitor()
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 is_available(self):
return self._idle_monitor is not None
def state(self):
if not self.is_available():
return IdleState.UNKNOWN
return self._state
def is_xa(self):
return self.state == IdleState.XA
def is_away(self):
return self.state == IdleState.AWAY
def is_awake(self):
return self.state == IdleState.AWAKE
def is_unknown(self):
return self.state == IdleState.UNKNOWN
def _get_idle_monitor(self):
if sys.platform == 'win32':
return WindowsIdleMonitor()
try:
return DBusGnomeIdleMonitor()
except GLib.Error as error:
log.info('Idle time via D-Bus not available: %s', error)
try:
return XssIdleMonitor()
except OSError as error:
log.info('Idle time via XScreenSaverInfo '
'not available: %s', error)
def get_idle_sec(self):
return self._idle_monitor.get_idle_sec()
def poll(self):
"""
Return the idle time in seconds
Check to see if we should change state
"""
if idle_monitor is None:
return 0
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
return True
idle_time = self.get_idle_sec()
log.info('Idle time: %s', idle_time)
# xa is stronger than away so check for xa first
if idle_time > self._xa_interval:
self._state = IdleState.XA
elif idle_time > self._away_interval:
self._state = IdleState.AWAY
else:
return idle_monitor.get_idle_sec()
self._state = IdleState.AWAKE
return True
try:
idle_monitor = DBusGnomeIdleMonitor()
except GLib.Error as e:
log.info("Idle time via D-Bus not available: %s", repr(e))
try:
idle_monitor = XssIdleMonitor()
except OSError as e:
log.info("Idle time via XScreenSaverInfo not available: %s", repr(e))
raise Exception('No supported idle monitor found')
if __name__ == '__main__':
import time
time.sleep(2.1)
print(getIdleSec())
Monitor = IdleMonitor()

View File

@ -1,147 +0,0 @@
# -*- coding:utf-8 -*-
## src/common/sleepy.py
##
## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
## Copyright (C) 2008 Mateusz Biliński <mateusz AT bilinski.it>
##
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
from gajim.common import app
import os
import logging
log = logging.getLogger('gajim.c.sleepy')
STATE_UNKNOWN = 'OS probably not supported'
STATE_XA = 'extended away'
STATE_AWAY = 'away'
STATE_AWAKE = 'awake'
SUPPORTED = True
try:
if os.name == 'nt':
import ctypes
GetTickCount = ctypes.windll.kernel32.GetTickCount
GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo
class LASTINPUTINFO(ctypes.Structure):
_fields_ = [('cbSize', ctypes.c_uint), ('dwTime', ctypes.c_uint)]
lastInputInfo = LASTINPUTINFO()
lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo)
# one or more of these may not be supported before XP.
OpenInputDesktop = ctypes.windll.user32.OpenInputDesktop
CloseDesktop = ctypes.windll.user32.CloseDesktop
SystemParametersInfo = ctypes.windll.user32.SystemParametersInfoW
else: # unix
from gajim.common import idle
except Exception:
log.warning('Unable to load idle module')
SUPPORTED = False
class SleepyWindows:
def __init__(self, away_interval = 60, xa_interval = 120):
self.away_interval = away_interval
self.xa_interval = xa_interval
self.state = STATE_AWAKE # assume we are awake
def getIdleSec(self):
GetLastInputInfo(ctypes.byref(lastInputInfo))
idleDelta = float(GetTickCount() - lastInputInfo.dwTime) / 1000
return idleDelta
def poll(self):
"""
Check to see if we should change state
"""
if not SUPPORTED:
return False
# screen saver, in windows >= XP
saver_runing = ctypes.c_int(0)
# 0x72 is SPI_GETSCREENSAVERRUNNING
if SystemParametersInfo(0x72, 0, ctypes.byref(saver_runing), 0) and \
saver_runing.value:
self.state = STATE_XA
return True
desk = OpenInputDesktop(0, False, 0)
if not desk:
# Screen locked
self.state = STATE_XA
return True
CloseDesktop(desk)
idleTime = self.getIdleSec()
# xa is stronger than away so check for xa first
if idleTime > self.xa_interval:
self.state = STATE_XA
elif idleTime > self.away_interval:
self.state = STATE_AWAY
else:
self.state = STATE_AWAKE
return True
def getState(self):
return self.state
def setState(self, val):
self.state = val
class SleepyUnix:
def __init__(self, away_interval = 60, xa_interval = 120):
global SUPPORTED
self.away_interval = away_interval
self.xa_interval = xa_interval
self.state = STATE_AWAKE # assume we are awake
def getIdleSec(self):
return idle.getIdleSec()
def poll(self):
"""
Check to see if we should change state
"""
if not SUPPORTED:
return False
idleTime = self.getIdleSec()
# xa is stronger than away so check for xa first
if idleTime > self.xa_interval:
self.state = STATE_XA
elif idleTime > self.away_interval:
self.state = STATE_AWAY
else:
self.state = STATE_AWAKE
return True
def getState(self):
return self.state
def setState(self, val):
self.state = val
if os.name == 'nt':
Sleepy = SleepyWindows
else:
Sleepy = SleepyUnix

View File

@ -40,7 +40,7 @@ from gi.repository import GObject
from gi.repository import GLib
from gajim.common import config as c_config
from gajim.common import sleepy
from gajim.common import idle
from gajim.common.i18n import Q_
from gajim import gtkgui_helpers
@ -353,7 +353,7 @@ class PreferencesWindow:
self.auto_xa_message_entry.set_text(st)
self.auto_xa_message_entry.set_sensitive(app.config.get('autoxa'))
if not sleepy.SUPPORTED:
if not idle.Monitor.is_available():
self.xml.get_object('autoaway_table').set_sensitive(False)
# ask_status when online / offline
@ -925,8 +925,7 @@ class PreferencesWindow:
def on_auto_away_time_spinbutton_value_changed(self, widget):
aat = widget.get_value_as_int()
app.config.set('autoawaytime', aat)
app.interface.sleeper = sleepy.Sleepy(
app.config.get('autoawaytime') * 60,
idle.Monitor.set_interval(app.config.get('autoawaytime') * 60,
app.config.get('autoxatime') * 60)
def on_auto_away_message_entry_changed(self, widget):
@ -939,8 +938,7 @@ class PreferencesWindow:
def on_auto_xa_time_spinbutton_value_changed(self, widget):
axt = widget.get_value_as_int()
app.config.set('autoxatime', axt)
app.interface.sleeper = sleepy.Sleepy(
app.config.get('autoawaytime') * 60,
idle.Monitor.set_interval(app.config.get('autoawaytime') * 60,
app.config.get('autoxatime') * 60)
def on_auto_xa_message_entry_changed(self, widget):

View File

@ -163,8 +163,8 @@ class FeaturesWindow:
return app.is_installed('GSPELL')
def idle_available(self):
from gajim.common import sleepy
return sleepy.SUPPORTED
from gajim.common import idle
return idle.Monitor.is_available()
def pycrypto_available(self):
return app.is_installed('PYCRYPTO')
@ -181,4 +181,3 @@ class FeaturesWindow:
def gupnp_igd_available(self):
return app.is_installed('UPNP')

View File

@ -79,7 +79,7 @@ from gajim.filetransfers_window import FileTransfersWindow
from gajim.atom_window import AtomWindow
from gajim.session import ChatControlSession
from gajim.common import sleepy
from gajim.common import idle
from nbxmpp import idlequeue
from nbxmpp import Hashes2
@ -1114,13 +1114,13 @@ class Interface:
app.logger.insert_jid(obj.conn.get_own_jid().getStripped())
account = obj.conn.name
app.block_signed_in_notifications[account] = True
state = self.sleeper.getState()
connected = obj.conn.connected
if state != sleepy.STATE_UNKNOWN and connected in (2, 3):
if idle.Monitor.is_unknown() and connected in (2, 3):
# we go online or free for chat, so we activate auto status
app.sleeper_state[account] = 'online'
elif not ((state == sleepy.STATE_AWAY and connected == 4) or \
(state == sleepy.STATE_XA and connected == 5)):
elif not ((idle.Monitor.is_away() and connected == 4) or \
(idle.Monitor.is_xa() and connected == 5)):
# If we are autoaway/xa and come back after a disconnection, do
# nothing
# Else disable autoaway
@ -2239,15 +2239,15 @@ class Interface:
"""
Check idle status and change that status if needed
"""
if not self.sleeper.poll():
if not idle.Monitor.poll():
# idle detection is not supported in that OS
return False # stop looping in vain
state = self.sleeper.getState()
for account in app.connections:
if account not in app.sleeper_state or \
not app.sleeper_state[account]:
continue
if state == sleepy.STATE_AWAKE:
if idle.Monitor.is_awake():
if app.sleeper_state[account] in ('autoaway', 'autoxa'):
# we go online
self.roster.send_status(account, 'online',
@ -2261,7 +2261,7 @@ class Interface:
app.status_before_autoaway[account])
app.status_before_autoaway[account] = ''
app.sleeper_state[account] = 'off'
elif state == sleepy.STATE_AWAY and app.config.get('autoaway'):
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] = \
@ -2288,7 +2288,7 @@ class Interface:
self.roster.send_status(account, app.SHOW_LIST[connected],
app.status_before_autoaway[account], auto=True)
app.sleeper_state[account] = 'idle'
elif state == sleepy.STATE_XA and \
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]
@ -2913,8 +2913,7 @@ class Interface:
self.show_vcard_when_connect = []
self.sleeper = sleepy.Sleepy(
app.config.get('autoawaytime') * 60, # make minutes to seconds
idle.Monitor.set_interval(app.config.get('autoawaytime') * 60,
app.config.get('autoxatime') * 60)
gtkgui_helpers.make_jabber_state_images()

View File

@ -46,7 +46,6 @@ import locale
from enum import IntEnum, unique
from gajim.common import sleepy
from gajim import history_window
from gajim import dialogs
from gajim import vcard
@ -62,6 +61,7 @@ from gajim.common.const import AvatarSize
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
if app.is_installed('GEOCLUE'):
@ -2125,8 +2125,7 @@ class RosterWindow:
def send_status_continue(self, account, status, txt, auto, to):
if app.account_is_connected(account) and not to:
if status == 'online' and app.interface.sleeper.getState() != \
sleepy.STATE_UNKNOWN:
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':