From 7b08777d77d6cfd5a4eeeee81fb51f5fdedde987 Mon Sep 17 00:00:00 2001 From: mw Date: Wed, 19 Jun 2019 22:26:48 +0200 Subject: [PATCH 01/37] Add support for switching dpms --- bumblebee/modules/caffeine.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index 2354ed0..91fb0e9 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -39,9 +39,11 @@ class Module(bumblebee.engine.Module): def _toggle(self, widget): if self._active(): + bumblebee.util.execute("xset +dpms") bumblebee.util.execute("xset s default") bumblebee.util.execute("notify-send \"Out of coffee\"") else: + bumblebee.util.execute("xset -dpms") bumblebee.util.execute("xset s off") bumblebee.util.execute("notify-send \"Consuming caffeine\"") From 61fe85842a0c932c4a8375f657eb06f406344ace Mon Sep 17 00:00:00 2001 From: mw Date: Sun, 25 Aug 2019 20:53:42 +0200 Subject: [PATCH 02/37] Use xdg-screensaver instead of xset --- bumblebee/modules/caffeine.py | 42 +++++++++++++++-------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index 91fb0e9..3002dfb 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -1,9 +1,9 @@ -# pylint: disable=C0111,R0903 +#pylint: disable=C0111,R0903 """Enable/disable automatic screen locking. Requires the following executables: - * xset + * xdg-screensaver * notify-send """ @@ -14,37 +14,31 @@ import bumblebee.engine class Module(bumblebee.engine.Module): def __init__(self, engine, config): super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.caffeine) + bumblebee.output.Widget(full_text="") ) + self._active = False + self.interval(1) + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self._toggle ) - def caffeine(self, widget): - return "" - def state(self, widget): - if self._active(): + if self._active: return "activated" return "deactivated" - def _active(self): - for line in bumblebee.util.execute("xset q").split("\n"): - if "timeout" in line: - timeout = int(line.split(" ")[4]) - if timeout == 0: - return True - return False - return False - - def _toggle(self, widget): - if self._active(): - bumblebee.util.execute("xset +dpms") - bumblebee.util.execute("xset s default") - bumblebee.util.execute("notify-send \"Out of coffee\"") - else: - bumblebee.util.execute("xset -dpms") - bumblebee.util.execute("xset s off") + def _toggle(self, event): + self._active = not self._active + if self._active: + bumblebee.util.execute("xdg-screensaver reset") bumblebee.util.execute("notify-send \"Consuming caffeine\"") + else: + bumblebee.util.execute("notify-send \"Out of coffee\"") + + def update(self, widgets): + if self._active: + bumblebee.util.execute("xdg-screensaver reset") + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From c05fc3ae4d6ac0ed459150acf2c19fd892c2ea9f Mon Sep 17 00:00:00 2001 From: mw Date: Sun, 25 Aug 2019 21:08:00 +0200 Subject: [PATCH 03/37] Add some basic error handling in case the executables don't exist --- bumblebee/modules/caffeine.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index 3002dfb..0db8fad 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -30,11 +30,14 @@ class Module(bumblebee.engine.Module): def _toggle(self, event): self._active = not self._active - if self._active: - bumblebee.util.execute("xdg-screensaver reset") - bumblebee.util.execute("notify-send \"Consuming caffeine\"") - else: - bumblebee.util.execute("notify-send \"Out of coffee\"") + try: + if self._active: + bumblebee.util.execute("xdg-screensaver reset") + bumblebee.util.execute("notify-send \"Consuming caffeine\"") + else: + bumblebee.util.execute("notify-send \"Out of coffee\"") + except: + self._active = not self._active def update(self, widgets): if self._active: From 6652a581dfd2ec4700bd9ce54d9cd88647dc099f Mon Sep 17 00:00:00 2001 From: mw Date: Tue, 27 Aug 2019 21:31:50 +0200 Subject: [PATCH 04/37] PoC using xdg-screensaver's suspend mechanisms --- bumblebee/modules/caffeine.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index 0db8fad..7b2e290 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -4,12 +4,15 @@ Requires the following executables: * xdg-screensaver + * xdotool * notify-send """ import bumblebee.input import bumblebee.output import bumblebee.engine +import psutil +import os class Module(bumblebee.engine.Module): def __init__(self, engine, config): @@ -17,7 +20,7 @@ class Module(bumblebee.engine.Module): bumblebee.output.Widget(full_text="") ) self._active = False - self.interval(1) + self._xid = 0 engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self._toggle @@ -32,16 +35,16 @@ class Module(bumblebee.engine.Module): self._active = not self._active try: if self._active: - bumblebee.util.execute("xdg-screensaver reset") + self._xid = bumblebee.util.execute("xdotool search --class \"i3bar\"").strip() + bumblebee.util.execute("xdg-screensaver suspend {}".format(self._xid)) bumblebee.util.execute("notify-send \"Consuming caffeine\"") else: + for process in psutil.process_iter(): + if process.cmdline() == ['/usr/bin/xprop','-id',str(self._xid),'-spy']: + pid = process.pid + os.kill(pid,9) bumblebee.util.execute("notify-send \"Out of coffee\"") except: self._active = not self._active - def update(self, widgets): - if self._active: - bumblebee.util.execute("xdg-screensaver reset") - - # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From b59ecc8aac71232186f234b8bf4669bbc18a64b0 Mon Sep 17 00:00:00 2001 From: mw Date: Wed, 28 Aug 2019 00:09:04 +0200 Subject: [PATCH 05/37] PoC use double fork to escape SIGSTOP --- bumblebee/modules/caffeine.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index 7b2e290..fa2876e 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -36,7 +36,13 @@ class Module(bumblebee.engine.Module): try: if self._active: self._xid = bumblebee.util.execute("xdotool search --class \"i3bar\"").strip() - bumblebee.util.execute("xdg-screensaver suspend {}".format(self._xid)) + pid = os.fork() + if pid == 0: + os.setsid() + bumblebee.util.execute("xdg-screensaver suspend {}".format(self._xid)) + os._exit(0) + else: + os.waitpid(pid,0) bumblebee.util.execute("notify-send \"Consuming caffeine\"") else: for process in psutil.process_iter(): From b956e8e2a65cd29f676082914d7b8ff7ec58d57c Mon Sep 17 00:00:00 2001 From: adario Date: Wed, 28 Aug 2019 19:08:42 +0200 Subject: [PATCH 06/37] find aliases without importing modules --- bumblebee/engine.py | 31 ++++++++++++++-------------- bumblebee/modules/aliases/datetime | 2 ++ bumblebee/modules/aliases/datetimetz | 2 ++ bumblebee/modules/aliases/pulseaudio | 2 ++ bumblebee/modules/aliases/test | 1 + bumblebee/modules/datetime.py | 2 -- bumblebee/modules/datetimetz.py | 2 -- bumblebee/modules/pulseaudio.py | 2 -- bumblebee/modules/test.py | 2 -- 9 files changed, 23 insertions(+), 23 deletions(-) create mode 100644 bumblebee/modules/aliases/datetime create mode 100644 bumblebee/modules/aliases/datetimetz create mode 100644 bumblebee/modules/aliases/pulseaudio create mode 100644 bumblebee/modules/aliases/test diff --git a/bumblebee/engine.py b/bumblebee/engine.py index 6c74f6a..07d32d6 100644 --- a/bumblebee/engine.py +++ b/bumblebee/engine.py @@ -16,16 +16,6 @@ try: except ImportError: from configparser import RawConfigParser -def all_modules(): - """Return a list of available modules""" - result = [] - path = os.path.dirname(bumblebee.modules.__file__) - for mod in [name for _, name, _ in pkgutil.iter_modules([path])]: - result.append({ - "name": mod - }) - return result - class Module(object): """Module instance base class @@ -231,14 +221,25 @@ class Engine(object): button=button["id"], cmd=module.parameter(button["name"])) def _read_aliases(self): + """Retruns a dictionary that maps every alias to its real module name""" result = {} - for module in all_modules(): + m_path = os.path.abspath(bumblebee.modules.__file__) + m_path = os.path.dirname(m_path) + ALIASES_PATH = m_path + "/aliases/" + for name in os.listdir(ALIASES_PATH): try: - mod = importlib.import_module("bumblebee.modules.{}".format(module["name"])) - for alias in getattr(mod, "ALIASES", []): - result[alias] = module["name"] + # listdir does not retur full paths + f_path = ALIASES_PATH + name + # skip any directory + if not os.path.isfile(f_path): continue + with open(f_path) as f: + for alias in f.readlines(): + alias = alias.strip() + # skip empty lines + if len(alias) == 0: continue + result[alias] = name except Exception as error: - log.warning("failed to import {}: {}".format(module["name"], str(error))) + log.warning("failed to load aliases of {}: {}".format(name, str(error))) return result def _load_module(self, module_name, config_name=None): diff --git a/bumblebee/modules/aliases/datetime b/bumblebee/modules/aliases/datetime new file mode 100644 index 0000000..8e43d6a --- /dev/null +++ b/bumblebee/modules/aliases/datetime @@ -0,0 +1,2 @@ +date +time diff --git a/bumblebee/modules/aliases/datetimetz b/bumblebee/modules/aliases/datetimetz new file mode 100644 index 0000000..08f6d17 --- /dev/null +++ b/bumblebee/modules/aliases/datetimetz @@ -0,0 +1,2 @@ +datetz +timetz diff --git a/bumblebee/modules/aliases/pulseaudio b/bumblebee/modules/aliases/pulseaudio new file mode 100644 index 0000000..9bcee50 --- /dev/null +++ b/bumblebee/modules/aliases/pulseaudio @@ -0,0 +1,2 @@ +pasink +pasource diff --git a/bumblebee/modules/aliases/test b/bumblebee/modules/aliases/test new file mode 100644 index 0000000..dc52a4f --- /dev/null +++ b/bumblebee/modules/aliases/test @@ -0,0 +1 @@ +test-alias diff --git a/bumblebee/modules/datetime.py b/bumblebee/modules/datetime.py index e9afeb2..8899c4a 100644 --- a/bumblebee/modules/datetime.py +++ b/bumblebee/modules/datetime.py @@ -16,8 +16,6 @@ import datetime import locale import bumblebee.engine -ALIASES = ["date", "time"] - def default_format(module): default = "%x %X" if module == "date": diff --git a/bumblebee/modules/datetimetz.py b/bumblebee/modules/datetimetz.py index e298249..1d0e3cb 100644 --- a/bumblebee/modules/datetimetz.py +++ b/bumblebee/modules/datetimetz.py @@ -26,8 +26,6 @@ import bumblebee.input import bumblebee.output import bumblebee.engine -ALIASES = ["datetz", "timetz"] - def default_format(module): default = "%x %X %Z" if module == "datetz": diff --git a/bumblebee/modules/pulseaudio.py b/bumblebee/modules/pulseaudio.py index 8b2f276..5cdd23c 100644 --- a/bumblebee/modules/pulseaudio.py +++ b/bumblebee/modules/pulseaudio.py @@ -24,8 +24,6 @@ import bumblebee.input import bumblebee.output import bumblebee.engine -ALIASES = ["pasink", "pasource"] - class Module(bumblebee.engine.Module): def __init__(self, engine, config): super(Module, self).__init__(engine, config, diff --git a/bumblebee/modules/test.py b/bumblebee/modules/test.py index b654575..5e92e0e 100644 --- a/bumblebee/modules/test.py +++ b/bumblebee/modules/test.py @@ -5,8 +5,6 @@ import bumblebee.engine -ALIASES = ["test-alias"] - class Module(bumblebee.engine.Module): def __init__(self, engine, config): super(Module, self).__init__(engine, config, From 2c861a3092ee88782be04ab1cb60f32da1a57292 Mon Sep 17 00:00:00 2001 From: mw Date: Sun, 1 Sep 2019 22:38:22 +0200 Subject: [PATCH 07/37] Clean up --- bumblebee/modules/caffeine.py | 81 +++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index fa2876e..75bea15 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -5,14 +5,16 @@ Requires the following executables: * xdg-screensaver * xdotool + * xprop (as dependency for xdotool) * notify-send """ +import psutil +import os +import logging import bumblebee.input import bumblebee.output import bumblebee.engine -import psutil -import os class Module(bumblebee.engine.Module): def __init__(self, engine, config): @@ -20,37 +22,72 @@ class Module(bumblebee.engine.Module): bumblebee.output.Widget(full_text="") ) self._active = False - self._xid = 0 + self._xid = None engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self._toggle ) + def _check_requirements(self): + requirements = ['xdotool','xprop','xdg-screensaver'] + missing = [] + for tool in requirements: + if not bumblebee.util.which(tool): + missing.append(tool) + return missing + + def _get_i3bar_xid(self): + xid = bumblebee.util.execute("xdotool search --class \"i3bar\"").partition('\n')[0].strip() + if xid.isdigit(): + return xid + else: + logging.info("Module caffeine: xdotool couldn't get X window ID of \"i3bar\".") + return None + + def _notify(self): + if not bumblebee.util.which('notify-send'): + return + + if self._active: + bumblebee.util.execute("notify-send \"Consuming caffeine\"") + else: + bumblebee.util.execute("notify-send \"Out of coffee\"") + + def _suspend_screensaver(self): + self._xid = self._get_i3bar_xid() + if self._xid == None: + return + + pid = os.fork() + if pid == 0: + os.setsid() + bumblebee.util.execute("xdg-screensaver suspend {}".format(self._xid)) + os._exit(0) + else: + os.waitpid(pid,0) + + def _resume_screensaver(self): + for process in psutil.process_iter(): + if process.cmdline() == [bumblebee.util.which('xprop'),'-id',str(self._xid),'-spy']: + pid = process.pid + os.kill(pid,9) + def state(self, widget): if self._active: return "activated" return "deactivated" def _toggle(self, event): + missing = self._check_requirements() + if missing: + logging.warning("Could not run caffeine - missing {}!".format(", ".join(missing))) + return + self._active = not self._active - try: - if self._active: - self._xid = bumblebee.util.execute("xdotool search --class \"i3bar\"").strip() - pid = os.fork() - if pid == 0: - os.setsid() - bumblebee.util.execute("xdg-screensaver suspend {}".format(self._xid)) - os._exit(0) - else: - os.waitpid(pid,0) - bumblebee.util.execute("notify-send \"Consuming caffeine\"") - else: - for process in psutil.process_iter(): - if process.cmdline() == ['/usr/bin/xprop','-id',str(self._xid),'-spy']: - pid = process.pid - os.kill(pid,9) - bumblebee.util.execute("notify-send \"Out of coffee\"") - except: - self._active = not self._active + if self._active: + self._suspend_screensaver() + else: + self._resume_screensaver() + self._notify() # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From 4743558a689d9e168f6402aa316bd9b1979f352e Mon Sep 17 00:00:00 2001 From: mw Date: Sun, 1 Sep 2019 22:54:38 +0200 Subject: [PATCH 08/37] Add fixes --- bumblebee/modules/caffeine.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index 75bea15..84a4f3b 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -9,9 +9,9 @@ Requires the following executables: * notify-send """ -import psutil -import os import logging +import os +import psutil import bumblebee.input import bumblebee.output import bumblebee.engine @@ -40,8 +40,7 @@ class Module(bumblebee.engine.Module): xid = bumblebee.util.execute("xdotool search --class \"i3bar\"").partition('\n')[0].strip() if xid.isdigit(): return xid - else: - logging.info("Module caffeine: xdotool couldn't get X window ID of \"i3bar\".") + logging.info("Module caffeine: xdotool couldn't get X window ID of \"i3bar\".") return None def _notify(self): @@ -55,7 +54,7 @@ class Module(bumblebee.engine.Module): def _suspend_screensaver(self): self._xid = self._get_i3bar_xid() - if self._xid == None: + if self._xid is None: return pid = os.fork() From 0b9829bc77f8e535ac0895a17e35cfac45c9b0d0 Mon Sep 17 00:00:00 2001 From: mw Date: Mon, 2 Sep 2019 16:42:25 +0200 Subject: [PATCH 09/37] Make pylint happy --- bumblebee/modules/caffeine.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index 84a4f3b..935cfb2 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -23,13 +23,13 @@ class Module(bumblebee.engine.Module): ) self._active = False self._xid = None - + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self._toggle ) def _check_requirements(self): - requirements = ['xdotool','xprop','xdg-screensaver'] + requirements = ['xdotool', 'xprop', 'xdg-screensaver'] missing = [] for tool in requirements: if not bumblebee.util.which(tool): @@ -56,27 +56,27 @@ class Module(bumblebee.engine.Module): self._xid = self._get_i3bar_xid() if self._xid is None: return - + pid = os.fork() if pid == 0: os.setsid() bumblebee.util.execute("xdg-screensaver suspend {}".format(self._xid)) os._exit(0) else: - os.waitpid(pid,0) - + os.waitpid(pid, 0) + def _resume_screensaver(self): for process in psutil.process_iter(): - if process.cmdline() == [bumblebee.util.which('xprop'),'-id',str(self._xid),'-spy']: + if process.cmdline() == [bumblebee.util.which('xprop'), '-id', str(self._xid), '-spy']: pid = process.pid - os.kill(pid,9) + os.kill(pid, 9) - def state(self, widget): + def state(self, _): if self._active: return "activated" return "deactivated" - def _toggle(self, event): + def _toggle(self, _): missing = self._check_requirements() if missing: logging.warning("Could not run caffeine - missing {}!".format(", ".join(missing))) From 43b29eedd240c7aebfefc535381c8256b8f87340 Mon Sep 17 00:00:00 2001 From: mw Date: Mon, 2 Sep 2019 16:53:10 +0200 Subject: [PATCH 10/37] Fix pylint logging warning, exclude W0212 --- bumblebee/modules/caffeine.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index 935cfb2..b0e7d57 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -1,4 +1,4 @@ -#pylint: disable=C0111,R0903 +#pylint: disable=C0111,R0903,W0212 """Enable/disable automatic screen locking. @@ -40,7 +40,7 @@ class Module(bumblebee.engine.Module): xid = bumblebee.util.execute("xdotool search --class \"i3bar\"").partition('\n')[0].strip() if xid.isdigit(): return xid - logging.info("Module caffeine: xdotool couldn't get X window ID of \"i3bar\".") + logging.warning("Module caffeine: xdotool couldn't get X window ID of \"i3bar\".") return None def _notify(self): @@ -79,7 +79,7 @@ class Module(bumblebee.engine.Module): def _toggle(self, _): missing = self._check_requirements() if missing: - logging.warning("Could not run caffeine - missing {}!".format(", ".join(missing))) + logging.warning('Could not run caffeine - missing %s!', ", ".join(missing)) return self._active = not self._active From 43988db4cc07a850b5f0d4aebfbde6ab9308e706 Mon Sep 17 00:00:00 2001 From: mw Date: Mon, 2 Sep 2019 17:26:01 +0200 Subject: [PATCH 11/37] Make sure state is reverted in case of error --- bumblebee/modules/caffeine.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index b0e7d57..86de530 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -55,7 +55,7 @@ class Module(bumblebee.engine.Module): def _suspend_screensaver(self): self._xid = self._get_i3bar_xid() if self._xid is None: - return + return False pid = os.fork() if pid == 0: @@ -64,12 +64,16 @@ class Module(bumblebee.engine.Module): os._exit(0) else: os.waitpid(pid, 0) + return True def _resume_screensaver(self): for process in psutil.process_iter(): if process.cmdline() == [bumblebee.util.which('xprop'), '-id', str(self._xid), '-spy']: - pid = process.pid - os.kill(pid, 9) + try: + os.kill(process.pid, 9) + except OSError: + return False + return True def state(self, _): if self._active: @@ -84,9 +88,13 @@ class Module(bumblebee.engine.Module): self._active = not self._active if self._active: - self._suspend_screensaver() + success = self._suspend_screensaver() else: - self._resume_screensaver() - self._notify() + success = self._resume_screensaver() + + if success: + self._notify() + else: + self._active = not self._active # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From 56c3b77d0e9d17d00f667d4fe6211b09262aafac Mon Sep 17 00:00:00 2001 From: adario7 <24436551+adario7@users.noreply.github.com> Date: Tue, 3 Sep 2019 14:24:02 +0200 Subject: [PATCH 12/37] put back the method all_modules even if no longer used by `_read_aliases` it is still needed in other parts of the program. --- bumblebee/engine.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bumblebee/engine.py b/bumblebee/engine.py index 07d32d6..fa54899 100644 --- a/bumblebee/engine.py +++ b/bumblebee/engine.py @@ -16,6 +16,16 @@ try: except ImportError: from configparser import RawConfigParser +def all_modules(): + """Return a list of available modules""" + result = [] + path = os.path.dirname(bumblebee.modules.__file__) + for mod in [name for _, name, _ in pkgutil.iter_modules([path])]: + result.append({ + "name": mod + }) + return result + class Module(object): """Module instance base class From ac418caa59b9299cd7003fa37cf240700de61265 Mon Sep 17 00:00:00 2001 From: mw Date: Thu, 5 Sep 2019 20:19:20 +0200 Subject: [PATCH 13/37] Add test cases --- tests/modules/test_caffeine.py | 63 ++++++++++++++++------------------ 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/tests/modules/test_caffeine.py b/tests/modules/test_caffeine.py index e95e2ac..ad0cf00 100644 --- a/tests/modules/test_caffeine.py +++ b/tests/modules/test_caffeine.py @@ -1,17 +1,9 @@ # pylint: disable=C0103,C0111 -import json import unittest -import mock - -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - +from mock import patch import tests.mocks as mocks -from bumblebee.config import Config from bumblebee.input import LEFT_MOUSE from bumblebee.modules.caffeine import Module @@ -19,35 +11,40 @@ class TestCaffeineModule(unittest.TestCase): def setUp(self): mocks.setup_test(self, Module) - self.xset_active = " timeout: 0 cycle: 123" - self.xset_inactive = " timeout: 600 cycle: 123" - def tearDown(self): mocks.teardown_test(self) - def test_text(self): - self.assertEquals(self.module.caffeine(self.anyWidget), "") + def test_check_requirements(self): + with patch('bumblebee.util.which', side_effect=['', 'xprop', 'xdg-screensaver']): + self.assertTrue(['xdotool'] == self.module._check_requirements()) - def test_active(self): - self.popen.mock.communicate.return_value = (self.xset_active, None) - self.assertTrue(not "deactivated" in self.module.state(self.anyWidget)) - self.assertTrue("activated" in self.module.state(self.anyWidget)) + def test_get_i3bar_xid_returns_digit(self): + self.popen.mock.communicate.return_value = ("8388614", None) + self.assertTrue(self.module._get_i3bar_xid().isdigit()) - def test_inactive(self): - self.popen.mock.communicate.return_value = (self.xset_inactive, None) - self.assertTrue("deactivated" in self.module.state(self.anyWidget)) - self.popen.mock.communicate.return_value = ("no text", None) - self.assertTrue("deactivated" in self.module.state(self.anyWidget)) + def test_get_i3bar_xid_returns_error_string(self): + self.popen.mock.communicate.return_value = ("Some error message", None) + self.assertTrue(self.module._get_i3bar_xid() is None) - def test_toggle(self): - self.popen.mock.communicate.return_value = (self.xset_active, None) - mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) - self.popen.assert_call("xset s default") - self.popen.assert_call("notify-send \"Out of coffee\"") - - self.popen.mock.communicate.return_value = (self.xset_inactive, None) - mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) - self.popen.assert_call("xset s off") - self.popen.assert_call("notify-send \"Consuming caffeine\"") + def test_get_i3bar_xid_returns_empty_string(self): + self.popen.mock.communicate.return_value = ("", None) + self.assertTrue(self.module._get_i3bar_xid() is None) + + def test_suspend_screensaver_success(self): + with patch.object(self.module, '_get_i3bar_xid', return_value=8388614): + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.assertTrue(self.module._suspend_screensaver() is True) + + def test_suspend_screensaver_fail(self): + with patch.object(self.module, '_get_i3bar_xid', return_value=None): + self.module._active = False + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.assertTrue(self.module._suspend_screensaver() is False) + + def test_resume_screensaver(self): + with patch.object(self.module, '_check_requirements', return_value=[]): + self.module._active = True + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.assertTrue(self.module._resume_screensaver() is True) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From a5ef86364706038e1bb2178820eaa915abcfd581 Mon Sep 17 00:00:00 2001 From: mw Date: Thu, 5 Sep 2019 20:48:19 +0200 Subject: [PATCH 14/37] Rewrite killing of xprop --- bumblebee/modules/caffeine.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index 86de530..a90a7d7 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -67,13 +67,17 @@ class Module(bumblebee.engine.Module): return True def _resume_screensaver(self): + pids = [] + success = True for process in psutil.process_iter(): if process.cmdline() == [bumblebee.util.which('xprop'), '-id', str(self._xid), '-spy']: - try: - os.kill(process.pid, 9) - except OSError: - return False - return True + pids.append(process.pid) + for pid in pids: + try: + os.kill(process.pid, 9) + except OSError: + success = False + return success def state(self, _): if self._active: From f19cf652cb7b9363fb74eada932aa0a059f98d08 Mon Sep 17 00:00:00 2001 From: mw Date: Thu, 5 Sep 2019 21:08:05 +0200 Subject: [PATCH 15/37] Try to make codeclimate happy --- bumblebee/modules/caffeine.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index a90a7d7..1f39a9e 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -69,8 +69,9 @@ class Module(bumblebee.engine.Module): def _resume_screensaver(self): pids = [] success = True + xprop_path = bumblebee.util.which('xprop') for process in psutil.process_iter(): - if process.cmdline() == [bumblebee.util.which('xprop'), '-id', str(self._xid), '-spy']: + if process.cmdline() == [xprop_path, '-id', str(self._xid), '-spy']: pids.append(process.pid) for pid in pids: try: From ed59823ac3fbfe8c8710b858219599b93c8e8a9a Mon Sep 17 00:00:00 2001 From: mw Date: Thu, 5 Sep 2019 21:57:32 +0200 Subject: [PATCH 16/37] Fix _resume_screensaver --- bumblebee/modules/caffeine.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py index 1f39a9e..ed9633c 100644 --- a/bumblebee/modules/caffeine.py +++ b/bumblebee/modules/caffeine.py @@ -67,15 +67,12 @@ class Module(bumblebee.engine.Module): return True def _resume_screensaver(self): - pids = [] success = True xprop_path = bumblebee.util.which('xprop') - for process in psutil.process_iter(): - if process.cmdline() == [xprop_path, '-id', str(self._xid), '-spy']: - pids.append(process.pid) + pids = [ p.pid for p in psutil.process_iter() if p.cmdline() == [xprop_path, '-id', str(self._xid), '-spy'] ] for pid in pids: try: - os.kill(process.pid, 9) + os.kill(pid, 9) except OSError: success = False return success From a8b13860108f5c8f74a9a103dfa8c671385e389c Mon Sep 17 00:00:00 2001 From: Frank Scherrer Date: Fri, 6 Sep 2019 17:37:15 +0200 Subject: [PATCH 17/37] add firefox-dark-powerline Theme --- themes/firefox-dark-powerline.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 themes/firefox-dark-powerline.json diff --git a/themes/firefox-dark-powerline.json b/themes/firefox-dark-powerline.json new file mode 100644 index 0000000..4def35a --- /dev/null +++ b/themes/firefox-dark-powerline.json @@ -0,0 +1,30 @@ +{ + "icons": [ "awesome-fonts" ], + "defaults": { + "separator-block-width": 0, + "warning": { + "fg": "#002b36", + "bg": "#b58900" + }, + "critical": { + "fg": "#002b36", + "bg": "#dc322f" + } + }, + "cycle": [ + { "fg": "#B1B1B3", "bg": "#474749" }, + { "fg": "#BEBEBE", "bg": "#323234" } + ], + "dnf": { + "good": { + "fg": "#002b36", + "bg": "#859900" + } + }, + "pacman": { + "good": { + "fg": "#002b36", + "bg": "#859900" + } + } +} From c09ce71fcec766357c6fcd7c4debfea9b7c43a1b Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Mon, 9 Sep 2019 20:17:03 +0200 Subject: [PATCH 18/37] [core] Replace aliases logic with simple dict Since aliases are so rarely used, just hardcode the list of aliases inside the engine for now. This combines the advantage of the speedup of the previous implementation with the robustness of the first implementation. As trade-off, extensibility suffers, but given the small number of aliases, that's OK for now. fixes #437 --- bumblebee/engine.py | 32 +++++++++------------------- bumblebee/modules/aliases/datetime | 2 -- bumblebee/modules/aliases/datetimetz | 2 -- bumblebee/modules/aliases/pulseaudio | 2 -- bumblebee/modules/aliases/test | 1 - 5 files changed, 10 insertions(+), 29 deletions(-) delete mode 100644 bumblebee/modules/aliases/datetime delete mode 100644 bumblebee/modules/aliases/datetimetz delete mode 100644 bumblebee/modules/aliases/pulseaudio delete mode 100644 bumblebee/modules/aliases/test diff --git a/bumblebee/engine.py b/bumblebee/engine.py index fa54899..600945e 100644 --- a/bumblebee/engine.py +++ b/bumblebee/engine.py @@ -142,7 +142,7 @@ class Engine(object): self._running = True self._modules = [] self.input = inp - self._aliases = self._read_aliases() + self._aliases = self._aliases() self.load_modules(config.modules()) self._current_module = None self._theme = theme @@ -230,27 +230,15 @@ class Engine(object): self.input.register_callback(obj=module, button=button["id"], cmd=module.parameter(button["name"])) - def _read_aliases(self): - """Retruns a dictionary that maps every alias to its real module name""" - result = {} - m_path = os.path.abspath(bumblebee.modules.__file__) - m_path = os.path.dirname(m_path) - ALIASES_PATH = m_path + "/aliases/" - for name in os.listdir(ALIASES_PATH): - try: - # listdir does not retur full paths - f_path = ALIASES_PATH + name - # skip any directory - if not os.path.isfile(f_path): continue - with open(f_path) as f: - for alias in f.readlines(): - alias = alias.strip() - # skip empty lines - if len(alias) == 0: continue - result[alias] = name - except Exception as error: - log.warning("failed to load aliases of {}: {}".format(name, str(error))) - return result + def _aliases(self): + return { + 'date': 'datetime', + 'time': 'datetime', + 'datetz': 'datetimetz', + 'timetz': 'datetimetz', + 'pasink': 'pulseaudio', + 'pasource': 'pulseaudio', + } def _load_module(self, module_name, config_name=None): """Load specified module and return it as object""" diff --git a/bumblebee/modules/aliases/datetime b/bumblebee/modules/aliases/datetime deleted file mode 100644 index 8e43d6a..0000000 --- a/bumblebee/modules/aliases/datetime +++ /dev/null @@ -1,2 +0,0 @@ -date -time diff --git a/bumblebee/modules/aliases/datetimetz b/bumblebee/modules/aliases/datetimetz deleted file mode 100644 index 08f6d17..0000000 --- a/bumblebee/modules/aliases/datetimetz +++ /dev/null @@ -1,2 +0,0 @@ -datetz -timetz diff --git a/bumblebee/modules/aliases/pulseaudio b/bumblebee/modules/aliases/pulseaudio deleted file mode 100644 index 9bcee50..0000000 --- a/bumblebee/modules/aliases/pulseaudio +++ /dev/null @@ -1,2 +0,0 @@ -pasink -pasource diff --git a/bumblebee/modules/aliases/test b/bumblebee/modules/aliases/test deleted file mode 100644 index dc52a4f..0000000 --- a/bumblebee/modules/aliases/test +++ /dev/null @@ -1 +0,0 @@ -test-alias From ef7cbd5c2e8c350e7f4e9546f885ef696f1e4cd1 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Mon, 9 Sep 2019 21:03:05 +0200 Subject: [PATCH 19/37] [core] Add alias test --- bumblebee/engine.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bumblebee/engine.py b/bumblebee/engine.py index 600945e..1ded7c5 100644 --- a/bumblebee/engine.py +++ b/bumblebee/engine.py @@ -238,6 +238,7 @@ class Engine(object): 'timetz': 'datetimetz', 'pasink': 'pulseaudio', 'pasource': 'pulseaudio', + 'test-alias': 'test', } def _load_module(self, module_name, config_name=None): From b7b0faf61353428bad9bafa45261d7f1b04ef6df Mon Sep 17 00:00:00 2001 From: WORD559 Date: Mon, 23 Sep 2019 15:12:23 +0100 Subject: [PATCH 20/37] Add support for nowplaying-tf, which provides better format strings This is added with the tf_format config, which will override format if it is set. This makes the call "deadbeef --nowplaying-tf " and displays the output directly. This output should now also support unicode, regardless of Python version. There is also the tf_format_if_stopped, a boolean config that configures whether or not the tf_format output should be displayed even if deadbeef isn't playing anything. --- bumblebee/modules/deadbeef.py | 126 ++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 38 deletions(-) diff --git a/bumblebee/modules/deadbeef.py b/bumblebee/modules/deadbeef.py index 2dce7fd..8616fa4 100644 --- a/bumblebee/modules/deadbeef.py +++ b/bumblebee/modules/deadbeef.py @@ -1,22 +1,38 @@ # pylint: disable=C0111,R0903 -"""Displays the current song being played in DeaDBeeF and -provides some media control bindings. -Left click toggles pause, scroll up skips the current song, -scroll down returns to the previous song. +"""Displays the current song being played in DeaDBeeF and provides +some media control bindings. +Left click toggles pause, scroll up skips the current song, scroll +down returns to the previous song. + Requires the following library: * subprocess Parameters: - * deadbeef.format: Format string (defaults to "{artist} - {title}") - Available values are: {artist}, {title}, {album}, {length}, - {trackno}, {year}, {comment}, - {copyright}, {time} - * deadbeef.previous: Change binding for previous song (default is left click) - * deadbeef.next: Change binding for next song (default is right click) - * deadbeef.pause: Change binding for toggling pause (default is middle click) + * deadbeef.format: Format string (defaults to "{artist} - {title}") + Available values are: {artist}, {title}, {album}, {length}, + {trackno}, {year}, {comment}, + {copyright}, {time} + This is deprecated, but much simpler. + * deadbeef.tf_format: A foobar2000 title formatting-style format string. + These can be much more sophisticated than the standard + format strings. This is off by default, but specifying + any tf_format will enable it. If both deadbeef.format + and deadbeef.tf_format are specified, deadbeef.tf_format + takes priority. + * deadbeef.tf_format_if_stopped: Controls whether or not the tf_format format + string should be displayed even if no song is paused or + playing. This could be useful if you want to implement + your own stop strings with the built in logic. Any non- + null value will enable this (by default the module will + hide itself when the player is stopped). + * deadbeef.previous: Change binding for previous song (default is left click) + * deadbeef.next: Change binding for next song (default is right click) + * deadbeef.pause: Change binding for toggling pause (default is middle click) Available options for deadbeef.previous, deadbeef.next and deadbeef.pause are: LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN + """ +import sys import bumblebee.input import bumblebee.output @@ -34,54 +50,88 @@ class Module(bumblebee.engine.Module): super(Module, self).__init__(engine, config, bumblebee.output.Widget(full_text=self.deadbeef) ) - buttons = {"LEFT_CLICK":bumblebee.input.LEFT_MOUSE, - "RIGHT_CLICK":bumblebee.input.RIGHT_MOUSE, - "MIDDLE_CLICK":bumblebee.input.MIDDLE_MOUSE, - "SCROLL_UP":bumblebee.input.WHEEL_UP, - "SCROLL_DOWN":bumblebee.input.WHEEL_DOWN, + buttons = {"LEFT_CLICK": bumblebee.input.LEFT_MOUSE, + "RIGHT_CLICK": bumblebee.input.RIGHT_MOUSE, + "MIDDLE_CLICK": bumblebee.input.MIDDLE_MOUSE, + "SCROLL_UP": bumblebee.input.WHEEL_UP, + "SCROLL_DOWN": bumblebee.input.WHEEL_DOWN, } self._song = "" self._format = self.parameter("format", "{artist} - {title}") + self._tf_format = self.parameter("tf_format", "") + self._show_tf_when_stopped = bool(self.parameter("tf_format_if_stopped", "")) prev_button = self.parameter("previous", "LEFT_CLICK") next_button = self.parameter("next", "RIGHT_CLICK") pause_button = self.parameter("pause", "MIDDLE_CLICK") - self.now_playing = ["deadbeef","--nowplaying","%a;%t;%b;%l;%n;%y;%c;%r;%e"] + self.now_playing = ["deadbeef", "--nowplaying", "%a;%t;%b;%l;%n;%y;%c;%r;%e"] + self.now_playing_tf = ["deadbeef", "--nowplaying-tf", ""] cmd = "deadbeef " - + engine.input.register_callback(self, button=buttons[prev_button], - cmd=cmd + "--prev") + cmd=cmd + "--prev") engine.input.register_callback(self, button=buttons[next_button], - cmd=cmd + "--next") + cmd=cmd + "--next") engine.input.register_callback(self, button=buttons[pause_button], - cmd=cmd + "--play-pause") + cmd=cmd + "--play-pause") @scrollable def deadbeef(self, widget): - return str(self._song) + return self.string_song def hidden(self): - return str(self._song) == "" + return self.string_song == "" def update(self, widgets): try: - deadbeef = subprocess.Popen(self.now_playing,stdin=subprocess.PIPE,stdout=subprocess.PIPE) - data = deadbeef.communicate()[0] - if data == "nothing": - self._song = "" - else: - data = data.split(";") - self._song = self._format.format(artist=data[0], - title=data[1], - album=data[2], - length=data[3], - trackno=data[4], - year=data[5], - comment=data[6], - copyright=data[7], - time=data[8]) + if self._tf_format == "": # no tf format set, use the old style + return self.update_standard(widgets) + return self.update_tf(widgets) except Exception: + self._song = "error" + + def update_tf(self, widgets): + if not self._show_tf_when_stopped: + ## check if the player is paused or playing + self.now_playing_tf[-1] = "%isplaying%%ispaused%" + data = read_process(self.now_playing_tf) + if data == "": + self._song = "" + return + ## perform the actual query -- these can be much more sophisticated + self.now_playing_tf[-1] = self._tf_format + data = read_process(self.now_playing_tf) + self._song = data + + def update_standard(self, widgets): + data = read_process(self.now_playing) + if data == "nothing": self._song = "" + else: + data = data.split(";") + self._song = self._format.format(artist=data[0], + title=data[1], + album=data[2], + length=data[3], + trackno=data[4], + year=data[5], + comment=data[6], + copyright=data[7], + time=data[8]) + + @property + def string_song(self): + """\ +Returns the current song as a string, either as a unicode() (Python < +3) or a regular str() (Python >= 3) + """ + if sys.version_info.major < 3: + return unicode(self._song) + return str(self._song) + +def read_process(command): + proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + return proc.stdout.read() # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From e42037ce6fad2786570295cfe5967f22eee210cf Mon Sep 17 00:00:00 2001 From: WORD559 Date: Mon, 23 Sep 2019 16:15:53 +0100 Subject: [PATCH 21/37] Check deadbeef is running and check if stopped as part of the query deadbeef is checked to ensure it's running before doing anything, otherwise a lot of useless data is pulled in, and the if statement to check whether or not the player is stopped is now integrated into the query at startup instead of running a separate query every time. --- bumblebee/modules/deadbeef.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/bumblebee/modules/deadbeef.py b/bumblebee/modules/deadbeef.py index 8616fa4..5b74933 100644 --- a/bumblebee/modules/deadbeef.py +++ b/bumblebee/modules/deadbeef.py @@ -76,6 +76,13 @@ class Module(bumblebee.engine.Module): engine.input.register_callback(self, button=buttons[pause_button], cmd=cmd + "--play-pause") + # modify the tf_format if we don't want it to show on stop + # this adds conditions to the query itself, rather than + # polling to see if deadbeef is running + # doing this reduces the number of calls we have to make + if self._tf_format and not self._show_tf_when_stopped: + self._tf_format = "$if($or(%isplaying%,%ispaused%),{query})".format(query=self._tf_format) + @scrollable def deadbeef(self, widget): return self.string_song @@ -92,13 +99,12 @@ class Module(bumblebee.engine.Module): self._song = "error" def update_tf(self, widgets): - if not self._show_tf_when_stopped: - ## check if the player is paused or playing - self.now_playing_tf[-1] = "%isplaying%%ispaused%" - data = read_process(self.now_playing_tf) - if data == "": - self._song = "" - return + ## ensure that deadbeef is actually running + ## easiest way to do this is to check --nowplaying for + ## the string "nothing" + if read_process(self.now_playing) == "nothing": + self._song = "" + return ## perform the actual query -- these can be much more sophisticated self.now_playing_tf[-1] = self._tf_format data = read_process(self.now_playing_tf) From ed4ac41e52ab986b4f9c9f6604deca011b1d267e Mon Sep 17 00:00:00 2001 From: Brian Lechthaler Date: Tue, 24 Sep 2019 20:57:21 -0700 Subject: [PATCH 22/37] Add Docker Support Added Dockerfile for easier deployment. Does not contain 'CMD pip3 install $packages' directive for module dependency fetching. --- Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8d95d41 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +#start with a Python 3.x container +FROM python:3 + +#grab repository from github +RUN git clone --recursive https://github.com/tobi-wan-kenobi/bumblebee-status.git /var/bumblebee-status +#run the statusline with no modules or themes specified +CMD python3 /var/bumblebee-status/bumblebee-status From 71d0fa6900f1dd0e65f38fb9c4940ca54b31b14a Mon Sep 17 00:00:00 2001 From: Victor Franzi Date: Wed, 25 Sep 2019 21:24:41 +0200 Subject: [PATCH 23/37] add vault default text option --- bumblebee/modules/vault.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee/modules/vault.py b/bumblebee/modules/vault.py index 9e1535e..6505dc2 100644 --- a/bumblebee/modules/vault.py +++ b/bumblebee/modules/vault.py @@ -60,7 +60,7 @@ class Module(bumblebee.engine.Module): def _reset(self): self._timer = None - self._text = "" + self._text = str(self.parameter("text", "")) def _callback(self, secret_name): secret_name = secret_name.replace(self._path, "") # remove common path From 3a2fcb1abfb79ddb3dd943f09f2bf203f335b6c4 Mon Sep 17 00:00:00 2001 From: Alex Boag-Munroe Date: Thu, 26 Sep 2019 00:25:18 +0100 Subject: [PATCH 24/37] Support parsing zpool output for ZFS >=0.8.0 Query /sys/module/zfs/version for ZFS version and account for the additional CKPOINT field in ZFS 0.8.0 and higher. --- bumblebee/modules/zpool.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bumblebee/modules/zpool.py b/bumblebee/modules/zpool.py index 701c1c1..5b6092a 100644 --- a/bumblebee/modules/zpool.py +++ b/bumblebee/modules/zpool.py @@ -25,6 +25,7 @@ Be aware of security implications of doing this! """ import time +from pkg_resources import parse_version import bumblebee.engine from bumblebee.util import execute, bytefmt, asbool @@ -64,6 +65,8 @@ class Module(bumblebee.engine.Module): def _update_widgets(self, widgets): # zpool list -H: List all zpools, use script mode (no headers and tabs as separators). + with open('/sys/module/zfs/version', 'r') as zfs_mod_version: + zfs_version = zfs_mod_version.readline().rstrip().split('-')[0] raw_zpools = execute(('sudo ' if self._usesudo else '') + 'zpool list -H').split('\n') for widget in widgets: @@ -71,8 +74,11 @@ class Module(bumblebee.engine.Module): for raw_zpool in raw_zpools: try: - # Ignored fields (assigned to _) are "expandsz" and "altroot" - name, size, alloc, free, _, frag, cap, dedup, health, _ = raw_zpool.split('\t') + # Ignored fields (assigned to _) are "expandsz" and "altroot", also "ckpoint" in ZFS 0.8.0+ + if parse_version(zfs_version) < parse_version("0.8.0"): + name, size, alloc, free, _, frag, cap, dedup, health, _ = raw_zpool.split('\t') + else: + name, size, alloc, free, _, _, frag, cap, dedup, health, _ = raw_zpool.split('\t') cap = cap.rstrip('%') percentuse=int(cap) percentfree=100-percentuse From a3ceada1298af60710fbaae06f09671951180dcb Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 26 Sep 2019 19:39:29 +0200 Subject: [PATCH 25/37] [module] New module pomodoro timer --- bumblebee/modules/pomodoro.py | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 bumblebee/modules/pomodoro.py diff --git a/bumblebee/modules/pomodoro.py b/bumblebee/modules/pomodoro.py new file mode 100644 index 0000000..92f9dcf --- /dev/null +++ b/bumblebee/modules/pomodoro.py @@ -0,0 +1,67 @@ +# pylint: disable=C0111,R0903 + +"""Display and run a Pomodoro timer. +Left click to start timer, left click again to pause. +Right click will cancel the timer. +""" + +import datetime + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + widgets = bumblebee.output.Widget(full_text=self.text) + self.remaining_time = datetime.timedelta(minutes=25) + self.remaining_time_str = "{}min{}s ".format(int((self.remaining_time.seconds / 60)), + round((self.remaining_time.seconds/60) % 1*60)) + self.time = None + self.pomodoro = { "state":"OFF", "type": "n/a"} + self._text = self.remaining_time_str + self.pomodoro["type"] + " " + self.pomodoro["state"] + super(Module, self).__init__(engine, config, widgets) + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, + cmd=self.timer_play_pause) + engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, + cmd=self.timer_reset) + + def text(self, widget): + return "{}".format(self._text) + + def update(self, widget): + if self.pomodoro["state"] == "ON": + timediff = (datetime.datetime.now() - self.time) + if timediff.seconds >= 0: + self.remaining_time -= timediff + self.time = datetime.datetime.now() + + if self.remaining_time.seconds <= 0: + if self.pomodoro["type"] == "WORK": + self.pomodoro["type"] = "PLAY" + self.remaining_time = datetime.timedelta(minutes=25) + elif self.pomodoro["type"] == "PLAY": + self.pomodoro["type"] = "WORK" + self.remaining_time = datetime.timedelta(minutes=5) + + self.remaining_time_str = "{}min{}s ".format(int((self.remaining_time.seconds / 60)), + round((self.remaining_time.seconds / 60) % 1 * 60)) + self._text = self.remaining_time_str + self.pomodoro["type"] + " " + self.pomodoro["state"] + + def timer_play_pause(self, widget): + if self.pomodoro["state"] == "OFF": + self.pomodoro = {"state": "ON", "type": "WORK"} + self.remaining_time = datetime.timedelta(minutes=25) + self.time = datetime.datetime.now() + elif self.pomodoro["state"] == "ON": + self.pomodoro["state"] = "PAUSED" + self.remaining_time -= (datetime.datetime.now() - self.time) + self.time = datetime.datetime.now() + elif self.pomodoro["state"] == "PAUSED": + self.pomodoro["state"] = "ON" + self.time = datetime.datetime.now() + + def timer_reset(self, widget): + if self.pomodoro["state"] == "ON" or self.pomodoro["state"] == "PAUSED": + self.pomodoro = {"state":"OFF", "type": "n/a" } + self.remaining_time = datetime.timedelta(minutes=25) \ No newline at end of file From 4a5ae622aac385eced044b60ea1bfc39d7f1bfc0 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Wed, 25 Sep 2019 19:00:08 +0200 Subject: [PATCH 26/37] [core] Ignore unknown UTF-8 characters --- bumblebee/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee/util.py b/bumblebee/util.py index f3545ac..fdd3af5 100644 --- a/bumblebee/util.py +++ b/bumblebee/util.py @@ -31,7 +31,7 @@ def execute(cmd, wait=True): raise RuntimeError("{} exited with {}".format(cmd, proc.returncode)) if hasattr(out, "decode"): - rv = out.decode("utf-8") + rv = out.decode("utf-8", "ignore") else: rv = out From fd990eb4fde0e206ca7898681395e6b966c81f83 Mon Sep 17 00:00:00 2001 From: Alex Boag-Munroe Date: Thu, 26 Sep 2019 23:22:25 +0100 Subject: [PATCH 27/37] Don't crash when ZFS version info is unavailable Catch the FileNotFoundError and stub the zfs version for the script to not crash. --- bumblebee/modules/zpool.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bumblebee/modules/zpool.py b/bumblebee/modules/zpool.py index 5b6092a..b8569df 100644 --- a/bumblebee/modules/zpool.py +++ b/bumblebee/modules/zpool.py @@ -65,8 +65,13 @@ class Module(bumblebee.engine.Module): def _update_widgets(self, widgets): # zpool list -H: List all zpools, use script mode (no headers and tabs as separators). - with open('/sys/module/zfs/version', 'r') as zfs_mod_version: - zfs_version = zfs_mod_version.readline().rstrip().split('-')[0] + try: + with open('/sys/module/zfs/version', 'r') as zfs_mod_version: + zfs_version = zfs_mod_version.readline().rstrip().split('-')[0] + except FileNotFoundError: + # ZFS isn't installed or the module isn't loaded, stub the version + zfs_version = "0.0.0" + raw_zpools = execute(('sudo ' if self._usesudo else '') + 'zpool list -H').split('\n') for widget in widgets: From e44eea3318d62c956d36aef115ee4786848418f4 Mon Sep 17 00:00:00 2001 From: Alex Boag-Munroe Date: Thu, 26 Sep 2019 23:30:06 +0100 Subject: [PATCH 28/37] Log when unable to ascertain ZFS version Use established logging strategy to emit an error log when ZFS version information cannot be obtained. --- bumblebee/modules/zpool.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bumblebee/modules/zpool.py b/bumblebee/modules/zpool.py index b8569df..e5f42c6 100644 --- a/bumblebee/modules/zpool.py +++ b/bumblebee/modules/zpool.py @@ -25,10 +25,12 @@ Be aware of security implications of doing this! """ import time +import logging from pkg_resources import parse_version import bumblebee.engine from bumblebee.util import execute, bytefmt, asbool +log = logging.getLogger(__name__) class Module(bumblebee.engine.Module): def __init__(self, engine, config): @@ -64,13 +66,15 @@ class Module(bumblebee.engine.Module): return state def _update_widgets(self, widgets): + zfs_version_path = "/sys/module/zfs/version" # zpool list -H: List all zpools, use script mode (no headers and tabs as separators). try: - with open('/sys/module/zfs/version', 'r') as zfs_mod_version: + with open(zfs_version_path, 'r') as zfs_mod_version: zfs_version = zfs_mod_version.readline().rstrip().split('-')[0] except FileNotFoundError: # ZFS isn't installed or the module isn't loaded, stub the version zfs_version = "0.0.0" + logging.error("ZFS version information not found at {}, check the module is loaded.".format(zfs_version_path)) raw_zpools = execute(('sudo ' if self._usesudo else '') + 'zpool list -H').split('\n') From ffac34a51c8393c42226f3f1deacffb7db26de6f Mon Sep 17 00:00:00 2001 From: Alex Boag-Munroe Date: Thu, 26 Sep 2019 23:40:41 +0100 Subject: [PATCH 29/37] Catch IOError for backwards compatability FileNotFoundError is not in Python 2, catch IOError instead. --- bumblebee/modules/zpool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee/modules/zpool.py b/bumblebee/modules/zpool.py index e5f42c6..1f3e897 100644 --- a/bumblebee/modules/zpool.py +++ b/bumblebee/modules/zpool.py @@ -71,7 +71,7 @@ class Module(bumblebee.engine.Module): try: with open(zfs_version_path, 'r') as zfs_mod_version: zfs_version = zfs_mod_version.readline().rstrip().split('-')[0] - except FileNotFoundError: + except IOError: # ZFS isn't installed or the module isn't loaded, stub the version zfs_version = "0.0.0" logging.error("ZFS version information not found at {}, check the module is loaded.".format(zfs_version_path)) From 43194a8244612c8d8df569272a0a59fe27e68427 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Fri, 27 Sep 2019 19:33:36 +0200 Subject: [PATCH 30/37] [README] bump module# --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 636e733..4c6d258 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Test Coverage](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/coverage.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage) [![Issue Count](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/issue_count.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status) -**Many, many thanks to all contributors! As of now, 47 of the modules are from various contributors (!), and only 19 from myself.** +**Many, many thanks to all contributors! As of now, 48 of the modules are from various contributors (!), and only 19 from myself.** ![Solarized Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-solarized.png) From 4edae93d923454b0e3e8257858c694a632c324e7 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Fri, 27 Sep 2019 19:33:59 +0200 Subject: [PATCH 31/37] [modules/pomodoro] Change import to fix tests --- bumblebee/modules/pomodoro.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bumblebee/modules/pomodoro.py b/bumblebee/modules/pomodoro.py index 92f9dcf..249ad2e 100644 --- a/bumblebee/modules/pomodoro.py +++ b/bumblebee/modules/pomodoro.py @@ -5,6 +5,7 @@ Left click to start timer, left click again to pause. Right click will cancel the timer. """ +from __future__ import absolute_import import datetime import bumblebee.input @@ -64,4 +65,4 @@ class Module(bumblebee.engine.Module): def timer_reset(self, widget): if self.pomodoro["state"] == "ON" or self.pomodoro["state"] == "PAUSED": self.pomodoro = {"state":"OFF", "type": "n/a" } - self.remaining_time = datetime.timedelta(minutes=25) \ No newline at end of file + self.remaining_time = datetime.timedelta(minutes=25) From 3c1f127fefee423f9cbe9e50c0f8127a2142dd41 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Fri, 27 Sep 2019 19:34:56 +0200 Subject: [PATCH 32/37] [modules/rss] Initialize response --- bumblebee/modules/rss.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bumblebee/modules/rss.py b/bumblebee/modules/rss.py index c2db30b..2a27d27 100644 --- a/bumblebee/modules/rss.py +++ b/bumblebee/modules/rss.py @@ -45,6 +45,7 @@ class Module(bumblebee.engine.Module): # Use BBC newsfeed as demo: self._feeds = self.parameter('feeds', 'https://www.espn.com/espn/rss/news').split(" ") self._feeds_to_update = [] + self._response = "" self._max_title_length = int(self.parameter("length", 60)) From d6072d0eb718f727baaa7727256e34e6625fc9bf Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur Date: Sat, 28 Sep 2019 16:27:26 -0700 Subject: [PATCH 33/37] Spruced up the pomodoro module. Added icons/colors to the pomodoro module and shortened the display text. --- bumblebee/modules/pomodoro.py | 45 +++++++++------- themes/firefox-dark-powerline.json | 14 ++++- themes/greyish-powerline.json | 17 +++++- themes/gruvbox-light.json | 17 +++++- themes/gruvbox-powerline-light.json | 82 +++++++++++++++++------------ themes/gruvbox-powerline.json | 16 +++++- themes/gruvbox.json | 17 +++++- themes/iceberg-dark-powerline.json | 15 ++++++ themes/iceberg-powerline.json | 14 +++++ themes/iceberg-rainbow.json | 15 ++++++ themes/iceberg.json | 15 ++++++ themes/icons/ascii.json | 8 ++- themes/icons/awesome-fonts.json | 5 ++ themes/icons/ionicons.json | 6 +++ themes/onedark-powerline.json | 17 +++++- themes/powerline.json | 17 +++++- themes/sac_red.json | 17 +++++- themes/solarized-dark-awesome.json | 64 +++++++++++++--------- themes/solarized-powerline.json | 16 +++++- themes/solarized.json | 17 +++++- themes/wal-powerline.json | 17 +++++- 21 files changed, 361 insertions(+), 90 deletions(-) diff --git a/bumblebee/modules/pomodoro.py b/bumblebee/modules/pomodoro.py index 249ad2e..0eacc63 100644 --- a/bumblebee/modules/pomodoro.py +++ b/bumblebee/modules/pomodoro.py @@ -7,6 +7,7 @@ Right click will cancel the timer. from __future__ import absolute_import import datetime +from math import ceil import bumblebee.input import bumblebee.output @@ -15,12 +16,13 @@ import bumblebee.engine class Module(bumblebee.engine.Module): def __init__(self, engine, config): widgets = bumblebee.output.Widget(full_text=self.text) - self.remaining_time = datetime.timedelta(minutes=25) - self.remaining_time_str = "{}min{}s ".format(int((self.remaining_time.seconds / 60)), - round((self.remaining_time.seconds/60) % 1*60)) + self._work_period = 25 + self._play_period = 5 + self.remaining_time = datetime.timedelta(minutes=self._work_period) + self.remaining_time_str = "{}m ".format(ceil((self.remaining_time.seconds / 60))) self.time = None - self.pomodoro = { "state":"OFF", "type": "n/a"} - self._text = self.remaining_time_str + self.pomodoro["type"] + " " + self.pomodoro["state"] + self.pomodoro = { "state":"OFF", "type": ""} + self._text = self.remaining_time_str + self.pomodoro["type"] super(Module, self).__init__(engine, config, widgets) engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self.timer_play_pause) @@ -38,21 +40,20 @@ class Module(bumblebee.engine.Module): self.time = datetime.datetime.now() if self.remaining_time.seconds <= 0: - if self.pomodoro["type"] == "WORK": - self.pomodoro["type"] = "PLAY" - self.remaining_time = datetime.timedelta(minutes=25) - elif self.pomodoro["type"] == "PLAY": - self.pomodoro["type"] = "WORK" - self.remaining_time = datetime.timedelta(minutes=5) + if self.pomodoro["type"] == "Work": + self.pomodoro["type"] = "Break" + self.remaining_time = datetime.timedelta(minutes=self._work_period) + elif self.pomodoro["type"] == "Break": + self.pomodoro["type"] = "Work" + self.remaining_time = datetime.timedelta(minutes=self._play_period) - self.remaining_time_str = "{}min{}s ".format(int((self.remaining_time.seconds / 60)), - round((self.remaining_time.seconds / 60) % 1 * 60)) - self._text = self.remaining_time_str + self.pomodoro["type"] + " " + self.pomodoro["state"] + self.remaining_time_str = "{}m ".format(ceil((self.remaining_time.seconds / 60))) + self._text = self.remaining_time_str + self.pomodoro["type"] def timer_play_pause(self, widget): if self.pomodoro["state"] == "OFF": - self.pomodoro = {"state": "ON", "type": "WORK"} - self.remaining_time = datetime.timedelta(minutes=25) + self.pomodoro = {"state": "ON", "type": "Work"} + self.remaining_time = datetime.timedelta(minutes=self._work_period) self.time = datetime.datetime.now() elif self.pomodoro["state"] == "ON": self.pomodoro["state"] = "PAUSED" @@ -64,5 +65,13 @@ class Module(bumblebee.engine.Module): def timer_reset(self, widget): if self.pomodoro["state"] == "ON" or self.pomodoro["state"] == "PAUSED": - self.pomodoro = {"state":"OFF", "type": "n/a" } - self.remaining_time = datetime.timedelta(minutes=25) + self.pomodoro = {"state":"OFF", "type": "" } + self.remaining_time = datetime.timedelta(minutes=self._work_period) + + def state(self, widget): + state = []; + state.append(self.pomodoro["state"].lower()) + if self.pomodoro["state"] == "ON" or self.pomodoro["state"] == "OFF": + state.append(self.pomodoro["type"].lower()) + + return state diff --git a/themes/firefox-dark-powerline.json b/themes/firefox-dark-powerline.json index 4def35a..00e7d95 100644 --- a/themes/firefox-dark-powerline.json +++ b/themes/firefox-dark-powerline.json @@ -26,5 +26,17 @@ "fg": "#002b36", "bg": "#859900" } - } + }, + "pomodoro": { + "paused": { + "fg": "#002b36", + "bg": "#b58900" + }, + "break": { + "fg": "#002b36", + "bg": "#859900" + } + } + + } diff --git a/themes/greyish-powerline.json b/themes/greyish-powerline.json index 1cbaaf6..2e9d9b8 100644 --- a/themes/greyish-powerline.json +++ b/themes/greyish-powerline.json @@ -42,5 +42,20 @@ "fg": "#002b36", "bg": "#859900" } - } + }, + "pomodoro": { + "paused": { + "fg": "#002b36", + "bg": "#b58900" + }, + "work": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "break": { + "fg": "#002b36", + "bg": "#859900" + } + } + } diff --git a/themes/gruvbox-light.json b/themes/gruvbox-light.json index ebb7983..ee4d69e 100644 --- a/themes/gruvbox-light.json +++ b/themes/gruvbox-light.json @@ -54,5 +54,20 @@ "modified": { "bg": "#458588" }, "deleted": { "bg": "#9d0006" }, "new": { "bg": "#b16286" } - } + }, + "pomodoro": { + "paused": { + "fg": "#1d2021", + "bg": "#d79921" + }, + "work": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "break": { + "fg": "#002b36", + "bg": "#859900" + } + } + } diff --git a/themes/gruvbox-powerline-light.json b/themes/gruvbox-powerline-light.json index ba4b441..d515df0 100644 --- a/themes/gruvbox-powerline-light.json +++ b/themes/gruvbox-powerline-light.json @@ -21,38 +21,52 @@ "fg": "#282828", "bg": "#fbf1c7" } - ], - "dnf": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "apt": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "battery": { - "charged": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "AC": { - "fg": "#1d2021", - "bg": "#b8bb26" - } - }, - "bluetooth": { - "ON": { - "fg": "#1d2021", - "bg": "#b8bb26" - } - }, - "git": { - "modified": { "bg": "#458588" }, - "deleted": { "bg": "#9d0006" }, - "new": { "bg": "#b16286" } - } + ], + "dnf": { + "good": { + "fg": "#002b36", + "bg": "#859900" + } + }, + "apt": { + "good": { + "fg": "#002b36", + "bg": "#859900" + } + }, + "battery": { + "charged": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "AC": { + "fg": "#1d2021", + "bg": "#b8bb26" + } + }, + "bluetooth": { + "ON": { + "fg": "#1d2021", + "bg": "#b8bb26" + } + }, + "git": { + "modified": { "bg": "#458588" }, + "deleted": { "bg": "#9d0006" }, + "new": { "bg": "#b16286" } + }, + "pomodoro": { + "paused": { + "fg": "#1d2021", + "bg": "#d79921" + }, + "work": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "break": { + "fg": "#002b36", + "bg": "#859900" + } + } } diff --git a/themes/gruvbox-powerline.json b/themes/gruvbox-powerline.json index 1e0f588..3481dbc 100644 --- a/themes/gruvbox-powerline.json +++ b/themes/gruvbox-powerline.json @@ -54,5 +54,19 @@ "modified": { "bg": "#458588" }, "deleted": { "bg": "#9d0006" }, "new": { "bg": "#b16286" } - } + }, + "pomodoro": { + "paused": { + "fg": "#1d2021", + "bg": "#d79921" + }, + "work": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "break": { + "fg": "#002b36", + "bg": "#859900" + } + } } diff --git a/themes/gruvbox.json b/themes/gruvbox.json index 8ef2813..ab71814 100644 --- a/themes/gruvbox.json +++ b/themes/gruvbox.json @@ -54,5 +54,20 @@ "modified": { "bg": "#458588" }, "deleted": { "bg": "#9d0006" }, "new": { "bg": "#b16286" } - } + }, + "pomodoro": { + "paused": { + "fg": "#1d2021", + "bg": "#d79921" + }, + "work": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "break": { + "fg": "#002b36", + "bg": "#859900" + } + } + } diff --git a/themes/iceberg-dark-powerline.json b/themes/iceberg-dark-powerline.json index 81af90e..1b13b84 100644 --- a/themes/iceberg-dark-powerline.json +++ b/themes/iceberg-dark-powerline.json @@ -47,5 +47,20 @@ "fg": "#0f1117", "bg": "#84a0c6" } + }, + "pomodoro": { + "paused": { + "fg": "#0f1117", + "bg": "#e2a478" + }, + "work": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "break": { + "fg": "#b4be82", + "bg": "#161821" + } } + } diff --git a/themes/iceberg-powerline.json b/themes/iceberg-powerline.json index f6af51b..c7dca23 100644 --- a/themes/iceberg-powerline.json +++ b/themes/iceberg-powerline.json @@ -46,5 +46,19 @@ "fg": "#0f1117", "bg": "#84a0c6" } + }, + "pomodoro": { + "paused": { + "fg": "#0f1117", + "bg": "#e2a478" + }, + "work": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "break": { + "fg": "#89b8c2", + "bg": "#161821" + } } } diff --git a/themes/iceberg-rainbow.json b/themes/iceberg-rainbow.json index e8cef97..9c56af6 100644 --- a/themes/iceberg-rainbow.json +++ b/themes/iceberg-rainbow.json @@ -46,5 +46,20 @@ "fg": "#89b8c2", "bg": "#161821" } + }, + "pomodoro": { + "paused": { + "fg": "#e2a478", + "bg": "#c6c8d1" + }, + "work": { + "fg": "#89b8c2", + "bg": "#161821" + }, + "break": { + "fg": "#b4be82", + "bg": "#161821" + } } + } diff --git a/themes/iceberg.json b/themes/iceberg.json index 5739b83..9943eb4 100644 --- a/themes/iceberg.json +++ b/themes/iceberg.json @@ -40,5 +40,20 @@ "fg": "#89b8c2", "bg": "#161821" } + }, + "pomodoro": { + "paused": { + "fg": "#0f1117", + "bg": "#e2a478" + }, + "work": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "break": { + "fg": "#89b8c2", + "bg": "#161821" + } } + } diff --git a/themes/icons/ascii.json b/themes/icons/ascii.json index 2084ca1..3b24f47 100644 --- a/themes/icons/ascii.json +++ b/themes/icons/ascii.json @@ -302,6 +302,10 @@ }, "system": { "prefix": "system" - } - + }, + "pomodoro": { + "off": { "prefix": "pom" }, + "paused": { "prefix": "||" }, + "on": { "prefix": " >" } + } } diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index eb805d4..c2c6c11 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -222,5 +222,10 @@ }, "rss": { "prefix": "" + }, + "pomodoro": { + "off": { "prefix": "🍅" }, + "paused": { "prefix": "" }, + "on": { "prefix": "" } } } diff --git a/themes/icons/ionicons.json b/themes/icons/ionicons.json index 08f9927..9f7a116 100644 --- a/themes/icons/ionicons.json +++ b/themes/icons/ionicons.json @@ -185,5 +185,11 @@ }, "rss": { "prefix": "\uf1ea" + }, + "pomodoro": { + "off": { "prefix": "\uf24f" }, + "paused": { "prefix": "\uf210" }, + "on": { "prefix": "\uf488" } } + } diff --git a/themes/onedark-powerline.json b/themes/onedark-powerline.json index 03867c6..d0ca586 100644 --- a/themes/onedark-powerline.json +++ b/themes/onedark-powerline.json @@ -36,5 +36,20 @@ "fg": "#282C34", "bg": "#98C379" } - } + }, + "pomodoro": { + "paused": { + "fg": "#282C34", + "bg": "#E5C07B" + }, + "work": { + "fg": "#98C379", + "bg": "#282C34" + }, + "break": { + "fg": "#282C34", + "bg": "#98C379" + } + } + } diff --git a/themes/powerline.json b/themes/powerline.json index 02f21f6..b2b603d 100644 --- a/themes/powerline.json +++ b/themes/powerline.json @@ -43,5 +43,20 @@ "fg": "#494949", "bg": "#41db00" } - } + }, + "pomodoro": { + "paused": { + "fg": "#d75f00", + "bg": "#ffd700" + }, + "work": { + "fg": "#ffd700", + "bg": "#d75f00" + }, + "break": { + "fg": "#494949", + "bg": "#41db00" + } + } + } diff --git a/themes/sac_red.json b/themes/sac_red.json index fade76e..7f6be68 100644 --- a/themes/sac_red.json +++ b/themes/sac_red.json @@ -40,5 +40,20 @@ }, "cmus": { "bg": "#C42021" - } + }, + "pomodoro": { + "paused": { + "fg": "#FDFFFC", + "bg": "#B91372" + }, + "work": { + "fg": "#FDFFFC", + "bg": "#41EAD4" + }, + "break": { + "fg": "#FDFFFC", + "bg": "#011627" + } + } + } diff --git a/themes/solarized-dark-awesome.json b/themes/solarized-dark-awesome.json index 9ec45e2..7274dc7 100644 --- a/themes/solarized-dark-awesome.json +++ b/themes/solarized-dark-awesome.json @@ -21,29 +21,43 @@ }, "apt": { "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "pacman": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "battery": { - "charged": { - "fg": "#002b36", - "bg": "#859900" - }, - "AC": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "git": { - "modified": { "bg": "#2aa198" }, - "deleted": { "bg": "#d33682" }, - "new": { "bg": "#859900" } - } + "fg": "#002b36", + "bg": "#859900" + } + }, + "pacman": { + "good": { + "fg": "#002b36", + "bg": "#859900" + } + }, + "battery": { + "charged": { + "fg": "#002b36", + "bg": "#859900" + }, + "AC": { + "fg": "#002b36", + "bg": "#859900" + } + }, + "git": { + "modified": { "bg": "#2aa198" }, + "deleted": { "bg": "#d33682" }, + "new": { "bg": "#859900" } + }, + "pomodoro": { + "paused": { + "fg": "#002b36", + "bg": "#b58900" + }, + "work": { + "fg": "#eee8d5", + "bg": "#586e75" + }, + "break": { + "fg": "#002b36", + "bg": "#859900" + } + } } diff --git a/themes/solarized-powerline.json b/themes/solarized-powerline.json index 3e52f7f..25854f5 100644 --- a/themes/solarized-powerline.json +++ b/themes/solarized-powerline.json @@ -47,5 +47,19 @@ "modified": { "bg": "#2aa198" }, "deleted": { "bg": "#d33682" }, "new": { "bg": "#859900" } - } + }, + "pomodoro": { + "paused": { + "fg": "#002b36", + "bg": "#b58900" + }, + "work": { + "fg": "#eee8d5", + "bg": "#586e75" + }, + "break": { + "fg": "#002b36", + "bg": "#859900" + } + } } diff --git a/themes/solarized.json b/themes/solarized.json index 4486634..e6f0082 100644 --- a/themes/solarized.json +++ b/themes/solarized.json @@ -49,5 +49,20 @@ "modified": { "bg": "#2aa198" }, "deleted": { "bg": "#d33682" }, "new": { "bg": "#859900" } - } + }, + "pomodoro": { + "paused": { + "fg": "#002b36", + "bg": "#b58900" + }, + "work": { + "fg": "#eee8d5", + "bg": "#586e75" + }, + "break": { + "fg": "#002b36", + "bg": "#859900" + } + } + } diff --git a/themes/wal-powerline.json b/themes/wal-powerline.json index dcb4fa9..8b28d3a 100644 --- a/themes/wal-powerline.json +++ b/themes/wal-powerline.json @@ -44,5 +44,20 @@ "fg": "background", "bg": "color3" } - } + }, + "pomodoro": { + "paused": { + "fg": "cursor", + "bg": "color6" + }, + "work": { + "fg": "background", + "bg": "foreground" + }, + "break": { + "fg": "background", + "bg": "color3" + } + } + } From c4ba2195b311b4a540048386fdc2c256ced843cd Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur Date: Sun, 29 Sep 2019 13:57:46 -0700 Subject: [PATCH 34/37] Changed Pomodoro icons, made the work/break periods variables --- bumblebee/modules/pomodoro.py | 18 +++++++++++------- themes/icons/awesome-fonts.json | 5 +++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/bumblebee/modules/pomodoro.py b/bumblebee/modules/pomodoro.py index 0eacc63..68909d9 100644 --- a/bumblebee/modules/pomodoro.py +++ b/bumblebee/modules/pomodoro.py @@ -16,10 +16,12 @@ import bumblebee.engine class Module(bumblebee.engine.Module): def __init__(self, engine, config): widgets = bumblebee.output.Widget(full_text=self.text) - self._work_period = 25 - self._play_period = 5 - self.remaining_time = datetime.timedelta(minutes=self._work_period) + self.work_period = 25 + self.break_period = 5 + self.remaining_time = datetime.timedelta(minutes=self.work_period) self.remaining_time_str = "{}m ".format(ceil((self.remaining_time.seconds / 60))) + # self.remaining_time_str = "{}min{}s ".format(int((self.remaining_time.seconds / 60)), + # round((self.remaining_time.seconds/60) % 1*60)) self.time = None self.pomodoro = { "state":"OFF", "type": ""} self._text = self.remaining_time_str + self.pomodoro["type"] @@ -42,18 +44,20 @@ class Module(bumblebee.engine.Module): if self.remaining_time.seconds <= 0: if self.pomodoro["type"] == "Work": self.pomodoro["type"] = "Break" - self.remaining_time = datetime.timedelta(minutes=self._work_period) + self.remaining_time = datetime.timedelta(minutes=self.break_period) elif self.pomodoro["type"] == "Break": self.pomodoro["type"] = "Work" - self.remaining_time = datetime.timedelta(minutes=self._play_period) + self.remaining_time = datetime.timedelta(minutes=self.work_period) self.remaining_time_str = "{}m ".format(ceil((self.remaining_time.seconds / 60))) + # self.remaining_time_str = "{}min{}s ".format(int((self.remaining_time.seconds / 60)), + # round((self.remaining_time.seconds / 60) % 1 * 60)) self._text = self.remaining_time_str + self.pomodoro["type"] def timer_play_pause(self, widget): if self.pomodoro["state"] == "OFF": self.pomodoro = {"state": "ON", "type": "Work"} - self.remaining_time = datetime.timedelta(minutes=self._work_period) + self.remaining_time = datetime.timedelta(minutes=self.work_period) self.time = datetime.datetime.now() elif self.pomodoro["state"] == "ON": self.pomodoro["state"] = "PAUSED" @@ -66,7 +70,7 @@ class Module(bumblebee.engine.Module): def timer_reset(self, widget): if self.pomodoro["state"] == "ON" or self.pomodoro["state"] == "PAUSED": self.pomodoro = {"state":"OFF", "type": "" } - self.remaining_time = datetime.timedelta(minutes=self._work_period) + self.remaining_time = datetime.timedelta(minutes=self.work_period) def state(self, widget): state = []; diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index c2c6c11..d5e947f 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -224,8 +224,9 @@ "prefix": "" }, "pomodoro": { - "off": { "prefix": "🍅" }, + "off": { "prefix": "" }, "paused": { "prefix": "" }, - "on": { "prefix": "" } + "work": { "prefix": "" }, + "break": { "prefix": "" } } } From b107623f7abcc3fa51db81eada25179e75977c90 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur Date: Mon, 30 Sep 2019 13:32:03 -0700 Subject: [PATCH 35/37] Added parameters for display format, work/break periods and notify --- bumblebee/modules/pomodoro.py | 70 +++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/bumblebee/modules/pomodoro.py b/bumblebee/modules/pomodoro.py index 68909d9..64f1514 100644 --- a/bumblebee/modules/pomodoro.py +++ b/bumblebee/modules/pomodoro.py @@ -3,6 +3,14 @@ """Display and run a Pomodoro timer. Left click to start timer, left click again to pause. Right click will cancel the timer. + +Parameters: + * pomodoro.work: The work duration of timer in minutes (defaults to 25) + * pomodoro.break: The break duration of timer in minutes (defaults to 5) + * pomodoro.format: Timer display format with "%m" and "%s" for minutes and seconds (defaults to "%m:%s") + Examples: "%m min %s sec", "%mm", "", "timer" + * pomodoro.notify: Notification command to run when timer ends/starts (defaults to nothing) + Example: 'notify-send "Time up!"' """ from __future__ import absolute_import @@ -16,21 +24,49 @@ import bumblebee.engine class Module(bumblebee.engine.Module): def __init__(self, engine, config): widgets = bumblebee.output.Widget(full_text=self.text) - self.work_period = 25 - self.break_period = 5 - self.remaining_time = datetime.timedelta(minutes=self.work_period) - self.remaining_time_str = "{}m ".format(ceil((self.remaining_time.seconds / 60))) - # self.remaining_time_str = "{}min{}s ".format(int((self.remaining_time.seconds / 60)), - # round((self.remaining_time.seconds/60) % 1*60)) + + super(Module, self).__init__(engine, config, widgets) + + # Parameters + self._work_period = int(self.parameter("work", 25)) + self._break_period = int(self.parameter("break", 5)) + self._time_format = self.parameter("format", "%m:%s") + self._notify_cmd = self.parameter("notify", "") + + # TODO: Handle time formats more gracefully. This is kludge. + self.display_seconds_p = False + self.display_minutes_p = False + if "%s" in self._time_format: + self.display_seconds_p = True + if "%m" in self._time_format: + self.display_minutes_p = True + + self.remaining_time = datetime.timedelta(minutes=self._work_period) + self.time = None self.pomodoro = { "state":"OFF", "type": ""} - self._text = self.remaining_time_str + self.pomodoro["type"] - super(Module, self).__init__(engine, config, widgets) + self._text = self.remaining_time_str() + self.pomodoro["type"] + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self.timer_play_pause) engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self.timer_reset) + def remaining_time_str(self): + + if self.display_seconds_p and self.display_minutes_p: + minutes, seconds = divmod(self.remaining_time.seconds, 60) + if not self.display_seconds_p: + minutes = ceil(self.remaining_time.seconds / 60) + seconds = 0 + if not self.display_minutes_p: + minutes = 0 + seconds = self.remaining_time.seconds + + minutes = "{:2d}".format(minutes) + seconds = "{:02d}".format(seconds) + return self._time_format.replace("%m",minutes).replace("%s",seconds)+" " + def text(self, widget): return "{}".format(self._text) @@ -42,22 +78,24 @@ class Module(bumblebee.engine.Module): self.time = datetime.datetime.now() if self.remaining_time.seconds <= 0: + self.notify() if self.pomodoro["type"] == "Work": self.pomodoro["type"] = "Break" - self.remaining_time = datetime.timedelta(minutes=self.break_period) + self.remaining_time = datetime.timedelta(minutes=self._break_period) elif self.pomodoro["type"] == "Break": self.pomodoro["type"] = "Work" - self.remaining_time = datetime.timedelta(minutes=self.work_period) + self.remaining_time = datetime.timedelta(minutes=self._work_period) - self.remaining_time_str = "{}m ".format(ceil((self.remaining_time.seconds / 60))) - # self.remaining_time_str = "{}min{}s ".format(int((self.remaining_time.seconds / 60)), - # round((self.remaining_time.seconds / 60) % 1 * 60)) - self._text = self.remaining_time_str + self.pomodoro["type"] + self._text = self.remaining_time_str() + self.pomodoro["type"] + def notify(self): + if self._notify_cmd: + bumblebee.util.execute(self._notify_cmd) + def timer_play_pause(self, widget): if self.pomodoro["state"] == "OFF": self.pomodoro = {"state": "ON", "type": "Work"} - self.remaining_time = datetime.timedelta(minutes=self.work_period) + self.remaining_time = datetime.timedelta(minutes=self._work_period) self.time = datetime.datetime.now() elif self.pomodoro["state"] == "ON": self.pomodoro["state"] = "PAUSED" @@ -70,7 +108,7 @@ class Module(bumblebee.engine.Module): def timer_reset(self, widget): if self.pomodoro["state"] == "ON" or self.pomodoro["state"] == "PAUSED": self.pomodoro = {"state":"OFF", "type": "" } - self.remaining_time = datetime.timedelta(minutes=self.work_period) + self.remaining_time = datetime.timedelta(minutes=self._work_period) def state(self, widget): state = []; From c8d5346d819d054715797a08f2c9fc018b80b54b Mon Sep 17 00:00:00 2001 From: WORD559 Date: Wed, 2 Oct 2019 21:30:55 +0100 Subject: [PATCH 36/37] Add parameter to prevent scrollable decorator from fixing the module width --- bumblebee/output.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 5c6ff92..f70b605 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -8,12 +8,15 @@ import uuid import bumblebee.store +_TrueValues = ["true", "t", "yes", "y", "1"] + def scrollable(func): def wrapper(module, widget): text = func(module, widget) if not text: return text width = widget.get("theme.width", module.parameter("width", 30)) - widget.set("theme.minwidth", "A"*width) + if module.parameter("scrolling.makewide", "true").lower() in _TrueValues: + widget.set("theme.minwidth", "A"*width) if len(text) <= width: return text # we need to shorten From 9b6fdbe9ace861d81662a300904a641a2ed45f40 Mon Sep 17 00:00:00 2001 From: WORD559 Date: Wed, 2 Oct 2019 21:35:47 +0100 Subject: [PATCH 37/37] Make spotify scrollable and support unicode in all python versions --- bumblebee/modules/spotify.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/bumblebee/modules/spotify.py b/bumblebee/modules/spotify.py index 5f4bcee..abc12bd 100644 --- a/bumblebee/modules/spotify.py +++ b/bumblebee/modules/spotify.py @@ -13,6 +13,8 @@ Parameters: LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN """ +import sys + import bumblebee.input import bumblebee.output import bumblebee.engine @@ -52,12 +54,12 @@ class Module(bumblebee.engine.Module): engine.input.register_callback(self, button=buttons[pause_button], cmd=cmd + "PlayPause") -## @scrollable + @scrollable def spotify(self, widget): - return str(self._song) + return self.string_song def hidden(self): - return str(self._song) == "" + return self.string_song == "" def update(self, widgets): try: @@ -71,7 +73,15 @@ class Module(bumblebee.engine.Module): artist=','.join(props.get('xesam:artist')), trackNumber=str(props.get('xesam:trackNumber')), playbackStatus=u"\u25B6" if playback_status=="Playing" else u"\u258D\u258D" if playback_status=="Paused" else "",) + except Exception: self._song = "" + @property + def string_song(self): + if sys.version_info.major < 3: + return unicode(self._song) + return str(self._song) + + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4