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"
|
||||
exclude_paths:
|
||||
- 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
|
||||
.vscode/
|
||||
|
||||
# mypy cache
|
||||
.mypy_cache
|
||||
|
|
10
.travis.yml
10
.travis.yml
|
@ -8,17 +8,9 @@ python:
|
|||
- "3.8"
|
||||
before_install:
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get install -y task libdbus-1-dev
|
||||
install:
|
||||
- pip install i3ipc
|
||||
- pip install psutil
|
||||
- pip install netifaces
|
||||
- pip install -U coverage==4.3
|
||||
- pip install coverage
|
||||
- pip install codeclimate-test-reporter
|
||||
- pip install taskw
|
||||
- pip install pytz
|
||||
- pip install tzlocal
|
||||
- pip install dbus-python
|
||||
- pip install coverage
|
||||
script:
|
||||
- coverage run -m unittest discover -v
|
||||
|
|
|
@ -10,6 +10,7 @@ import logging
|
|||
import threading
|
||||
|
||||
import bumblebee_status.discover
|
||||
|
||||
bumblebee_status.discover.discover()
|
||||
|
||||
import core.config
|
||||
|
@ -101,6 +102,7 @@ def main():
|
|||
|
||||
for module in config.modules():
|
||||
modules.append(core.module.load(module, config, theme))
|
||||
modules[-1].register_callbacks()
|
||||
|
||||
if config.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():
|
||||
"""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 = {}
|
||||
|
||||
for path in [modules.core.__file__, modules.contrib.__file__]:
|
||||
|
@ -127,6 +131,11 @@ class print_usage(argparse.Action):
|
|||
|
||||
|
||||
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):
|
||||
super(Config, self).__init__()
|
||||
|
||||
|
@ -202,6 +211,11 @@ class Config(util.store.Store):
|
|||
key, value = param.split("=", 1)
|
||||
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):
|
||||
if os.path.exists(filename):
|
||||
log.info("loading {}".format(filename))
|
||||
|
@ -212,27 +226,75 @@ class Config(util.store.Store):
|
|||
for key, value in tmp.items("module-parameters"):
|
||||
self.set(key, value)
|
||||
|
||||
"""Returns a list of configured modules
|
||||
|
||||
:return: list of configured (active) modules
|
||||
:rtype: list of strings
|
||||
"""
|
||||
|
||||
def modules(self):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
return self.__args.right_to_left
|
||||
|
||||
"""Returns the logfile location
|
||||
|
||||
:return: location where the logfile should be written
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
def logfile(self):
|
||||
return self.__args.logfile
|
||||
|
||||
"""Returns the configured theme name
|
||||
|
||||
:return: name of the configured theme
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
def theme(self):
|
||||
return self.__args.theme
|
||||
|
||||
"""Returns the configured iconset name
|
||||
|
||||
:return: name of the configured iconset
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
def iconset(self):
|
||||
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):
|
||||
return name in self.__args.autohide
|
||||
|
||||
|
|
|
@ -1,5 +1,19 @@
|
|||
import difflib
|
||||
import logging
|
||||
|
||||
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 call_init(obj, *args, **kwargs):
|
||||
|
@ -10,6 +24,16 @@ def never(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 decorator_init(init):
|
||||
def call_init(obj, *args, **kwargs):
|
||||
|
@ -22,13 +46,30 @@ def every(hours=0, minutes=0, seconds=0):
|
|||
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 wrapper(module, widget):
|
||||
text = func(module, widget)
|
||||
if not 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.direction", "right")
|
||||
widget.set("__content__", text)
|
||||
|
@ -45,9 +86,10 @@ def scrollable(func):
|
|||
direction = widget.get("scrolling.direction", "right")
|
||||
|
||||
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")
|
||||
else:
|
||||
scroll_speed = -scroll_speed
|
||||
|
||||
next_start = start + scroll_speed
|
||||
if next_start + width > len(text):
|
||||
|
|
|
@ -36,20 +36,20 @@ def __event_id(obj_id, button):
|
|||
return "{}::{}".format(obj_id, button_name(button))
|
||||
|
||||
|
||||
def __execute(cmd):
|
||||
def __execute(cmd, wait=False):
|
||||
try:
|
||||
util.cli.execute(cmd, wait=False)
|
||||
util.cli.execute(cmd, wait=wait, shell=True)
|
||||
except Exception as 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)
|
||||
logging.debug("registering callback {}".format(event_id))
|
||||
if callable(cmd):
|
||||
core.event.register(event_id, cmd)
|
||||
else:
|
||||
core.event.register(event_id, lambda _: __execute(cmd))
|
||||
core.event.register(event_id, lambda _: __execute(cmd, wait))
|
||||
|
||||
|
||||
def trigger(event):
|
||||
|
|
|
@ -2,10 +2,13 @@ import os
|
|||
import importlib
|
||||
import logging
|
||||
|
||||
import core.config
|
||||
import core.input
|
||||
import core.widget
|
||||
import core.decorators
|
||||
|
||||
import util.format
|
||||
|
||||
try:
|
||||
error = ModuleNotFoundError("")
|
||||
except Exception as e:
|
||||
|
@ -14,6 +17,17 @@ except Exception as e:
|
|||
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):
|
||||
error = None
|
||||
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):
|
||||
"""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=[]):
|
||||
super().__init__()
|
||||
self.__config = config
|
||||
|
@ -51,23 +72,55 @@ class Module(core.input.Object):
|
|||
for widget in self.__widgets:
|
||||
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):
|
||||
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):
|
||||
value = default
|
||||
|
||||
for prefix in [self.name, self.module_name, self.alias]:
|
||||
value = self.__config.get("{}.{}".format(prefix, key), value)
|
||||
# TODO retrieve from config file
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
try:
|
||||
self.update()
|
||||
|
@ -77,17 +130,42 @@ class Module(core.input.Object):
|
|||
self.__widgets = [module.widget()]
|
||||
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):
|
||||
return self.__widgets
|
||||
|
||||
"""Removes all widgets of this module"""
|
||||
|
||||
def clear_widgets(self):
|
||||
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):
|
||||
widget = core.widget.Widget(full_text=full_text, name=name, module=self)
|
||||
self.widgets().append(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):
|
||||
if not name:
|
||||
return self.widgets()[0]
|
||||
|
@ -97,9 +175,27 @@ class Module(core.input.Object):
|
|||
return w
|
||||
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):
|
||||
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):
|
||||
if value > float(self.parameter("critical", crit)):
|
||||
return "critical"
|
||||
|
@ -107,16 +203,49 @@ class Module(core.input.Object):
|
|||
return "warning"
|
||||
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):
|
||||
"""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):
|
||||
super().__init__(config, theme, core.widget.Widget(self.full_text))
|
||||
self.__module = module
|
||||
self.__error = error
|
||||
|
||||
"""Returns the error message
|
||||
:param widget: the error widget to display
|
||||
"""
|
||||
|
||||
def full_text(self, widget):
|
||||
return "{}: {}".format(self.__module, self.__error)
|
||||
|
||||
"""Overriden state, always returns critical (it *is* an error, after all"""
|
||||
|
||||
def state(self, widget):
|
||||
return ["critical"]
|
||||
|
||||
|
|
|
@ -9,4 +9,19 @@ def discover():
|
|||
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
|
||||
|
|
|
@ -33,13 +33,18 @@ class Module(core.module.Module):
|
|||
return self.__packages == 0 and not self.__error
|
||||
|
||||
def update(self):
|
||||
try:
|
||||
result = util.cli.execute("checkupdates")
|
||||
self.__packages = len(result.split("\n")) - 1
|
||||
self.__error = False
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
code, result = util.cli.execute(
|
||||
"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
|
||||
log.error("checkupdates exited with {}: {}".format(code, result))
|
||||
|
||||
def state(self, widget):
|
||||
if self.__error:
|
||||
|
|
|
@ -115,7 +115,7 @@ class Module(core.module.Module):
|
|||
for battery in glob.glob("/sys/class/power_supply/BAT*")
|
||||
]
|
||||
if len(self._batteries) == 0:
|
||||
raise Exceptions("no batteries configured/found")
|
||||
raise Exception("no batteries configured/found")
|
||||
core.input.register(
|
||||
self, button=core.input.LEFT_MOUSE, cmd="gnome-power-statistics"
|
||||
)
|
||||
|
|
|
@ -22,15 +22,15 @@ import core.decorators
|
|||
import util.cli
|
||||
import util.format
|
||||
|
||||
from bumblebee_status.discover import utility
|
||||
|
||||
# list of repositories.
|
||||
# the last one should always be other
|
||||
repos = ["core", "extra", "community", "multilib", "testing", "other"]
|
||||
|
||||
|
||||
def get_pacman_info(widget, path):
|
||||
cmd = "{}/../../bin/pacman-updates".format(path)
|
||||
if not os.path.exists(cmd):
|
||||
cmd = "/usr/share/bumblebee-status/bin/pacman-update"
|
||||
cmd = utility("pacman-updates")
|
||||
result = util.cli.execute(cmd, ignore_errors=True)
|
||||
|
||||
count = len(repos) * [0]
|
||||
|
|
|
@ -24,6 +24,8 @@ import core.module
|
|||
import core.input
|
||||
import core.decorators
|
||||
|
||||
from bumblebee_status.discover import utility
|
||||
|
||||
import util.cli
|
||||
import util.format
|
||||
|
||||
|
@ -36,8 +38,7 @@ except:
|
|||
class Module(core.module.Module):
|
||||
@core.decorators.every(seconds=5) # takes up to 5s to detect a new screen
|
||||
def __init__(self, config, theme):
|
||||
widgets = []
|
||||
super().__init__(config, theme, widgets)
|
||||
super().__init__(config, theme, [])
|
||||
|
||||
self._autoupdate = util.format.asbool(self.parameter("autoupdate", True))
|
||||
self._needs_update = True
|
||||
|
@ -85,10 +86,9 @@ class Module(core.module.Module):
|
|||
|
||||
def _toggle(self, event):
|
||||
self._refresh(self, event)
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
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:
|
||||
toggle_cmd = "xrandr"
|
||||
|
||||
|
|
|
@ -4,7 +4,15 @@ import subprocess
|
|||
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
|
||||
|
||||
: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 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 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
|
||||
|
||||
:return: output of cmd, or stderr, if ignore_errors is True and the command failed
|
||||
:rtype: string
|
||||
: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 or tuple (if return_exitcode is set to True)
|
||||
"""
|
||||
args = shlex.split(cmd)
|
||||
args = cmd if shell else shlex.split(cmd)
|
||||
logging.debug(cmd)
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
|
@ -26,6 +36,7 @@ def execute(cmd, wait=True, ignore_errors=False, include_stderr=False, env=None)
|
|||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT if include_stderr else subprocess.PIPE,
|
||||
env=env,
|
||||
shell=shell,
|
||||
)
|
||||
except FileNotFoundError as e:
|
||||
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()
|
||||
if proc.returncode != 0:
|
||||
err = "{} exited with code {}".format(cmd, proc.returncode)
|
||||
logging.warning(err)
|
||||
if ignore_errors:
|
||||
return err
|
||||
return (proc.returncode, err) if return_exitcode else err
|
||||
raise RuntimeError(err)
|
||||
return out.decode("utf-8")
|
||||
return ""
|
||||
res = out.decode("utf-8")
|
||||
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
|
||||
|
|
|
@ -2,22 +2,13 @@ MAX_PERCENTS = 100.0
|
|||
|
||||
|
||||
class Bar(object):
|
||||
"""superclass"""
|
||||
|
||||
bars = None
|
||||
|
||||
def __init__(self, value):
|
||||
"""
|
||||
Args:
|
||||
|
||||
value (float): value between 0. and 100. meaning percents
|
||||
"""
|
||||
self.value = value
|
||||
|
||||
|
||||
class HBar(Bar):
|
||||
"""horizontal bar (1 char)"""
|
||||
|
||||
bars = [
|
||||
"\u2581",
|
||||
"\u2582",
|
||||
|
@ -29,20 +20,20 @@ class HBar(Bar):
|
|||
"\u2588",
|
||||
]
|
||||
|
||||
def __init__(self, value):
|
||||
"""
|
||||
Args:
|
||||
"""This class is a helper class used to draw horizontal bars - please use hbar directly
|
||||
|
||||
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)
|
||||
self.step = MAX_PERCENTS / len(HBar.bars)
|
||||
|
||||
def get_char(self):
|
||||
"""
|
||||
Decide which char to draw
|
||||
"""Returns the character representing the current object's value
|
||||
|
||||
Return: str
|
||||
:return: character representing the value passed during initialization
|
||||
:rtype: string with one character
|
||||
"""
|
||||
for i in range(len(HBar.bars)):
|
||||
left = i * self.step
|
||||
|
@ -53,13 +44,16 @@ class HBar(Bar):
|
|||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
class VBar(Bar):
|
||||
"""vertical bar (can be more than 1 char)"""
|
||||
|
||||
bars = [
|
||||
"\u258f",
|
||||
"\u258e",
|
||||
|
@ -71,24 +65,24 @@ class VBar(Bar):
|
|||
"\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):
|
||||
"""
|
||||
Args:
|
||||
|
||||
value (float): value between 0. and 100. meaning percents
|
||||
|
||||
width (int): width
|
||||
"""
|
||||
super(VBar, self).__init__(value)
|
||||
self.step = MAX_PERCENTS / (len(VBar.bars) * width)
|
||||
self.width = width
|
||||
|
||||
def get_chars(self):
|
||||
"""
|
||||
Decide which char to draw
|
||||
"""Returns the characters representing the current object's value
|
||||
|
||||
Return: str
|
||||
:return: characters representing the value passed during initialization
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
def get_chars(self):
|
||||
if self.value == 100:
|
||||
return self.bars[-1] * self.width
|
||||
if self.width == 1:
|
||||
|
@ -111,16 +105,18 @@ class VBar(Bar):
|
|||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
class BrailleGraph(object):
|
||||
"""
|
||||
graph using Braille chars
|
||||
scaled to passed values
|
||||
"""
|
||||
|
||||
chars = {
|
||||
(0, 0): " ",
|
||||
(1, 0): "\u2840",
|
||||
|
@ -149,12 +145,12 @@ class BrailleGraph(object):
|
|||
(4, 4): "\u28ff",
|
||||
}
|
||||
|
||||
def __init__(self, values):
|
||||
"""
|
||||
Args:
|
||||
"""This class is a helper class used to draw braille graphs - please use braille directly
|
||||
|
||||
values (list): list of values
|
||||
:param values: values to draw
|
||||
"""
|
||||
|
||||
def __init__(self, values):
|
||||
self.values = values
|
||||
# length of values list must be even
|
||||
# because one Braille char displays two values
|
||||
|
@ -165,15 +161,6 @@ class BrailleGraph(object):
|
|||
|
||||
@staticmethod
|
||||
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:
|
||||
return 0
|
||||
elif value <= unit:
|
||||
|
@ -186,11 +173,6 @@ class BrailleGraph(object):
|
|||
return 4
|
||||
|
||||
def get_steps(self):
|
||||
"""
|
||||
Convert the list of values to a list of steps
|
||||
|
||||
Return: list
|
||||
"""
|
||||
maxval = max(self.values)
|
||||
unit = maxval / 4.0
|
||||
if unit == 0:
|
||||
|
@ -201,11 +183,6 @@ class BrailleGraph(object):
|
|||
return stepslist
|
||||
|
||||
def get_chars(self):
|
||||
"""
|
||||
Decide which chars to draw
|
||||
|
||||
Return: str
|
||||
"""
|
||||
chars = []
|
||||
for part in self.parts:
|
||||
chars.append(BrailleGraph.chars[part])
|
||||
|
@ -213,7 +190,6 @@ class BrailleGraph(object):
|
|||
|
||||
|
||||
def braille(values):
|
||||
"""wrapper function"""
|
||||
return BrailleGraph(values).get_chars()
|
||||
|
||||
|
||||
|
|
|
@ -2,51 +2,82 @@
|
|||
|
||||
import logging
|
||||
|
||||
try:
|
||||
import Tkinter as tk
|
||||
except ImportError:
|
||||
# python 3
|
||||
import tkinter as tk
|
||||
|
||||
import functools
|
||||
|
||||
|
||||
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):
|
||||
if not parent:
|
||||
self._root = tk.Tk()
|
||||
self._root.withdraw()
|
||||
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:
|
||||
self._root = parent.root()
|
||||
self._root.withdraw()
|
||||
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:
|
||||
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):
|
||||
return self._root
|
||||
|
||||
"""Returns the menu
|
||||
|
||||
:return: menu
|
||||
"""
|
||||
|
||||
def menu(self):
|
||||
return self._menu
|
||||
|
||||
def _on_focus_out(self, event=None):
|
||||
def __on_focus_out(self, event=None):
|
||||
self._root.destroy()
|
||||
|
||||
def _on_click(self, callback):
|
||||
def __on_click(self, callback):
|
||||
self._root.destroy()
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
try:
|
||||
self._menu.tk_popup(event["x"] + offset_x, event["y"] + offset_y)
|
||||
|
|
|
@ -4,5 +4,4 @@ API Reference
|
|||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
src/core
|
||||
src/util
|
||||
src/bumblebee_status
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
- use __ for private
|
||||
|
||||
## Improvements
|
||||
- app launcher (list of apps, themeable)
|
||||
|
||||
## TODO
|
||||
- themes: use colors to improve theme readability
|
||||
- convert some stuff to simple attributes to reduce LOCs
|
||||
- use widget index for bumblebee-ctl as alternative (??)
|
||||
- use pytest?
|
||||
|
||||
# documentation
|
||||
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>`__)
|
||||
|
||||
.. 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
|
||||
: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 =
|
||||
./bumblebee-status
|
||||
./bumblebee-ctl
|
||||
./bin/load-i3-bars.sh
|
||||
./bin/pacman-updates
|
||||
./bin/toggle-display.sh
|
||||
|
||||
[versioneer]
|
||||
VCS = git
|
||||
|
|
2
setup.py
2
setup.py
|
@ -53,8 +53,10 @@ setup(
|
|||
version=versioneer.get_version(),
|
||||
cmdclass=versioneer.get_cmdclass(),
|
||||
zip_safe=False,
|
||||
test_suite="tests",
|
||||
data_files=[
|
||||
("share/bumblebee-status/themes", glob.glob("themes/*.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):
|
||||
cfg = core.config.Config(["-f", "my-custom-logfile"])
|
||||
self.assertEquals(None, self.defaultConfig.logfile())
|
||||
self.assertEquals("my-custom-logfile", cfg.logfile())
|
||||
self.assertEqual(None, self.defaultConfig.logfile())
|
||||
self.assertEqual("my-custom-logfile", cfg.logfile())
|
||||
|
||||
def test_all_modules(self):
|
||||
modules = core.config.all_modules()
|
||||
|
|
|
@ -7,6 +7,7 @@ import core.config
|
|||
|
||||
|
||||
class TestModule(core.module.Module):
|
||||
@core.decorators.never
|
||||
def __init__(self, config=None, theme=None):
|
||||
config = core.config.Config([])
|
||||
super().__init__(config, theme, core.widget.Widget(self.get))
|
||||
|
@ -24,6 +25,10 @@ class config(unittest.TestCase):
|
|||
self.width = 10
|
||||
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):
|
||||
self.assertEqual("", self.module.text)
|
||||
self.assertEqual("", self.module.get(self.widget))
|
||||
|
@ -70,5 +75,25 @@ class config(unittest.TestCase):
|
|||
self.module.text = "wxyz"
|
||||
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
|
||||
|
|
|
@ -70,7 +70,9 @@ class config(unittest.TestCase):
|
|||
self.inputObject, self.someEvent["button"], self.someCommand
|
||||
)
|
||||
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):
|
||||
with unittest.mock.patch("core.input.util.cli") as cli:
|
||||
|
|
|
@ -6,6 +6,7 @@ import shlex
|
|||
import core.module
|
||||
import core.widget
|
||||
import core.config
|
||||
import core.input
|
||||
|
||||
|
||||
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(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
|
||||
|
|
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