Merge branch 'development' into arandr
This commit is contained in:
commit
2fcd277cf9
34 changed files with 713 additions and 270 deletions
|
@ -13,4 +13,3 @@ ratings:
|
||||||
- "**.py"
|
- "**.py"
|
||||||
exclude_paths:
|
exclude_paths:
|
||||||
- tests/
|
- tests/
|
||||||
- thirdparty/
|
|
||||||
|
|
27
.github/workflows/pythonpublish.yml
vendored
Normal file
27
.github/workflows/pythonpublish.yml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
name: Upload Python Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install setuptools wheel twine
|
||||||
|
- name: Build and publish
|
||||||
|
env:
|
||||||
|
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||||
|
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
python setup.py sdist bdist_wheel
|
||||||
|
twine upload dist/*
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -94,3 +94,6 @@ ENV/
|
||||||
|
|
||||||
# Visual studio project files
|
# Visual studio project files
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
# mypy cache
|
||||||
|
.mypy_cache
|
||||||
|
|
10
.travis.yml
10
.travis.yml
|
@ -8,17 +8,9 @@ python:
|
||||||
- "3.8"
|
- "3.8"
|
||||||
before_install:
|
before_install:
|
||||||
- sudo apt-get -qq update
|
- sudo apt-get -qq update
|
||||||
- sudo apt-get install -y task libdbus-1-dev
|
|
||||||
install:
|
install:
|
||||||
- pip install i3ipc
|
- pip install coverage
|
||||||
- pip install psutil
|
|
||||||
- pip install netifaces
|
|
||||||
- pip install -U coverage==4.3
|
|
||||||
- pip install codeclimate-test-reporter
|
- pip install codeclimate-test-reporter
|
||||||
- pip install taskw
|
|
||||||
- pip install pytz
|
|
||||||
- pip install tzlocal
|
|
||||||
- pip install dbus-python
|
|
||||||
- pip install coverage
|
- pip install coverage
|
||||||
script:
|
script:
|
||||||
- coverage run -m unittest discover -v
|
- coverage run -m unittest discover -v
|
||||||
|
|
|
@ -10,6 +10,7 @@ import logging
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import bumblebee_status.discover
|
import bumblebee_status.discover
|
||||||
|
|
||||||
bumblebee_status.discover.discover()
|
bumblebee_status.discover.discover()
|
||||||
|
|
||||||
import core.config
|
import core.config
|
||||||
|
@ -101,6 +102,7 @@ def main():
|
||||||
|
|
||||||
for module in config.modules():
|
for module in config.modules():
|
||||||
modules.append(core.module.load(module, config, theme))
|
modules.append(core.module.load(module, config, theme))
|
||||||
|
modules[-1].register_callbacks()
|
||||||
|
|
||||||
if config.reverse():
|
if config.reverse():
|
||||||
modules.reverse()
|
modules.reverse()
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import bumblebee_status.discover
|
||||||
|
|
||||||
|
bumblebee_status.discover.discover()
|
|
@ -27,7 +27,11 @@ THEME_HELP = "Specify the theme to use for drawing modules"
|
||||||
|
|
||||||
|
|
||||||
def all_modules():
|
def all_modules():
|
||||||
"""Return a list of available modules"""
|
"""Returns a list of all available modules (either core or contrib)
|
||||||
|
|
||||||
|
:return: list of modules
|
||||||
|
:rtype: list of strings
|
||||||
|
"""
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
for path in [modules.core.__file__, modules.contrib.__file__]:
|
for path in [modules.core.__file__, modules.contrib.__file__]:
|
||||||
|
@ -127,6 +131,11 @@ class print_usage(argparse.Action):
|
||||||
|
|
||||||
|
|
||||||
class Config(util.store.Store):
|
class Config(util.store.Store):
|
||||||
|
"""Represents the configuration of bumblebee-status (either via config file or via CLI)
|
||||||
|
|
||||||
|
:param args: The arguments passed via the commandline
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
super(Config, self).__init__()
|
super(Config, self).__init__()
|
||||||
|
|
||||||
|
@ -202,6 +211,11 @@ class Config(util.store.Store):
|
||||||
key, value = param.split("=", 1)
|
key, value = param.split("=", 1)
|
||||||
self.set(key, value)
|
self.set(key, value)
|
||||||
|
|
||||||
|
"""Loads parameters from an init-style configuration file
|
||||||
|
|
||||||
|
:param filename: path to the file to load
|
||||||
|
"""
|
||||||
|
|
||||||
def load_config(self, filename):
|
def load_config(self, filename):
|
||||||
if os.path.exists(filename):
|
if os.path.exists(filename):
|
||||||
log.info("loading {}".format(filename))
|
log.info("loading {}".format(filename))
|
||||||
|
@ -212,27 +226,75 @@ class Config(util.store.Store):
|
||||||
for key, value in tmp.items("module-parameters"):
|
for key, value in tmp.items("module-parameters"):
|
||||||
self.set(key, value)
|
self.set(key, value)
|
||||||
|
|
||||||
|
"""Returns a list of configured modules
|
||||||
|
|
||||||
|
:return: list of configured (active) modules
|
||||||
|
:rtype: list of strings
|
||||||
|
"""
|
||||||
|
|
||||||
def modules(self):
|
def modules(self):
|
||||||
return [item for sub in self.__args.modules for item in sub]
|
return [item for sub in self.__args.modules for item in sub]
|
||||||
|
|
||||||
|
"""Returns the global update interval
|
||||||
|
|
||||||
|
:return: update interval in seconds
|
||||||
|
:rtype: float
|
||||||
|
"""
|
||||||
|
|
||||||
def interval(self, default=1):
|
def interval(self, default=1):
|
||||||
return util.format.seconds(self.get("interval", default))
|
return util.format.seconds(self.get("interval", default))
|
||||||
|
|
||||||
|
"""Returns whether debug mode is enabled
|
||||||
|
|
||||||
|
:return: True if debug is enabled, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
"""
|
||||||
|
|
||||||
def debug(self):
|
def debug(self):
|
||||||
return self.__args.debug
|
return self.__args.debug
|
||||||
|
|
||||||
|
"""Returns whether module order should be reversed/inverted
|
||||||
|
|
||||||
|
:return: True if modules should be reversed, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
"""
|
||||||
|
|
||||||
def reverse(self):
|
def reverse(self):
|
||||||
return self.__args.right_to_left
|
return self.__args.right_to_left
|
||||||
|
|
||||||
|
"""Returns the logfile location
|
||||||
|
|
||||||
|
:return: location where the logfile should be written
|
||||||
|
:rtype: string
|
||||||
|
"""
|
||||||
|
|
||||||
def logfile(self):
|
def logfile(self):
|
||||||
return self.__args.logfile
|
return self.__args.logfile
|
||||||
|
|
||||||
|
"""Returns the configured theme name
|
||||||
|
|
||||||
|
:return: name of the configured theme
|
||||||
|
:rtype: string
|
||||||
|
"""
|
||||||
|
|
||||||
def theme(self):
|
def theme(self):
|
||||||
return self.__args.theme
|
return self.__args.theme
|
||||||
|
|
||||||
|
"""Returns the configured iconset name
|
||||||
|
|
||||||
|
:return: name of the configured iconset
|
||||||
|
:rtype: string
|
||||||
|
"""
|
||||||
|
|
||||||
def iconset(self):
|
def iconset(self):
|
||||||
return self.__args.iconset
|
return self.__args.iconset
|
||||||
|
|
||||||
|
"""Returns which modules should be hidden if their state is not warning/critical
|
||||||
|
|
||||||
|
:return: list of modules to hide automatically
|
||||||
|
:rtype: list of strings
|
||||||
|
"""
|
||||||
|
|
||||||
def autohide(self, name):
|
def autohide(self, name):
|
||||||
return name in self.__args.autohide
|
return name in self.__args.autohide
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,19 @@
|
||||||
|
import difflib
|
||||||
|
import logging
|
||||||
|
|
||||||
import util.format
|
import util.format
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
"""Specifies that a module should never update (i.e. has static content).
|
||||||
|
This means that its update() method will never be invoked
|
||||||
|
|
||||||
|
:param init: The __init__() method of the module
|
||||||
|
|
||||||
|
:return: Wrapped method that sets the module's interval to "never"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def never(init):
|
def never(init):
|
||||||
def call_init(obj, *args, **kwargs):
|
def call_init(obj, *args, **kwargs):
|
||||||
|
@ -10,6 +24,16 @@ def never(init):
|
||||||
return call_init
|
return call_init
|
||||||
|
|
||||||
|
|
||||||
|
"""Specifies the interval for executing the module's update() method
|
||||||
|
|
||||||
|
:param hours: Hours between two update() invocations, defaults to 0
|
||||||
|
:param minutes: Minutes between two update() invocations, defaults to 0
|
||||||
|
:param seconds: Seconds between two update() invocations, defaults to 0
|
||||||
|
|
||||||
|
:return: Wrapped method that sets the module's interval correspondingly
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def every(hours=0, minutes=0, seconds=0):
|
def every(hours=0, minutes=0, seconds=0):
|
||||||
def decorator_init(init):
|
def decorator_init(init):
|
||||||
def call_init(obj, *args, **kwargs):
|
def call_init(obj, *args, **kwargs):
|
||||||
|
@ -22,13 +46,30 @@ def every(hours=0, minutes=0, seconds=0):
|
||||||
return decorator_init
|
return decorator_init
|
||||||
|
|
||||||
|
|
||||||
|
"""Specifies that the module's content should scroll, if required
|
||||||
|
|
||||||
|
The exact behaviour of this method is governed by a number of parameters,
|
||||||
|
specifically: The module's parameter "scrolling.width" specifies the width when
|
||||||
|
scrolling starts, "scrolling.makewide" defines whether the module should be expanded
|
||||||
|
to "scrolling.width" automatically, if the content is shorter, the parameter
|
||||||
|
"scrolling.bounce" defines whether it scrolls like a marquee (False) or should bounce
|
||||||
|
when the end of the content is reached. "scrolling.speed" defines the number of characters
|
||||||
|
to scroll each iteration.
|
||||||
|
|
||||||
|
:param func: Function for which the result should be scrolled
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def scrollable(func):
|
def scrollable(func):
|
||||||
def wrapper(module, widget):
|
def wrapper(module, widget):
|
||||||
text = func(module, widget)
|
text = func(module, widget)
|
||||||
if not text:
|
if not text:
|
||||||
return text
|
return text
|
||||||
|
|
||||||
if text != widget.get("__content__", text):
|
if (
|
||||||
|
difflib.SequenceMatcher(a=text, b=widget.get("__content__", text)).ratio()
|
||||||
|
< 0.9
|
||||||
|
):
|
||||||
widget.set("scrolling.start", 0)
|
widget.set("scrolling.start", 0)
|
||||||
widget.set("scrolling.direction", "right")
|
widget.set("scrolling.direction", "right")
|
||||||
widget.set("__content__", text)
|
widget.set("__content__", text)
|
||||||
|
@ -45,9 +86,10 @@ def scrollable(func):
|
||||||
direction = widget.get("scrolling.direction", "right")
|
direction = widget.get("scrolling.direction", "right")
|
||||||
|
|
||||||
if direction == "left":
|
if direction == "left":
|
||||||
scroll_speed = -scroll_speed
|
if start - scroll_speed < 0: # bounce back
|
||||||
if start + scroll_speed <= 0: # bounce back
|
|
||||||
widget.set("scrolling.direction", "right")
|
widget.set("scrolling.direction", "right")
|
||||||
|
else:
|
||||||
|
scroll_speed = -scroll_speed
|
||||||
|
|
||||||
next_start = start + scroll_speed
|
next_start = start + scroll_speed
|
||||||
if next_start + width > len(text):
|
if next_start + width > len(text):
|
||||||
|
|
|
@ -36,20 +36,20 @@ def __event_id(obj_id, button):
|
||||||
return "{}::{}".format(obj_id, button_name(button))
|
return "{}::{}".format(obj_id, button_name(button))
|
||||||
|
|
||||||
|
|
||||||
def __execute(cmd):
|
def __execute(cmd, wait=False):
|
||||||
try:
|
try:
|
||||||
util.cli.execute(cmd, wait=False)
|
util.cli.execute(cmd, wait=wait, shell=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("failed to invoke callback: {}".format(e))
|
logging.error("failed to invoke callback: {}".format(e))
|
||||||
|
|
||||||
|
|
||||||
def register(obj, button=None, cmd=None):
|
def register(obj, button=None, cmd=None, wait=False):
|
||||||
event_id = __event_id(obj.id if obj is not None else "", button)
|
event_id = __event_id(obj.id if obj is not None else "", button)
|
||||||
logging.debug("registering callback {}".format(event_id))
|
logging.debug("registering callback {}".format(event_id))
|
||||||
if callable(cmd):
|
if callable(cmd):
|
||||||
core.event.register(event_id, cmd)
|
core.event.register(event_id, cmd)
|
||||||
else:
|
else:
|
||||||
core.event.register(event_id, lambda _: __execute(cmd))
|
core.event.register(event_id, lambda _: __execute(cmd, wait))
|
||||||
|
|
||||||
|
|
||||||
def trigger(event):
|
def trigger(event):
|
||||||
|
|
|
@ -2,10 +2,13 @@ import os
|
||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import core.config
|
||||||
import core.input
|
import core.input
|
||||||
import core.widget
|
import core.widget
|
||||||
import core.decorators
|
import core.decorators
|
||||||
|
|
||||||
|
import util.format
|
||||||
|
|
||||||
try:
|
try:
|
||||||
error = ModuleNotFoundError("")
|
error = ModuleNotFoundError("")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -14,6 +17,17 @@ except Exception as e:
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
"""Loads a module by name
|
||||||
|
|
||||||
|
:param module_name: Name of the module to load
|
||||||
|
:param config: Configuration to apply to the module (defaults to an empty configuration)
|
||||||
|
:param theme: Theme for this module, defaults to None, which means whatever is configured in "config"
|
||||||
|
|
||||||
|
:return: A module object representing the module, or an Error module if loading failed
|
||||||
|
:rtype: class bumblebee_status.module.Module
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def load(module_name, config=core.config.Config([]), theme=None):
|
def load(module_name, config=core.config.Config([]), theme=None):
|
||||||
error = None
|
error = None
|
||||||
module_short, alias = (module_name.split(":") + [module_name])[0:2]
|
module_short, alias = (module_name.split(":") + [module_name])[0:2]
|
||||||
|
@ -35,6 +49,13 @@ def load(module_name, config=core.config.Config([]), theme=None):
|
||||||
|
|
||||||
|
|
||||||
class Module(core.input.Object):
|
class Module(core.input.Object):
|
||||||
|
"""Represents a module (single piece of functionality) of the bar
|
||||||
|
|
||||||
|
:param config: Configuration to apply to the module (defaults to an empty configuration)
|
||||||
|
:param theme: Theme for this module, defaults to None, which means whatever is configured in "config"
|
||||||
|
:param widgets: A list of bumblebee_status.widget.Widget objects that the module is comprised of
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, config=core.config.Config([]), theme=None, widgets=[]):
|
def __init__(self, config=core.config.Config([]), theme=None, widgets=[]):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.__config = config
|
self.__config = config
|
||||||
|
@ -51,23 +72,55 @@ class Module(core.input.Object):
|
||||||
for widget in self.__widgets:
|
for widget in self.__widgets:
|
||||||
widget.module = self
|
widget.module = self
|
||||||
|
|
||||||
|
"""Override this to determine when to show this module
|
||||||
|
|
||||||
|
:return: True if the module should be hidden, False otherwise
|
||||||
|
:rtype: boolean
|
||||||
|
"""
|
||||||
|
|
||||||
def hidden(self):
|
def hidden(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
"""Retrieve CLI/configuration parameters for this module. For example, if
|
||||||
|
the module is called "test" and the user specifies "-p test.x=123" on the
|
||||||
|
commandline, using self.parameter("x") retrieves the value 123.
|
||||||
|
|
||||||
|
:param key: Name of the parameter to retrieve
|
||||||
|
:param default: Default value, if parameter is not set by user (defaults to None)
|
||||||
|
|
||||||
|
:return: Parameter value, or default
|
||||||
|
:rtype: string
|
||||||
|
"""
|
||||||
|
|
||||||
def parameter(self, key, default=None):
|
def parameter(self, key, default=None):
|
||||||
value = default
|
value = default
|
||||||
|
|
||||||
for prefix in [self.name, self.module_name, self.alias]:
|
for prefix in [self.name, self.module_name, self.alias]:
|
||||||
value = self.__config.get("{}.{}".format(prefix, key), value)
|
value = self.__config.get("{}.{}".format(prefix, key), value)
|
||||||
# TODO retrieve from config file
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
"""Set a parameter for this module
|
||||||
|
|
||||||
|
:param key: Name of the parameter to set
|
||||||
|
:param value: New value of the parameter
|
||||||
|
"""
|
||||||
|
|
||||||
def set(self, key, value):
|
def set(self, key, value):
|
||||||
self.__config.set("{}.{}".format(self.name, key), value)
|
self.__config.set("{}.{}".format(self.name, key), value)
|
||||||
|
|
||||||
|
"""Override this method to define tasks that should be done during each
|
||||||
|
update interval (for instance, querying an API, calling a CLI tool to get new
|
||||||
|
date, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
"""Wrapper method that ensures that all exceptions thrown by the
|
||||||
|
update() method are caught and displayed in a bumblebee_status.module.Error
|
||||||
|
module
|
||||||
|
"""
|
||||||
|
|
||||||
def update_wrapper(self):
|
def update_wrapper(self):
|
||||||
try:
|
try:
|
||||||
self.update()
|
self.update()
|
||||||
|
@ -77,17 +130,42 @@ class Module(core.input.Object):
|
||||||
self.__widgets = [module.widget()]
|
self.__widgets = [module.widget()]
|
||||||
self.update = module.update
|
self.update = module.update
|
||||||
|
|
||||||
|
"""Retrieves the list of widgets for this module
|
||||||
|
|
||||||
|
:return: A list of widgets
|
||||||
|
:rtype: list of bumblebee_status.widget.Widgets
|
||||||
|
"""
|
||||||
|
|
||||||
def widgets(self):
|
def widgets(self):
|
||||||
return self.__widgets
|
return self.__widgets
|
||||||
|
|
||||||
|
"""Removes all widgets of this module"""
|
||||||
|
|
||||||
def clear_widgets(self):
|
def clear_widgets(self):
|
||||||
del self.__widgets[:]
|
del self.__widgets[:]
|
||||||
|
|
||||||
|
"""Adds a widget to the module
|
||||||
|
|
||||||
|
:param full_text: Text or callable (method) that defines the text of the widget (defaults to "")
|
||||||
|
:param name: Name of the widget, defaults to None, which means autogenerate
|
||||||
|
|
||||||
|
:return: The new widget
|
||||||
|
:rtype: bumblebee_status.widget.Widget
|
||||||
|
"""
|
||||||
|
|
||||||
def add_widget(self, full_text="", name=None):
|
def add_widget(self, full_text="", name=None):
|
||||||
widget = core.widget.Widget(full_text=full_text, name=name, module=self)
|
widget = core.widget.Widget(full_text=full_text, name=name, module=self)
|
||||||
self.widgets().append(widget)
|
self.widgets().append(widget)
|
||||||
return widget
|
return widget
|
||||||
|
|
||||||
|
"""Convenience method to retrieve a named widget
|
||||||
|
|
||||||
|
:param name: Name of widget to retrieve, defaults to None (in which case the first widget is returned)
|
||||||
|
|
||||||
|
:return: The widget with the corresponding name, None if not found
|
||||||
|
:rtype: bumblebee_status.widget.Widget
|
||||||
|
"""
|
||||||
|
|
||||||
def widget(self, name=None):
|
def widget(self, name=None):
|
||||||
if not name:
|
if not name:
|
||||||
return self.widgets()[0]
|
return self.widgets()[0]
|
||||||
|
@ -97,9 +175,27 @@ class Module(core.input.Object):
|
||||||
return w
|
return w
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
"""Override this method to define states for the module
|
||||||
|
|
||||||
|
:param widget: Widget for which state should be returned
|
||||||
|
|
||||||
|
:return: a list of states for this widget
|
||||||
|
:rtype: list of strings
|
||||||
|
"""
|
||||||
|
|
||||||
def state(self, widget):
|
def state(self, widget):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
"""Convenience method that sets warning and critical state for numbers
|
||||||
|
|
||||||
|
:param value: Current value to calculate state against
|
||||||
|
:param warn: Warning threshold
|
||||||
|
:parm crit: Critical threshold
|
||||||
|
|
||||||
|
:return: None if value is below both thresholds, "critical", "warning" as appropriate otherwise
|
||||||
|
:rtype: string
|
||||||
|
"""
|
||||||
|
|
||||||
def threshold_state(self, value, warn, crit):
|
def threshold_state(self, value, warn, crit):
|
||||||
if value > float(self.parameter("critical", crit)):
|
if value > float(self.parameter("critical", crit)):
|
||||||
return "critical"
|
return "critical"
|
||||||
|
@ -107,16 +203,49 @@ class Module(core.input.Object):
|
||||||
return "warning"
|
return "warning"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def register_callbacks(self):
|
||||||
|
actions = [
|
||||||
|
{"name": "left-click", "id": core.input.LEFT_MOUSE},
|
||||||
|
{"name": "right-click", "id": core.input.RIGHT_MOUSE},
|
||||||
|
{"name": "middle-click", "id": core.input.MIDDLE_MOUSE},
|
||||||
|
{"name": "wheel-up", "id": core.input.WHEEL_UP},
|
||||||
|
{"name": "wheel-down", "id": core.input.WHEEL_DOWN},
|
||||||
|
]
|
||||||
|
for action in actions:
|
||||||
|
if self.parameter(action["name"]):
|
||||||
|
core.input.register(
|
||||||
|
self,
|
||||||
|
action["id"],
|
||||||
|
self.parameter(action["name"]),
|
||||||
|
util.format.asbool(
|
||||||
|
self.parameter("{}-wait".format(action["name"]), False)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Error(Module):
|
class Error(Module):
|
||||||
|
"""Represents an "error" module
|
||||||
|
|
||||||
|
:param module: The module name that produced the error
|
||||||
|
:param error: The error message to display
|
||||||
|
:param config: Configuration to apply to the module (defaults to an empty configuration)
|
||||||
|
:param theme: Theme for this module, defaults to None, which means whatever is configured in "config"
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, module, error, config=core.config.Config([]), theme=None):
|
def __init__(self, module, error, config=core.config.Config([]), theme=None):
|
||||||
super().__init__(config, theme, core.widget.Widget(self.full_text))
|
super().__init__(config, theme, core.widget.Widget(self.full_text))
|
||||||
self.__module = module
|
self.__module = module
|
||||||
self.__error = error
|
self.__error = error
|
||||||
|
|
||||||
|
"""Returns the error message
|
||||||
|
:param widget: the error widget to display
|
||||||
|
"""
|
||||||
|
|
||||||
def full_text(self, widget):
|
def full_text(self, widget):
|
||||||
return "{}: {}".format(self.__module, self.__error)
|
return "{}: {}".format(self.__module, self.__error)
|
||||||
|
|
||||||
|
"""Overriden state, always returns critical (it *is* an error, after all"""
|
||||||
|
|
||||||
def state(self, widget):
|
def state(self, widget):
|
||||||
return ["critical"]
|
return ["critical"]
|
||||||
|
|
||||||
|
|
|
@ -9,4 +9,19 @@ def discover():
|
||||||
sys.path.append(libdir)
|
sys.path.append(libdir)
|
||||||
|
|
||||||
|
|
||||||
|
def utility(name):
|
||||||
|
current_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
for path in [
|
||||||
|
os.path.join(current_path, "..", "bin"),
|
||||||
|
os.path.join(
|
||||||
|
current_path, "..", "..", "..", "..", "share", "bumblebee-status", "utility"
|
||||||
|
),
|
||||||
|
"/usr/share/bumblebee-status/bin/",
|
||||||
|
]:
|
||||||
|
if os.path.exists(os.path.abspath(os.path.join(path, name))):
|
||||||
|
return os.path.abspath(os.path.join(path, name))
|
||||||
|
raise Exception("{} not found".format(name))
|
||||||
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
||||||
|
|
|
@ -33,13 +33,18 @@ class Module(core.module.Module):
|
||||||
return self.__packages == 0 and not self.__error
|
return self.__packages == 0 and not self.__error
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
try:
|
|
||||||
result = util.cli.execute("checkupdates")
|
|
||||||
self.__packages = len(result.split("\n")) - 1
|
|
||||||
self.__error = False
|
self.__error = False
|
||||||
except Exception as e:
|
code, result = util.cli.execute(
|
||||||
logging.exception(e)
|
"checkupdates", ignore_errors=True, return_exitcode=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if code == 0:
|
||||||
|
self.__packages = len(result.split("\n"))
|
||||||
|
elif code == 2:
|
||||||
|
self.__packages = 0
|
||||||
|
else:
|
||||||
self.__error = True
|
self.__error = True
|
||||||
|
log.error("checkupdates exited with {}: {}".format(code, result))
|
||||||
|
|
||||||
def state(self, widget):
|
def state(self, widget):
|
||||||
if self.__error:
|
if self.__error:
|
||||||
|
|
|
@ -115,7 +115,7 @@ class Module(core.module.Module):
|
||||||
for battery in glob.glob("/sys/class/power_supply/BAT*")
|
for battery in glob.glob("/sys/class/power_supply/BAT*")
|
||||||
]
|
]
|
||||||
if len(self._batteries) == 0:
|
if len(self._batteries) == 0:
|
||||||
raise Exceptions("no batteries configured/found")
|
raise Exception("no batteries configured/found")
|
||||||
core.input.register(
|
core.input.register(
|
||||||
self, button=core.input.LEFT_MOUSE, cmd="gnome-power-statistics"
|
self, button=core.input.LEFT_MOUSE, cmd="gnome-power-statistics"
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,15 +22,15 @@ import core.decorators
|
||||||
import util.cli
|
import util.cli
|
||||||
import util.format
|
import util.format
|
||||||
|
|
||||||
|
from bumblebee_status.discover import utility
|
||||||
|
|
||||||
# list of repositories.
|
# list of repositories.
|
||||||
# the last one should always be other
|
# the last one should always be other
|
||||||
repos = ["core", "extra", "community", "multilib", "testing", "other"]
|
repos = ["core", "extra", "community", "multilib", "testing", "other"]
|
||||||
|
|
||||||
|
|
||||||
def get_pacman_info(widget, path):
|
def get_pacman_info(widget, path):
|
||||||
cmd = "{}/../../bin/pacman-updates".format(path)
|
cmd = utility("pacman-updates")
|
||||||
if not os.path.exists(cmd):
|
|
||||||
cmd = "/usr/share/bumblebee-status/bin/pacman-update"
|
|
||||||
result = util.cli.execute(cmd, ignore_errors=True)
|
result = util.cli.execute(cmd, ignore_errors=True)
|
||||||
|
|
||||||
count = len(repos) * [0]
|
count = len(repos) * [0]
|
||||||
|
|
|
@ -24,6 +24,8 @@ import core.module
|
||||||
import core.input
|
import core.input
|
||||||
import core.decorators
|
import core.decorators
|
||||||
|
|
||||||
|
from bumblebee_status.discover import utility
|
||||||
|
|
||||||
import util.cli
|
import util.cli
|
||||||
import util.format
|
import util.format
|
||||||
|
|
||||||
|
@ -36,8 +38,7 @@ except:
|
||||||
class Module(core.module.Module):
|
class Module(core.module.Module):
|
||||||
@core.decorators.every(seconds=5) # takes up to 5s to detect a new screen
|
@core.decorators.every(seconds=5) # takes up to 5s to detect a new screen
|
||||||
def __init__(self, config, theme):
|
def __init__(self, config, theme):
|
||||||
widgets = []
|
super().__init__(config, theme, [])
|
||||||
super().__init__(config, theme, widgets)
|
|
||||||
|
|
||||||
self._autoupdate = util.format.asbool(self.parameter("autoupdate", True))
|
self._autoupdate = util.format.asbool(self.parameter("autoupdate", True))
|
||||||
self._needs_update = True
|
self._needs_update = True
|
||||||
|
@ -85,10 +86,9 @@ class Module(core.module.Module):
|
||||||
|
|
||||||
def _toggle(self, event):
|
def _toggle(self, event):
|
||||||
self._refresh(self, event)
|
self._refresh(self, event)
|
||||||
path = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
if util.format.asbool(self.parameter("overwrite_i3config", False)) == True:
|
if util.format.asbool(self.parameter("overwrite_i3config", False)) == True:
|
||||||
toggle_cmd = "{}/../../bin/toggle-display.sh".format(path)
|
toggle_cmd = utility("toggle-display.sh")
|
||||||
else:
|
else:
|
||||||
toggle_cmd = "xrandr"
|
toggle_cmd = "xrandr"
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,15 @@ import subprocess
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
def execute(cmd, wait=True, ignore_errors=False, include_stderr=False, env=None):
|
def execute(
|
||||||
|
cmd,
|
||||||
|
wait=True,
|
||||||
|
ignore_errors=False,
|
||||||
|
include_stderr=False,
|
||||||
|
env=None,
|
||||||
|
return_exitcode=False,
|
||||||
|
shell=False,
|
||||||
|
):
|
||||||
"""Executes a commandline utility and returns its output
|
"""Executes a commandline utility and returns its output
|
||||||
|
|
||||||
:param cmd: the command (as string) to execute, returns the program's output
|
:param cmd: the command (as string) to execute, returns the program's output
|
||||||
|
@ -12,13 +20,15 @@ def execute(cmd, wait=True, ignore_errors=False, include_stderr=False, env=None)
|
||||||
:param ignore_errors: set to True to return a string when an exception is thrown, otherwise might throw, defaults to False
|
:param ignore_errors: set to True to return a string when an exception is thrown, otherwise might throw, defaults to False
|
||||||
:param include_stderr: set to True to include stderr output in the return value, defaults to False
|
:param include_stderr: set to True to include stderr output in the return value, defaults to False
|
||||||
:param env: provide a dict here to specify a custom execution environment, defaults to None
|
:param env: provide a dict here to specify a custom execution environment, defaults to None
|
||||||
|
:param return_exitcode: set to True to return a pair, where the first member is the exit code and the message the second, defaults to False
|
||||||
|
:param shell: set to True to run command in a separate shell, defaults to False
|
||||||
|
|
||||||
:raises RuntimeError: the command either didn't exist or didn't exit cleanly, and ignore_errors was set to False
|
:raises RuntimeError: the command either didn't exist or didn't exit cleanly, and ignore_errors was set to False
|
||||||
|
|
||||||
:return: output of cmd, or stderr, if ignore_errors is True and the command failed
|
:return: output of cmd, or stderr, if ignore_errors is True and the command failed; or a tuple of exitcode and the previous, if return_exitcode is set to True
|
||||||
:rtype: string
|
:rtype: string or tuple (if return_exitcode is set to True)
|
||||||
"""
|
"""
|
||||||
args = shlex.split(cmd)
|
args = cmd if shell else shlex.split(cmd)
|
||||||
logging.debug(cmd)
|
logging.debug(cmd)
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
|
@ -26,6 +36,7 @@ def execute(cmd, wait=True, ignore_errors=False, include_stderr=False, env=None)
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT if include_stderr else subprocess.PIPE,
|
stderr=subprocess.STDOUT if include_stderr else subprocess.PIPE,
|
||||||
env=env,
|
env=env,
|
||||||
|
shell=shell,
|
||||||
)
|
)
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
raise RuntimeError("{} not found".format(cmd))
|
raise RuntimeError("{} not found".format(cmd))
|
||||||
|
@ -34,11 +45,14 @@ def execute(cmd, wait=True, ignore_errors=False, include_stderr=False, env=None)
|
||||||
out, _ = proc.communicate()
|
out, _ = proc.communicate()
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
err = "{} exited with code {}".format(cmd, proc.returncode)
|
err = "{} exited with code {}".format(cmd, proc.returncode)
|
||||||
|
logging.warning(err)
|
||||||
if ignore_errors:
|
if ignore_errors:
|
||||||
return err
|
return (proc.returncode, err) if return_exitcode else err
|
||||||
raise RuntimeError(err)
|
raise RuntimeError(err)
|
||||||
return out.decode("utf-8")
|
res = out.decode("utf-8")
|
||||||
return ""
|
logging.debug(res)
|
||||||
|
return (proc.returncode, res) if return_exitcode else res
|
||||||
|
return (0, "") if return_exitcode else ""
|
||||||
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
||||||
|
|
|
@ -2,22 +2,13 @@ MAX_PERCENTS = 100.0
|
||||||
|
|
||||||
|
|
||||||
class Bar(object):
|
class Bar(object):
|
||||||
"""superclass"""
|
|
||||||
|
|
||||||
bars = None
|
bars = None
|
||||||
|
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
|
|
||||||
value (float): value between 0. and 100. meaning percents
|
|
||||||
"""
|
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
class HBar(Bar):
|
class HBar(Bar):
|
||||||
"""horizontal bar (1 char)"""
|
|
||||||
|
|
||||||
bars = [
|
bars = [
|
||||||
"\u2581",
|
"\u2581",
|
||||||
"\u2582",
|
"\u2582",
|
||||||
|
@ -29,20 +20,20 @@ class HBar(Bar):
|
||||||
"\u2588",
|
"\u2588",
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, value):
|
"""This class is a helper class used to draw horizontal bars - please use hbar directly
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
|
|
||||||
value (float): value between 0. and 100. meaning percents
|
:param value: percentage value to draw (float, between 0 and 100)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
super(HBar, self).__init__(value)
|
super(HBar, self).__init__(value)
|
||||||
self.step = MAX_PERCENTS / len(HBar.bars)
|
self.step = MAX_PERCENTS / len(HBar.bars)
|
||||||
|
|
||||||
def get_char(self):
|
def get_char(self):
|
||||||
"""
|
"""Returns the character representing the current object's value
|
||||||
Decide which char to draw
|
|
||||||
|
|
||||||
Return: str
|
:return: character representing the value passed during initialization
|
||||||
|
:rtype: string with one character
|
||||||
"""
|
"""
|
||||||
for i in range(len(HBar.bars)):
|
for i in range(len(HBar.bars)):
|
||||||
left = i * self.step
|
left = i * self.step
|
||||||
|
@ -53,13 +44,16 @@ class HBar(Bar):
|
||||||
|
|
||||||
|
|
||||||
def hbar(value):
|
def hbar(value):
|
||||||
"""wrapper function"""
|
""""Retrieves the horizontal bar character representing the input value
|
||||||
|
|
||||||
|
:param value: percentage value to draw (float, between 0 and 100)
|
||||||
|
:return: character representing the value passed during initialization
|
||||||
|
:rtype: string with one character
|
||||||
|
"""
|
||||||
return HBar(value).get_char()
|
return HBar(value).get_char()
|
||||||
|
|
||||||
|
|
||||||
class VBar(Bar):
|
class VBar(Bar):
|
||||||
"""vertical bar (can be more than 1 char)"""
|
|
||||||
|
|
||||||
bars = [
|
bars = [
|
||||||
"\u258f",
|
"\u258f",
|
||||||
"\u258e",
|
"\u258e",
|
||||||
|
@ -71,24 +65,24 @@ class VBar(Bar):
|
||||||
"\u2588",
|
"\u2588",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
"""This class is a helper class used to draw vertical bars - please use vbar directly
|
||||||
|
|
||||||
|
:param value: percentage value to draw (float, between 0 and 100)
|
||||||
|
:param width: maximum width of the bar in characters
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, value, width=1):
|
def __init__(self, value, width=1):
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
|
|
||||||
value (float): value between 0. and 100. meaning percents
|
|
||||||
|
|
||||||
width (int): width
|
|
||||||
"""
|
|
||||||
super(VBar, self).__init__(value)
|
super(VBar, self).__init__(value)
|
||||||
self.step = MAX_PERCENTS / (len(VBar.bars) * width)
|
self.step = MAX_PERCENTS / (len(VBar.bars) * width)
|
||||||
self.width = width
|
self.width = width
|
||||||
|
|
||||||
def get_chars(self):
|
"""Returns the characters representing the current object's value
|
||||||
"""
|
|
||||||
Decide which char to draw
|
|
||||||
|
|
||||||
Return: str
|
:return: characters representing the value passed during initialization
|
||||||
|
:rtype: string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def get_chars(self):
|
||||||
if self.value == 100:
|
if self.value == 100:
|
||||||
return self.bars[-1] * self.width
|
return self.bars[-1] * self.width
|
||||||
if self.width == 1:
|
if self.width == 1:
|
||||||
|
@ -111,16 +105,18 @@ class VBar(Bar):
|
||||||
|
|
||||||
|
|
||||||
def vbar(value, width):
|
def vbar(value, width):
|
||||||
"""wrapper function"""
|
"""Returns the characters representing the current object's value
|
||||||
|
|
||||||
|
:param value: percentage value to draw (float, between 0 and 100)
|
||||||
|
:param width: maximum width of the bar in characters
|
||||||
|
|
||||||
|
:return: characters representing the value passed during initialization
|
||||||
|
:rtype: string
|
||||||
|
"""
|
||||||
return VBar(value, width).get_chars()
|
return VBar(value, width).get_chars()
|
||||||
|
|
||||||
|
|
||||||
class BrailleGraph(object):
|
class BrailleGraph(object):
|
||||||
"""
|
|
||||||
graph using Braille chars
|
|
||||||
scaled to passed values
|
|
||||||
"""
|
|
||||||
|
|
||||||
chars = {
|
chars = {
|
||||||
(0, 0): " ",
|
(0, 0): " ",
|
||||||
(1, 0): "\u2840",
|
(1, 0): "\u2840",
|
||||||
|
@ -149,12 +145,12 @@ class BrailleGraph(object):
|
||||||
(4, 4): "\u28ff",
|
(4, 4): "\u28ff",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, values):
|
"""This class is a helper class used to draw braille graphs - please use braille directly
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
|
|
||||||
values (list): list of values
|
:param values: values to draw
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, values):
|
||||||
self.values = values
|
self.values = values
|
||||||
# length of values list must be even
|
# length of values list must be even
|
||||||
# because one Braille char displays two values
|
# because one Braille char displays two values
|
||||||
|
@ -165,15 +161,6 @@ class BrailleGraph(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_height(value, unit):
|
def get_height(value, unit):
|
||||||
"""
|
|
||||||
Compute height of a value relative to unit
|
|
||||||
|
|
||||||
Args:
|
|
||||||
|
|
||||||
value (number): value
|
|
||||||
|
|
||||||
unit (number): unit
|
|
||||||
"""
|
|
||||||
if value < unit / 10.0:
|
if value < unit / 10.0:
|
||||||
return 0
|
return 0
|
||||||
elif value <= unit:
|
elif value <= unit:
|
||||||
|
@ -186,11 +173,6 @@ class BrailleGraph(object):
|
||||||
return 4
|
return 4
|
||||||
|
|
||||||
def get_steps(self):
|
def get_steps(self):
|
||||||
"""
|
|
||||||
Convert the list of values to a list of steps
|
|
||||||
|
|
||||||
Return: list
|
|
||||||
"""
|
|
||||||
maxval = max(self.values)
|
maxval = max(self.values)
|
||||||
unit = maxval / 4.0
|
unit = maxval / 4.0
|
||||||
if unit == 0:
|
if unit == 0:
|
||||||
|
@ -201,11 +183,6 @@ class BrailleGraph(object):
|
||||||
return stepslist
|
return stepslist
|
||||||
|
|
||||||
def get_chars(self):
|
def get_chars(self):
|
||||||
"""
|
|
||||||
Decide which chars to draw
|
|
||||||
|
|
||||||
Return: str
|
|
||||||
"""
|
|
||||||
chars = []
|
chars = []
|
||||||
for part in self.parts:
|
for part in self.parts:
|
||||||
chars.append(BrailleGraph.chars[part])
|
chars.append(BrailleGraph.chars[part])
|
||||||
|
@ -213,7 +190,6 @@ class BrailleGraph(object):
|
||||||
|
|
||||||
|
|
||||||
def braille(values):
|
def braille(values):
|
||||||
"""wrapper function"""
|
|
||||||
return BrailleGraph(values).get_chars()
|
return BrailleGraph(values).get_chars()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,51 +2,82 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
try:
|
|
||||||
import Tkinter as tk
|
|
||||||
except ImportError:
|
|
||||||
# python 3
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
|
|
||||||
class menu(object):
|
class menu(object):
|
||||||
|
"""Draws a hierarchical popup menu
|
||||||
|
|
||||||
|
:param parent: If given, this menu is a leave of the "parent" menu
|
||||||
|
:param leave: If set to True, close this menu when mouse leaves the area (defaults to True)
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None, leave=True):
|
def __init__(self, parent=None, leave=True):
|
||||||
if not parent:
|
if not parent:
|
||||||
self._root = tk.Tk()
|
self._root = tk.Tk()
|
||||||
self._root.withdraw()
|
self._root.withdraw()
|
||||||
self._menu = tk.Menu(self._root, tearoff=0)
|
self._menu = tk.Menu(self._root, tearoff=0)
|
||||||
self._menu.bind("<FocusOut>", self._on_focus_out)
|
self._menu.bind("<FocusOut>", self.__on_focus_out)
|
||||||
else:
|
else:
|
||||||
self._root = parent.root()
|
self._root = parent.root()
|
||||||
self._root.withdraw()
|
self._root.withdraw()
|
||||||
self._menu = tk.Menu(self._root, tearoff=0)
|
self._menu = tk.Menu(self._root, tearoff=0)
|
||||||
self._menu.bind("<FocusOut>", self._on_focus_out)
|
self._menu.bind("<FocusOut>", self.__on_focus_out)
|
||||||
if leave:
|
if leave:
|
||||||
self._menu.bind("<Leave>", self._on_focus_out)
|
self._menu.bind("<Leave>", self.__on_focus_out)
|
||||||
|
|
||||||
|
"""Returns the root node of this menu
|
||||||
|
|
||||||
|
:return: root node
|
||||||
|
"""
|
||||||
|
|
||||||
def root(self):
|
def root(self):
|
||||||
return self._root
|
return self._root
|
||||||
|
|
||||||
|
"""Returns the menu
|
||||||
|
|
||||||
|
:return: menu
|
||||||
|
"""
|
||||||
|
|
||||||
def menu(self):
|
def menu(self):
|
||||||
return self._menu
|
return self._menu
|
||||||
|
|
||||||
def _on_focus_out(self, event=None):
|
def __on_focus_out(self, event=None):
|
||||||
self._root.destroy()
|
self._root.destroy()
|
||||||
|
|
||||||
def _on_click(self, callback):
|
def __on_click(self, callback):
|
||||||
self._root.destroy()
|
self._root.destroy()
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
|
"""Adds a cascading submenu to the current menu
|
||||||
|
|
||||||
|
:param menuitem: label to display for the submenu
|
||||||
|
:param submenu: submenu to show
|
||||||
|
"""
|
||||||
|
|
||||||
def add_cascade(self, menuitem, submenu):
|
def add_cascade(self, menuitem, submenu):
|
||||||
self._menu.add_cascade(label=menuitem, menu=submenu.menu())
|
self._menu.add_cascade(label=menuitem, menu=submenu.menu())
|
||||||
|
|
||||||
|
"""Adds an item to the current menu
|
||||||
|
|
||||||
|
:param menuitem: label to display for the entry
|
||||||
|
:param callback: method to invoke on click
|
||||||
|
"""
|
||||||
|
|
||||||
def add_menuitem(self, menuitem, callback):
|
def add_menuitem(self, menuitem, callback):
|
||||||
self._menu.add_command(
|
self._menu.add_command(
|
||||||
label=menuitem, command=functools.partial(self._on_click, callback)
|
label=menuitem, command=functools.partial(self.__on_click, callback)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
"""Shows this menu
|
||||||
|
|
||||||
|
:param event: i3wm event that triggered the menu (dict that contains "x" and "y" fields)
|
||||||
|
:param offset_x: x-axis offset from mouse position for the menu (defaults to 0)
|
||||||
|
:param offset_y: y-axis offset from mouse position for the menu (defaults to 0)
|
||||||
|
"""
|
||||||
|
|
||||||
def show(self, event, offset_x=0, offset_y=0):
|
def show(self, event, offset_x=0, offset_y=0):
|
||||||
try:
|
try:
|
||||||
self._menu.tk_popup(event["x"] + offset_x, event["y"] + offset_y)
|
self._menu.tk_popup(event["x"] + offset_x, event["y"] + offset_y)
|
||||||
|
|
|
@ -4,5 +4,4 @@ API Reference
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 4
|
:maxdepth: 4
|
||||||
|
|
||||||
src/core
|
src/bumblebee_status
|
||||||
src/util
|
|
||||||
|
|
|
@ -9,11 +9,13 @@
|
||||||
- use __ for private
|
- use __ for private
|
||||||
|
|
||||||
## Improvements
|
## Improvements
|
||||||
|
- app launcher (list of apps, themeable)
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
- themes: use colors to improve theme readability
|
- themes: use colors to improve theme readability
|
||||||
- convert some stuff to simple attributes to reduce LOCs
|
- convert some stuff to simple attributes to reduce LOCs
|
||||||
- use widget index for bumblebee-ctl as alternative (??)
|
- use widget index for bumblebee-ctl as alternative (??)
|
||||||
|
- use pytest?
|
||||||
|
|
||||||
# documentation
|
# documentation
|
||||||
Add info about error widget and events for error logging
|
Add info about error widget and events for error logging
|
||||||
|
|
78
docs/src/bumblebee_status.core.rst
Normal file
78
docs/src/bumblebee_status.core.rst
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
bumblebee\_status.core package
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
bumblebee\_status.core.config module
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.core.config
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.core.decorators module
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.core.decorators
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.core.event module
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.core.event
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.core.input module
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.core.input
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.core.module module
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.core.module
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.core.output module
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.core.output
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.core.theme module
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.core.theme
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.core.widget module
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.core.widget
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.core
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
31
docs/src/bumblebee_status.rst
Normal file
31
docs/src/bumblebee_status.rst
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
bumblebee\_status package
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Subpackages
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
|
||||||
|
bumblebee_status.core
|
||||||
|
bumblebee_status.util
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
bumblebee\_status.discover module
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.discover
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
70
docs/src/bumblebee_status.util.rst
Normal file
70
docs/src/bumblebee_status.util.rst
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
bumblebee\_status.util package
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
bumblebee\_status.util.algorithm module
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.util.algorithm
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.util.cli module
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.util.cli
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.util.format module
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.util.format
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.util.graph module
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.util.graph
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.util.location module
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.util.location
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.util.popup module
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.util.popup
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
bumblebee\_status.util.store module
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.util.store
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: bumblebee_status.util
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
|
@ -1,78 +0,0 @@
|
||||||
core package
|
|
||||||
============
|
|
||||||
|
|
||||||
Submodules
|
|
||||||
----------
|
|
||||||
|
|
||||||
core.config module
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. automodule:: core.config
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
core.decorators module
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
.. automodule:: core.decorators
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
core.event module
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. automodule:: core.event
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
core.input module
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. automodule:: core.input
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
core.module module
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. automodule:: core.module
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
core.output module
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. automodule:: core.output
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
core.theme module
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. automodule:: core.theme
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
core.widget module
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. automodule:: core.widget
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
|
|
||||||
Module contents
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. automodule:: core
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
|
@ -1,70 +0,0 @@
|
||||||
util package
|
|
||||||
============
|
|
||||||
|
|
||||||
Submodules
|
|
||||||
----------
|
|
||||||
|
|
||||||
util.algorithm module
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
.. automodule:: util.algorithm
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
util.cli module
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. automodule:: util.cli
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
util.format module
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. automodule:: util.format
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
util.graph module
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. automodule:: util.graph
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
util.location module
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
.. automodule:: util.location
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
util.popup module
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. automodule:: util.popup
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
util.store module
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. automodule:: util.store
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
|
|
||||||
Module contents
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. automodule:: util
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
|
@ -88,6 +88,11 @@ List of available themes
|
||||||
|
|
||||||
Nord Powerline (-t nord-powerline) (contributed by `uselessthird <https://github.com/uselessthird>`__)
|
Nord Powerline (-t nord-powerline) (contributed by `uselessthird <https://github.com/uselessthird>`__)
|
||||||
|
|
||||||
|
.. figure:: ../screenshots/themes/night-powerline.png
|
||||||
|
:alt: Night Powerline
|
||||||
|
|
||||||
|
Night Powerline (-t night-powerline) (contributed by `LtPeriwinkle <https://github.com/LtPeriwinkle>`__)
|
||||||
|
|
||||||
.. figure:: ../screenshots/themes/default.png
|
.. figure:: ../screenshots/themes/default.png
|
||||||
:alt: Default
|
:alt: Default
|
||||||
|
|
||||||
|
|
BIN
screenshots/themes/night-powerline.png
Normal file
BIN
screenshots/themes/night-powerline.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -39,9 +39,6 @@ packages = find:
|
||||||
scripts =
|
scripts =
|
||||||
./bumblebee-status
|
./bumblebee-status
|
||||||
./bumblebee-ctl
|
./bumblebee-ctl
|
||||||
./bin/load-i3-bars.sh
|
|
||||||
./bin/pacman-updates
|
|
||||||
./bin/toggle-display.sh
|
|
||||||
|
|
||||||
[versioneer]
|
[versioneer]
|
||||||
VCS = git
|
VCS = git
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -53,8 +53,10 @@ setup(
|
||||||
version=versioneer.get_version(),
|
version=versioneer.get_version(),
|
||||||
cmdclass=versioneer.get_cmdclass(),
|
cmdclass=versioneer.get_cmdclass(),
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
test_suite="tests",
|
||||||
data_files=[
|
data_files=[
|
||||||
("share/bumblebee-status/themes", glob.glob("themes/*.json")),
|
("share/bumblebee-status/themes", glob.glob("themes/*.json")),
|
||||||
("share/bumblebee-status/themes/icons", glob.glob("themes/icons/*.json")),
|
("share/bumblebee-status/themes/icons", glob.glob("themes/icons/*.json")),
|
||||||
|
("share/bumblebee-status/utility", glob.glob("bin/*")),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -52,8 +52,8 @@ class config(unittest.TestCase):
|
||||||
|
|
||||||
def test_logfile(self):
|
def test_logfile(self):
|
||||||
cfg = core.config.Config(["-f", "my-custom-logfile"])
|
cfg = core.config.Config(["-f", "my-custom-logfile"])
|
||||||
self.assertEquals(None, self.defaultConfig.logfile())
|
self.assertEqual(None, self.defaultConfig.logfile())
|
||||||
self.assertEquals("my-custom-logfile", cfg.logfile())
|
self.assertEqual("my-custom-logfile", cfg.logfile())
|
||||||
|
|
||||||
def test_all_modules(self):
|
def test_all_modules(self):
|
||||||
modules = core.config.all_modules()
|
modules = core.config.all_modules()
|
||||||
|
|
|
@ -7,6 +7,7 @@ import core.config
|
||||||
|
|
||||||
|
|
||||||
class TestModule(core.module.Module):
|
class TestModule(core.module.Module):
|
||||||
|
@core.decorators.never
|
||||||
def __init__(self, config=None, theme=None):
|
def __init__(self, config=None, theme=None):
|
||||||
config = core.config.Config([])
|
config = core.config.Config([])
|
||||||
super().__init__(config, theme, core.widget.Widget(self.get))
|
super().__init__(config, theme, core.widget.Widget(self.get))
|
||||||
|
@ -24,6 +25,10 @@ class config(unittest.TestCase):
|
||||||
self.width = 10
|
self.width = 10
|
||||||
self.module.set("scrolling.width", self.width)
|
self.module.set("scrolling.width", self.width)
|
||||||
|
|
||||||
|
def test_never(self):
|
||||||
|
self.module = TestModule()
|
||||||
|
self.assertEqual("never", self.module.parameter("interval"))
|
||||||
|
|
||||||
def test_no_text(self):
|
def test_no_text(self):
|
||||||
self.assertEqual("", self.module.text)
|
self.assertEqual("", self.module.text)
|
||||||
self.assertEqual("", self.module.get(self.widget))
|
self.assertEqual("", self.module.get(self.widget))
|
||||||
|
@ -70,5 +75,25 @@ class config(unittest.TestCase):
|
||||||
self.module.text = "wxyz"
|
self.module.text = "wxyz"
|
||||||
self.assertEqual("wx", self.module.get(self.widget))
|
self.assertEqual("wx", self.module.get(self.widget))
|
||||||
|
|
||||||
|
def test_minimum_changed_data(self):
|
||||||
|
self.module.text = "this is a sample song (0:00)"
|
||||||
|
self.module.set("scrolling.width", 10)
|
||||||
|
self.assertEqual(self.module.text[0:10], self.module.get(self.widget))
|
||||||
|
self.module.text = "this is a sample song (0:01)"
|
||||||
|
self.assertEqual(self.module.text[1:11], self.module.get(self.widget))
|
||||||
|
self.module.text = "this is a sample song (0:12)"
|
||||||
|
self.assertEqual(self.module.text[2:12], self.module.get(self.widget))
|
||||||
|
self.module.text = "this is a different song (0:12)"
|
||||||
|
self.assertEqual(self.module.text[0:10], self.module.get(self.widget))
|
||||||
|
|
||||||
|
def test_n_plus_one(self):
|
||||||
|
self.module.text = "10 letters"
|
||||||
|
self.module.set("scrolling.width", 9)
|
||||||
|
self.assertEqual(self.module.text[0:9], self.module.get(self.widget))
|
||||||
|
self.assertEqual(self.module.text[1:10], self.module.get(self.widget))
|
||||||
|
self.assertEqual(self.module.text[0:9], self.module.get(self.widget))
|
||||||
|
self.assertEqual(self.module.text[1:10], self.module.get(self.widget))
|
||||||
|
self.assertEqual(self.module.text[0:9], self.module.get(self.widget))
|
||||||
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
||||||
|
|
|
@ -70,7 +70,9 @@ class config(unittest.TestCase):
|
||||||
self.inputObject, self.someEvent["button"], self.someCommand
|
self.inputObject, self.someEvent["button"], self.someCommand
|
||||||
)
|
)
|
||||||
core.input.trigger(self.someEvent)
|
core.input.trigger(self.someEvent)
|
||||||
cli.execute.assert_called_once_with(self.someCommand, wait=False)
|
cli.execute.assert_called_once_with(
|
||||||
|
self.someCommand, wait=False, shell=True
|
||||||
|
)
|
||||||
|
|
||||||
def test_non_existent_callback(self):
|
def test_non_existent_callback(self):
|
||||||
with unittest.mock.patch("core.input.util.cli") as cli:
|
with unittest.mock.patch("core.input.util.cli") as cli:
|
||||||
|
|
|
@ -6,6 +6,7 @@ import shlex
|
||||||
import core.module
|
import core.module
|
||||||
import core.widget
|
import core.widget
|
||||||
import core.config
|
import core.config
|
||||||
|
import core.input
|
||||||
|
|
||||||
|
|
||||||
class TestModule(core.module.Module):
|
class TestModule(core.module.Module):
|
||||||
|
@ -137,5 +138,21 @@ class module(unittest.TestCase):
|
||||||
self.assertEqual(None, module.threshold_state(80, 80, 100))
|
self.assertEqual(None, module.threshold_state(80, 80, 100))
|
||||||
self.assertEqual(None, module.threshold_state(10, 80, 100))
|
self.assertEqual(None, module.threshold_state(10, 80, 100))
|
||||||
|
|
||||||
|
def test_configured_callbacks(self):
|
||||||
|
cfg = core.config.Config([])
|
||||||
|
module = TestModule(config=cfg, widgets=[self.someWidget, self.anotherWidget])
|
||||||
|
|
||||||
|
cmd = "sample-tool arg1 arg2 arg3"
|
||||||
|
module.set("left-click", cmd)
|
||||||
|
module.register_callbacks()
|
||||||
|
|
||||||
|
with unittest.mock.patch("core.input.util.cli") as cli:
|
||||||
|
cli.execute.return_value = ""
|
||||||
|
core.input.trigger(
|
||||||
|
{"button": core.input.LEFT_MOUSE, "instance": module.id,}
|
||||||
|
)
|
||||||
|
|
||||||
|
cli.execute.assert_called_once_with(cmd, wait=False, shell=True)
|
||||||
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
||||||
|
|
63
themes/night-powerline.json
Normal file
63
themes/night-powerline.json
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"icons": [ "awesome-fonts" ],
|
||||||
|
"defaults": {
|
||||||
|
"separator-block-width": 0,
|
||||||
|
"warning": {
|
||||||
|
"fg": "#afafaf",
|
||||||
|
"bg": "#4d401d"
|
||||||
|
},
|
||||||
|
"critical": {
|
||||||
|
"fg": "#afafaf",
|
||||||
|
"bg": "#6e0b0a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cycle": [
|
||||||
|
{ "fg": "#afafaf", "bg": "#0f0f0f" },
|
||||||
|
{ "fg": "#afafaf", "bg": "#1f1f1f" },
|
||||||
|
{ "fg": "#afafaf", "bg": "#2b2b2b" },
|
||||||
|
{ "fg": "#afafaf", "bg": "#1e1e1e" }
|
||||||
|
],
|
||||||
|
"dnf": {
|
||||||
|
"good": {
|
||||||
|
"fg": "#afafaf",
|
||||||
|
"bg": "#26362d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apt": {
|
||||||
|
"good": {
|
||||||
|
"fg": "#afafaf",
|
||||||
|
"bg": "#26362d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pacman": {
|
||||||
|
"good": {
|
||||||
|
"fg": "#b2b2b2",
|
||||||
|
"bg": "#26362d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"battery": {
|
||||||
|
"charged": {
|
||||||
|
"fg": "#afafaf",
|
||||||
|
"bg": "#26362d"
|
||||||
|
},
|
||||||
|
"AC": {
|
||||||
|
"fg": "#afafaf",
|
||||||
|
"bg": "#26362d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pomodoro": {
|
||||||
|
"paused": {
|
||||||
|
"fg": "#afafaf",
|
||||||
|
"bg": "#b58900"
|
||||||
|
},
|
||||||
|
"work": {
|
||||||
|
"fg": "#1d2021",
|
||||||
|
"bg": "#b8bb26"
|
||||||
|
},
|
||||||
|
"break": {
|
||||||
|
"fg": "#afafaf",
|
||||||
|
"bg": "#26362d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue