From 5e1111727dfba8398945b6c7c924aa9b51721a20 Mon Sep 17 00:00:00 2001 From: Lars Vierbergen Date: Thu, 19 Jan 2017 22:58:36 +0100 Subject: [PATCH] Add generic toggle button module, and implement caffeine with it --- modules/caffeine.py | 53 +++++++++++++-------------------------- modules/toggle.py | 61 +++++++++++++++++++++++++++++++++++++++++++++ modules/util.py | 37 +++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 35 deletions(-) create mode 100644 modules/toggle.py diff --git a/modules/caffeine.py b/modules/caffeine.py index 01e7461..ec9a77c 100644 --- a/modules/caffeine.py +++ b/modules/caffeine.py @@ -1,19 +1,16 @@ import subprocess import logging -import time -from .core import AbstractControl, action +from modules.toggle import ToggleAction, ToggleControl +from .util import process_reaper, backoff logger = logging.getLogger(__name__) -class CaffeineControl(AbstractControl): +class CaffeineToggleAction(ToggleAction): def __init__(self): - super().__init__() - self.caffeine_enabled = False - self._activity_proc = None - self._activity_proc_lastrun = 0 + super().__init__(False) def configure(self, argument_parser): argument_parser.add_argument('--caffeine-timeout', @@ -21,31 +18,17 @@ class CaffeineControl(AbstractControl): type=int, default=10) - def respond_to(self, command): - if command == 'caffeine': - self.caffeine_enabled = not self.caffeine_enabled - logger.info("Set caffeine enabled %r", self.caffeine_enabled) - return True - - def __str__(self): - return action(self.create_pipe_command('caffeine'), 'C' if self.caffeine_enabled else 'c') - - def periodic(self): - if self._activity_proc is not None: - if self._activity_proc.returncode is None: - self._activity_proc.poll() - else: - logger.debug("Reaped subprocess: %s", self._activity_proc) - self._activity_proc = None - - if self.caffeine_enabled and self._activity_proc is None: - if self._activity_proc_lastrun + self.args.caffeine_timeout < time.time(): - logger.debug("Poking screensaver") - self._activity_proc = subprocess.Popen(['xscreensaver-command', '-deactivate'], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - self._activity_proc_lastrun = time.time() - - def dump_state(self): - return dict(caffeine_enabled=self.caffeine_enabled) - - def load_state(self, state): - self.caffeine_enabled = state['caffeine_enabled'] + def bind_arguments(self, args): + super().bind_arguments(args) + self.periodic = process_reaper(backoff(self.args.caffeine_timeout)(self.__periodic)) + + def __periodic(self): + if self.state: + logger.debug("Poking screensaver") + return subprocess.Popen(['xscreensaver-command', '-deactivate'], + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + + +def CaffeineControl(): + return ToggleControl('c', CaffeineToggleAction()) diff --git a/modules/toggle.py b/modules/toggle.py new file mode 100644 index 0000000..97bfa06 --- /dev/null +++ b/modules/toggle.py @@ -0,0 +1,61 @@ +from .core import AbstractControl, WrappingControl, action +import abc +import functools + + +class ToggleAction(AbstractControl, metaclass=abc.ABCMeta): + def __init__(self, initial_state: bool = False): + super().__init__() + self.__state = initial_state + + def dump_state(self): + return {'state': self.__state} + + def load_state(self, state): + if 'state' in state: + self.state = state['state'] + + def toggle(self): + self.state = not self.state + + @property + def state(self): + return self.__state + + @state.setter + def state(self, state: bool): + if self.__state == state: + return + if state: + self.enable() + else: + self.disable() + self.__state = state + + def enable(self): + pass + + def disable(self): + pass + + +class ToggleControl(WrappingControl): + """Implements a simple toggle button""" + + def __init__(self, letter: str, toggle_action: ToggleAction): + super().__init__(toggle_action) + self.__letter = letter + + def __get_control_command(self): + return '%s:%s:%s' % (self.__class__.__name__, self.__letter, self.child.__class__.__name__) + + def respond_to(self, command): + if command == self.__get_control_command(): + self.child.toggle() + return True + + def __str__(self): + return action( + self.create_pipe_command(self.__get_control_command()), + self.__letter.upper() if self.child.state else self.__letter.lower() + ) diff --git a/modules/util.py b/modules/util.py index e3b1818..4299dbe 100644 --- a/modules/util.py +++ b/modules/util.py @@ -1,5 +1,9 @@ import sys import os.path +from functools import wraps + +import time + from .core import AbstractControl @@ -26,3 +30,36 @@ class ChildReaperControl(AbstractControl): except: pass + +def backoff(backoff, default=None): + def decorator(fn): + last_called = 0 + + @wraps(fn) + def wrapper(*a, **kw): + nonlocal last_called + if last_called + backoff > time.time(): + return default + last_called = time.time() + return fn(*a, **kw) + + return wrapper + + return decorator + + +def process_reaper(fn): + process = None + + @wraps(fn) + def wrapper(*a, **kw): + nonlocal process + if process is not None: + process.poll() + if process.returncode is not None: + process = None + + if process is None: + process = fn(*a, **kw) + + return wrapper