Merge pull request #1 from tobi-wan-kenobi/master

updating everything
This commit is contained in:
Martin Morlot 2019-10-04 15:56:00 +02:00 committed by GitHub
commit b453c41230
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 739 additions and 192 deletions

7
Dockerfile Normal file
View file

@ -0,0 +1,7 @@
#start with a Python 3.x container
FROM python:3
#grab repository from github
RUN git clone --recursive https://github.com/tobi-wan-kenobi/bumblebee-status.git /var/bumblebee-status
#run the statusline with no modules or themes specified
CMD python3 /var/bumblebee-status/bumblebee-status

View file

@ -5,7 +5,7 @@
[![Test Coverage](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/coverage.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage) [![Test Coverage](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/coverage.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage)
[![Issue Count](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/issue_count.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status) [![Issue Count](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/issue_count.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status)
**Many, many thanks to all contributors! As of now, 47 of the modules are from various contributors (!), and only 19 from myself.** **Many, many thanks to all contributors! As of now, 48 of the modules are from various contributors (!), and only 19 from myself.**
![Solarized Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-solarized.png) ![Solarized Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-solarized.png)

View file

@ -25,7 +25,7 @@ def all_modules():
"name": mod "name": mod
}) })
return result return result
class Module(object): class Module(object):
"""Module instance base class """Module instance base class
@ -142,7 +142,7 @@ class Engine(object):
self._running = True self._running = True
self._modules = [] self._modules = []
self.input = inp self.input = inp
self._aliases = self._read_aliases() self._aliases = self._aliases()
self.load_modules(config.modules()) self.load_modules(config.modules())
self._current_module = None self._current_module = None
self._theme = theme self._theme = theme
@ -230,16 +230,16 @@ class Engine(object):
self.input.register_callback(obj=module, self.input.register_callback(obj=module,
button=button["id"], cmd=module.parameter(button["name"])) button=button["id"], cmd=module.parameter(button["name"]))
def _read_aliases(self): def _aliases(self):
result = {} return {
for module in all_modules(): 'date': 'datetime',
try: 'time': 'datetime',
mod = importlib.import_module("bumblebee.modules.{}".format(module["name"])) 'datetz': 'datetimetz',
for alias in getattr(mod, "ALIASES", []): 'timetz': 'datetimetz',
result[alias] = module["name"] 'pasink': 'pulseaudio',
except Exception as error: 'pasource': 'pulseaudio',
log.warning("failed to import {}: {}".format(module["name"], str(error))) 'test-alias': 'test',
return result }
def _load_module(self, module_name, config_name=None): def _load_module(self, module_name, config_name=None):
"""Load specified module and return it as object""" """Load specified module and return it as object"""

View file

@ -1,12 +1,17 @@
# pylint: disable=C0111,R0903 #pylint: disable=C0111,R0903,W0212
"""Enable/disable automatic screen locking. """Enable/disable automatic screen locking.
Requires the following executables: Requires the following executables:
* xset * xdg-screensaver
* xdotool
* xprop (as dependency for xdotool)
* notify-send * notify-send
""" """
import logging
import os
import psutil
import bumblebee.input import bumblebee.input
import bumblebee.output import bumblebee.output
import bumblebee.engine import bumblebee.engine
@ -14,35 +19,84 @@ import bumblebee.engine
class Module(bumblebee.engine.Module): class Module(bumblebee.engine.Module):
def __init__(self, engine, config): def __init__(self, engine, config):
super(Module, self).__init__(engine, config, super(Module, self).__init__(engine, config,
bumblebee.output.Widget(full_text=self.caffeine) bumblebee.output.Widget(full_text="")
) )
self._active = False
self._xid = None
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
cmd=self._toggle cmd=self._toggle
) )
def caffeine(self, widget): def _check_requirements(self):
return "" requirements = ['xdotool', 'xprop', 'xdg-screensaver']
missing = []
for tool in requirements:
if not bumblebee.util.which(tool):
missing.append(tool)
return missing
def state(self, widget): def _get_i3bar_xid(self):
if self._active(): xid = bumblebee.util.execute("xdotool search --class \"i3bar\"").partition('\n')[0].strip()
if xid.isdigit():
return xid
logging.warning("Module caffeine: xdotool couldn't get X window ID of \"i3bar\".")
return None
def _notify(self):
if not bumblebee.util.which('notify-send'):
return
if self._active:
bumblebee.util.execute("notify-send \"Consuming caffeine\"")
else:
bumblebee.util.execute("notify-send \"Out of coffee\"")
def _suspend_screensaver(self):
self._xid = self._get_i3bar_xid()
if self._xid is None:
return False
pid = os.fork()
if pid == 0:
os.setsid()
bumblebee.util.execute("xdg-screensaver suspend {}".format(self._xid))
os._exit(0)
else:
os.waitpid(pid, 0)
return True
def _resume_screensaver(self):
success = True
xprop_path = bumblebee.util.which('xprop')
pids = [ p.pid for p in psutil.process_iter() if p.cmdline() == [xprop_path, '-id', str(self._xid), '-spy'] ]
for pid in pids:
try:
os.kill(pid, 9)
except OSError:
success = False
return success
def state(self, _):
if self._active:
return "activated" return "activated"
return "deactivated" return "deactivated"
def _active(self): def _toggle(self, _):
for line in bumblebee.util.execute("xset q").split("\n"): missing = self._check_requirements()
if "timeout" in line: if missing:
timeout = int(line.split(" ")[4]) logging.warning('Could not run caffeine - missing %s!', ", ".join(missing))
if timeout == 0: return
return True
return False
return False
def _toggle(self, widget): self._active = not self._active
if self._active(): if self._active:
bumblebee.util.execute("xset s default") success = self._suspend_screensaver()
bumblebee.util.execute("notify-send \"Out of coffee\"")
else: else:
bumblebee.util.execute("xset s off") success = self._resume_screensaver()
bumblebee.util.execute("notify-send \"Consuming caffeine\"")
if success:
self._notify()
else:
self._active = not self._active
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -16,8 +16,6 @@ import datetime
import locale import locale
import bumblebee.engine import bumblebee.engine
ALIASES = ["date", "time"]
def default_format(module): def default_format(module):
default = "%x %X" default = "%x %X"
if module == "date": if module == "date":

View file

@ -26,8 +26,6 @@ import bumblebee.input
import bumblebee.output import bumblebee.output
import bumblebee.engine import bumblebee.engine
ALIASES = ["datetz", "timetz"]
def default_format(module): def default_format(module):
default = "%x %X %Z" default = "%x %X %Z"
if module == "datetz": if module == "datetz":

View file

@ -1,22 +1,38 @@
# pylint: disable=C0111,R0903 # pylint: disable=C0111,R0903
"""Displays the current song being played in DeaDBeeF and """Displays the current song being played in DeaDBeeF and provides
provides some media control bindings. some media control bindings.
Left click toggles pause, scroll up skips the current song, Left click toggles pause, scroll up skips the current song, scroll
scroll down returns to the previous song. down returns to the previous song.
Requires the following library: Requires the following library:
* subprocess * subprocess
Parameters: Parameters:
* deadbeef.format: Format string (defaults to "{artist} - {title}") * deadbeef.format: Format string (defaults to "{artist} - {title}")
Available values are: {artist}, {title}, {album}, {length}, Available values are: {artist}, {title}, {album}, {length},
{trackno}, {year}, {comment}, {trackno}, {year}, {comment},
{copyright}, {time} {copyright}, {time}
* deadbeef.previous: Change binding for previous song (default is left click) This is deprecated, but much simpler.
* deadbeef.next: Change binding for next song (default is right click) * deadbeef.tf_format: A foobar2000 title formatting-style format string.
* deadbeef.pause: Change binding for toggling pause (default is middle click) These can be much more sophisticated than the standard
format strings. This is off by default, but specifying
any tf_format will enable it. If both deadbeef.format
and deadbeef.tf_format are specified, deadbeef.tf_format
takes priority.
* deadbeef.tf_format_if_stopped: Controls whether or not the tf_format format
string should be displayed even if no song is paused or
playing. This could be useful if you want to implement
your own stop strings with the built in logic. Any non-
null value will enable this (by default the module will
hide itself when the player is stopped).
* deadbeef.previous: Change binding for previous song (default is left click)
* deadbeef.next: Change binding for next song (default is right click)
* deadbeef.pause: Change binding for toggling pause (default is middle click)
Available options for deadbeef.previous, deadbeef.next and deadbeef.pause are: Available options for deadbeef.previous, deadbeef.next and deadbeef.pause are:
LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN
""" """
import sys
import bumblebee.input import bumblebee.input
import bumblebee.output import bumblebee.output
@ -34,54 +50,94 @@ class Module(bumblebee.engine.Module):
super(Module, self).__init__(engine, config, super(Module, self).__init__(engine, config,
bumblebee.output.Widget(full_text=self.deadbeef) bumblebee.output.Widget(full_text=self.deadbeef)
) )
buttons = {"LEFT_CLICK":bumblebee.input.LEFT_MOUSE, buttons = {"LEFT_CLICK": bumblebee.input.LEFT_MOUSE,
"RIGHT_CLICK":bumblebee.input.RIGHT_MOUSE, "RIGHT_CLICK": bumblebee.input.RIGHT_MOUSE,
"MIDDLE_CLICK":bumblebee.input.MIDDLE_MOUSE, "MIDDLE_CLICK": bumblebee.input.MIDDLE_MOUSE,
"SCROLL_UP":bumblebee.input.WHEEL_UP, "SCROLL_UP": bumblebee.input.WHEEL_UP,
"SCROLL_DOWN":bumblebee.input.WHEEL_DOWN, "SCROLL_DOWN": bumblebee.input.WHEEL_DOWN,
} }
self._song = "" self._song = ""
self._format = self.parameter("format", "{artist} - {title}") self._format = self.parameter("format", "{artist} - {title}")
self._tf_format = self.parameter("tf_format", "")
self._show_tf_when_stopped = bool(self.parameter("tf_format_if_stopped", ""))
prev_button = self.parameter("previous", "LEFT_CLICK") prev_button = self.parameter("previous", "LEFT_CLICK")
next_button = self.parameter("next", "RIGHT_CLICK") next_button = self.parameter("next", "RIGHT_CLICK")
pause_button = self.parameter("pause", "MIDDLE_CLICK") pause_button = self.parameter("pause", "MIDDLE_CLICK")
self.now_playing = ["deadbeef","--nowplaying","%a;%t;%b;%l;%n;%y;%c;%r;%e"] self.now_playing = ["deadbeef", "--nowplaying", "%a;%t;%b;%l;%n;%y;%c;%r;%e"]
self.now_playing_tf = ["deadbeef", "--nowplaying-tf", ""]
cmd = "deadbeef " cmd = "deadbeef "
engine.input.register_callback(self, button=buttons[prev_button], engine.input.register_callback(self, button=buttons[prev_button],
cmd=cmd + "--prev") cmd=cmd + "--prev")
engine.input.register_callback(self, button=buttons[next_button], engine.input.register_callback(self, button=buttons[next_button],
cmd=cmd + "--next") cmd=cmd + "--next")
engine.input.register_callback(self, button=buttons[pause_button], engine.input.register_callback(self, button=buttons[pause_button],
cmd=cmd + "--play-pause") cmd=cmd + "--play-pause")
# modify the tf_format if we don't want it to show on stop
# this adds conditions to the query itself, rather than
# polling to see if deadbeef is running
# doing this reduces the number of calls we have to make
if self._tf_format and not self._show_tf_when_stopped:
self._tf_format = "$if($or(%isplaying%,%ispaused%),{query})".format(query=self._tf_format)
@scrollable @scrollable
def deadbeef(self, widget): def deadbeef(self, widget):
return str(self._song) return self.string_song
def hidden(self): def hidden(self):
return str(self._song) == "" return self.string_song == ""
def update(self, widgets): def update(self, widgets):
try: try:
deadbeef = subprocess.Popen(self.now_playing,stdin=subprocess.PIPE,stdout=subprocess.PIPE) if self._tf_format == "": # no tf format set, use the old style
data = deadbeef.communicate()[0] return self.update_standard(widgets)
if data == "nothing": return self.update_tf(widgets)
self._song = ""
else:
data = data.split(";")
self._song = self._format.format(artist=data[0],
title=data[1],
album=data[2],
length=data[3],
trackno=data[4],
year=data[5],
comment=data[6],
copyright=data[7],
time=data[8])
except Exception: except Exception:
self._song = "error"
def update_tf(self, widgets):
## ensure that deadbeef is actually running
## easiest way to do this is to check --nowplaying for
## the string "nothing"
if read_process(self.now_playing) == "nothing":
self._song = "" self._song = ""
return
## perform the actual query -- these can be much more sophisticated
self.now_playing_tf[-1] = self._tf_format
data = read_process(self.now_playing_tf)
self._song = data
def update_standard(self, widgets):
data = read_process(self.now_playing)
if data == "nothing":
self._song = ""
else:
data = data.split(";")
self._song = self._format.format(artist=data[0],
title=data[1],
album=data[2],
length=data[3],
trackno=data[4],
year=data[5],
comment=data[6],
copyright=data[7],
time=data[8])
@property
def string_song(self):
"""\
Returns the current song as a string, either as a unicode() (Python <
3) or a regular str() (Python >= 3)
"""
if sys.version_info.major < 3:
return unicode(self._song)
return str(self._song)
def read_process(command):
proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
return proc.stdout.read()
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -0,0 +1,119 @@
# pylint: disable=C0111,R0903
"""Display and run a Pomodoro timer.
Left click to start timer, left click again to pause.
Right click will cancel the timer.
Parameters:
* pomodoro.work: The work duration of timer in minutes (defaults to 25)
* pomodoro.break: The break duration of timer in minutes (defaults to 5)
* pomodoro.format: Timer display format with "%m" and "%s" for minutes and seconds (defaults to "%m:%s")
Examples: "%m min %s sec", "%mm", "", "timer"
* pomodoro.notify: Notification command to run when timer ends/starts (defaults to nothing)
Example: 'notify-send "Time up!"'
"""
from __future__ import absolute_import
import datetime
from math import ceil
import bumblebee.input
import bumblebee.output
import bumblebee.engine
class Module(bumblebee.engine.Module):
def __init__(self, engine, config):
widgets = bumblebee.output.Widget(full_text=self.text)
super(Module, self).__init__(engine, config, widgets)
# Parameters
self._work_period = int(self.parameter("work", 25))
self._break_period = int(self.parameter("break", 5))
self._time_format = self.parameter("format", "%m:%s")
self._notify_cmd = self.parameter("notify", "")
# TODO: Handle time formats more gracefully. This is kludge.
self.display_seconds_p = False
self.display_minutes_p = False
if "%s" in self._time_format:
self.display_seconds_p = True
if "%m" in self._time_format:
self.display_minutes_p = True
self.remaining_time = datetime.timedelta(minutes=self._work_period)
self.time = None
self.pomodoro = { "state":"OFF", "type": ""}
self._text = self.remaining_time_str() + self.pomodoro["type"]
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
cmd=self.timer_play_pause)
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE,
cmd=self.timer_reset)
def remaining_time_str(self):
if self.display_seconds_p and self.display_minutes_p:
minutes, seconds = divmod(self.remaining_time.seconds, 60)
if not self.display_seconds_p:
minutes = ceil(self.remaining_time.seconds / 60)
seconds = 0
if not self.display_minutes_p:
minutes = 0
seconds = self.remaining_time.seconds
minutes = "{:2d}".format(minutes)
seconds = "{:02d}".format(seconds)
return self._time_format.replace("%m",minutes).replace("%s",seconds)+" "
def text(self, widget):
return "{}".format(self._text)
def update(self, widget):
if self.pomodoro["state"] == "ON":
timediff = (datetime.datetime.now() - self.time)
if timediff.seconds >= 0:
self.remaining_time -= timediff
self.time = datetime.datetime.now()
if self.remaining_time.seconds <= 0:
self.notify()
if self.pomodoro["type"] == "Work":
self.pomodoro["type"] = "Break"
self.remaining_time = datetime.timedelta(minutes=self._break_period)
elif self.pomodoro["type"] == "Break":
self.pomodoro["type"] = "Work"
self.remaining_time = datetime.timedelta(minutes=self._work_period)
self._text = self.remaining_time_str() + self.pomodoro["type"]
def notify(self):
if self._notify_cmd:
bumblebee.util.execute(self._notify_cmd)
def timer_play_pause(self, widget):
if self.pomodoro["state"] == "OFF":
self.pomodoro = {"state": "ON", "type": "Work"}
self.remaining_time = datetime.timedelta(minutes=self._work_period)
self.time = datetime.datetime.now()
elif self.pomodoro["state"] == "ON":
self.pomodoro["state"] = "PAUSED"
self.remaining_time -= (datetime.datetime.now() - self.time)
self.time = datetime.datetime.now()
elif self.pomodoro["state"] == "PAUSED":
self.pomodoro["state"] = "ON"
self.time = datetime.datetime.now()
def timer_reset(self, widget):
if self.pomodoro["state"] == "ON" or self.pomodoro["state"] == "PAUSED":
self.pomodoro = {"state":"OFF", "type": "" }
self.remaining_time = datetime.timedelta(minutes=self._work_period)
def state(self, widget):
state = [];
state.append(self.pomodoro["state"].lower())
if self.pomodoro["state"] == "ON" or self.pomodoro["state"] == "OFF":
state.append(self.pomodoro["type"].lower())
return state

View file

@ -24,8 +24,6 @@ import bumblebee.input
import bumblebee.output import bumblebee.output
import bumblebee.engine import bumblebee.engine
ALIASES = ["pasink", "pasource"]
class Module(bumblebee.engine.Module): class Module(bumblebee.engine.Module):
def __init__(self, engine, config): def __init__(self, engine, config):
super(Module, self).__init__(engine, config, super(Module, self).__init__(engine, config,

View file

@ -45,6 +45,7 @@ class Module(bumblebee.engine.Module):
# Use BBC newsfeed as demo: # Use BBC newsfeed as demo:
self._feeds = self.parameter('feeds', 'https://www.espn.com/espn/rss/news').split(" ") self._feeds = self.parameter('feeds', 'https://www.espn.com/espn/rss/news').split(" ")
self._feeds_to_update = [] self._feeds_to_update = []
self._response = ""
self._max_title_length = int(self.parameter("length", 60)) self._max_title_length = int(self.parameter("length", 60))

View file

@ -13,6 +13,8 @@ Parameters:
LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN
""" """
import sys
import bumblebee.input import bumblebee.input
import bumblebee.output import bumblebee.output
import bumblebee.engine import bumblebee.engine
@ -52,12 +54,12 @@ class Module(bumblebee.engine.Module):
engine.input.register_callback(self, button=buttons[pause_button], engine.input.register_callback(self, button=buttons[pause_button],
cmd=cmd + "PlayPause") cmd=cmd + "PlayPause")
## @scrollable @scrollable
def spotify(self, widget): def spotify(self, widget):
return str(self._song) return self.string_song
def hidden(self): def hidden(self):
return str(self._song) == "" return self.string_song == ""
def update(self, widgets): def update(self, widgets):
try: try:
@ -71,7 +73,15 @@ class Module(bumblebee.engine.Module):
artist=','.join(props.get('xesam:artist')), artist=','.join(props.get('xesam:artist')),
trackNumber=str(props.get('xesam:trackNumber')), trackNumber=str(props.get('xesam:trackNumber')),
playbackStatus=u"\u25B6" if playback_status=="Playing" else u"\u258D\u258D" if playback_status=="Paused" else "",) playbackStatus=u"\u25B6" if playback_status=="Playing" else u"\u258D\u258D" if playback_status=="Paused" else "",)
except Exception: except Exception:
self._song = "" self._song = ""
@property
def string_song(self):
if sys.version_info.major < 3:
return unicode(self._song)
return str(self._song)
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -5,8 +5,6 @@
import bumblebee.engine import bumblebee.engine
ALIASES = ["test-alias"]
class Module(bumblebee.engine.Module): class Module(bumblebee.engine.Module):
def __init__(self, engine, config): def __init__(self, engine, config):
super(Module, self).__init__(engine, config, super(Module, self).__init__(engine, config,

View file

@ -60,7 +60,7 @@ class Module(bumblebee.engine.Module):
def _reset(self): def _reset(self):
self._timer = None self._timer = None
self._text = "<click-for-password>" self._text = str(self.parameter("text", "<click-for-password>"))
def _callback(self, secret_name): def _callback(self, secret_name):
secret_name = secret_name.replace(self._path, "") # remove common path secret_name = secret_name.replace(self._path, "") # remove common path

View file

@ -25,9 +25,12 @@ Be aware of security implications of doing this!
""" """
import time import time
import logging
from pkg_resources import parse_version
import bumblebee.engine import bumblebee.engine
from bumblebee.util import execute, bytefmt, asbool from bumblebee.util import execute, bytefmt, asbool
log = logging.getLogger(__name__)
class Module(bumblebee.engine.Module): class Module(bumblebee.engine.Module):
def __init__(self, engine, config): def __init__(self, engine, config):
@ -63,7 +66,16 @@ class Module(bumblebee.engine.Module):
return state return state
def _update_widgets(self, widgets): def _update_widgets(self, widgets):
zfs_version_path = "/sys/module/zfs/version"
# zpool list -H: List all zpools, use script mode (no headers and tabs as separators). # zpool list -H: List all zpools, use script mode (no headers and tabs as separators).
try:
with open(zfs_version_path, 'r') as zfs_mod_version:
zfs_version = zfs_mod_version.readline().rstrip().split('-')[0]
except IOError:
# ZFS isn't installed or the module isn't loaded, stub the version
zfs_version = "0.0.0"
logging.error("ZFS version information not found at {}, check the module is loaded.".format(zfs_version_path))
raw_zpools = execute(('sudo ' if self._usesudo else '') + 'zpool list -H').split('\n') raw_zpools = execute(('sudo ' if self._usesudo else '') + 'zpool list -H').split('\n')
for widget in widgets: for widget in widgets:
@ -71,8 +83,11 @@ class Module(bumblebee.engine.Module):
for raw_zpool in raw_zpools: for raw_zpool in raw_zpools:
try: try:
# Ignored fields (assigned to _) are "expandsz" and "altroot" # Ignored fields (assigned to _) are "expandsz" and "altroot", also "ckpoint" in ZFS 0.8.0+
name, size, alloc, free, _, frag, cap, dedup, health, _ = raw_zpool.split('\t') if parse_version(zfs_version) < parse_version("0.8.0"):
name, size, alloc, free, _, frag, cap, dedup, health, _ = raw_zpool.split('\t')
else:
name, size, alloc, free, _, _, frag, cap, dedup, health, _ = raw_zpool.split('\t')
cap = cap.rstrip('%') cap = cap.rstrip('%')
percentuse=int(cap) percentuse=int(cap)
percentfree=100-percentuse percentfree=100-percentuse

View file

@ -8,12 +8,15 @@ import uuid
import bumblebee.store import bumblebee.store
_TrueValues = ["true", "t", "yes", "y", "1"]
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: return text if not text: return text
width = widget.get("theme.width", module.parameter("width", 30)) width = widget.get("theme.width", module.parameter("width", 30))
widget.set("theme.minwidth", "A"*width) if module.parameter("scrolling.makewide", "true").lower() in _TrueValues:
widget.set("theme.minwidth", "A"*width)
if len(text) <= width: if len(text) <= width:
return text return text
# we need to shorten # we need to shorten

View file

@ -31,7 +31,7 @@ def execute(cmd, wait=True):
raise RuntimeError("{} exited with {}".format(cmd, proc.returncode)) raise RuntimeError("{} exited with {}".format(cmd, proc.returncode))
if hasattr(out, "decode"): if hasattr(out, "decode"):
rv = out.decode("utf-8") rv = out.decode("utf-8", "ignore")
else: else:
rv = out rv = out

View file

@ -1,17 +1,9 @@
# pylint: disable=C0103,C0111 # pylint: disable=C0103,C0111
import json
import unittest import unittest
import mock from mock import patch
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
import tests.mocks as mocks import tests.mocks as mocks
from bumblebee.config import Config
from bumblebee.input import LEFT_MOUSE from bumblebee.input import LEFT_MOUSE
from bumblebee.modules.caffeine import Module from bumblebee.modules.caffeine import Module
@ -19,35 +11,40 @@ class TestCaffeineModule(unittest.TestCase):
def setUp(self): def setUp(self):
mocks.setup_test(self, Module) mocks.setup_test(self, Module)
self.xset_active = " timeout: 0 cycle: 123"
self.xset_inactive = " timeout: 600 cycle: 123"
def tearDown(self): def tearDown(self):
mocks.teardown_test(self) mocks.teardown_test(self)
def test_text(self): def test_check_requirements(self):
self.assertEquals(self.module.caffeine(self.anyWidget), "") with patch('bumblebee.util.which', side_effect=['', 'xprop', 'xdg-screensaver']):
self.assertTrue(['xdotool'] == self.module._check_requirements())
def test_active(self): def test_get_i3bar_xid_returns_digit(self):
self.popen.mock.communicate.return_value = (self.xset_active, None) self.popen.mock.communicate.return_value = ("8388614", None)
self.assertTrue(not "deactivated" in self.module.state(self.anyWidget)) self.assertTrue(self.module._get_i3bar_xid().isdigit())
self.assertTrue("activated" in self.module.state(self.anyWidget))
def test_inactive(self): def test_get_i3bar_xid_returns_error_string(self):
self.popen.mock.communicate.return_value = (self.xset_inactive, None) self.popen.mock.communicate.return_value = ("Some error message", None)
self.assertTrue("deactivated" in self.module.state(self.anyWidget)) self.assertTrue(self.module._get_i3bar_xid() is None)
self.popen.mock.communicate.return_value = ("no text", None)
self.assertTrue("deactivated" in self.module.state(self.anyWidget))
def test_toggle(self): def test_get_i3bar_xid_returns_empty_string(self):
self.popen.mock.communicate.return_value = (self.xset_active, None) self.popen.mock.communicate.return_value = ("", None)
mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) self.assertTrue(self.module._get_i3bar_xid() is None)
self.popen.assert_call("xset s default")
self.popen.assert_call("notify-send \"Out of coffee\"") def test_suspend_screensaver_success(self):
with patch.object(self.module, '_get_i3bar_xid', return_value=8388614):
self.popen.mock.communicate.return_value = (self.xset_inactive, None) mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module)
mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) self.assertTrue(self.module._suspend_screensaver() is True)
self.popen.assert_call("xset s off")
self.popen.assert_call("notify-send \"Consuming caffeine\"") def test_suspend_screensaver_fail(self):
with patch.object(self.module, '_get_i3bar_xid', return_value=None):
self.module._active = False
mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module)
self.assertTrue(self.module._suspend_screensaver() is False)
def test_resume_screensaver(self):
with patch.object(self.module, '_check_requirements', return_value=[]):
self.module._active = True
mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module)
self.assertTrue(self.module._resume_screensaver() is True)
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -0,0 +1,42 @@
{
"icons": [ "awesome-fonts" ],
"defaults": {
"separator-block-width": 0,
"warning": {
"fg": "#002b36",
"bg": "#b58900"
},
"critical": {
"fg": "#002b36",
"bg": "#dc322f"
}
},
"cycle": [
{ "fg": "#B1B1B3", "bg": "#474749" },
{ "fg": "#BEBEBE", "bg": "#323234" }
],
"dnf": {
"good": {
"fg": "#002b36",
"bg": "#859900"
}
},
"pacman": {
"good": {
"fg": "#002b36",
"bg": "#859900"
}
},
"pomodoro": {
"paused": {
"fg": "#002b36",
"bg": "#b58900"
},
"break": {
"fg": "#002b36",
"bg": "#859900"
}
}
}

View file

@ -42,5 +42,20 @@
"fg": "#002b36", "fg": "#002b36",
"bg": "#859900" "bg": "#859900"
} }
} },
"pomodoro": {
"paused": {
"fg": "#002b36",
"bg": "#b58900"
},
"work": {
"fg": "#1d2021",
"bg": "#b8bb26"
},
"break": {
"fg": "#002b36",
"bg": "#859900"
}
}
} }

View file

@ -54,5 +54,20 @@
"modified": { "bg": "#458588" }, "modified": { "bg": "#458588" },
"deleted": { "bg": "#9d0006" }, "deleted": { "bg": "#9d0006" },
"new": { "bg": "#b16286" } "new": { "bg": "#b16286" }
} },
"pomodoro": {
"paused": {
"fg": "#1d2021",
"bg": "#d79921"
},
"work": {
"fg": "#1d2021",
"bg": "#b8bb26"
},
"break": {
"fg": "#002b36",
"bg": "#859900"
}
}
} }

View file

@ -21,38 +21,52 @@
"fg": "#282828", "fg": "#282828",
"bg": "#fbf1c7" "bg": "#fbf1c7"
} }
], ],
"dnf": { "dnf": {
"good": { "good": {
"fg": "#002b36", "fg": "#002b36",
"bg": "#859900" "bg": "#859900"
} }
}, },
"apt": { "apt": {
"good": { "good": {
"fg": "#002b36", "fg": "#002b36",
"bg": "#859900" "bg": "#859900"
} }
}, },
"battery": { "battery": {
"charged": { "charged": {
"fg": "#1d2021", "fg": "#1d2021",
"bg": "#b8bb26" "bg": "#b8bb26"
}, },
"AC": { "AC": {
"fg": "#1d2021", "fg": "#1d2021",
"bg": "#b8bb26" "bg": "#b8bb26"
} }
}, },
"bluetooth": { "bluetooth": {
"ON": { "ON": {
"fg": "#1d2021", "fg": "#1d2021",
"bg": "#b8bb26" "bg": "#b8bb26"
} }
}, },
"git": { "git": {
"modified": { "bg": "#458588" }, "modified": { "bg": "#458588" },
"deleted": { "bg": "#9d0006" }, "deleted": { "bg": "#9d0006" },
"new": { "bg": "#b16286" } "new": { "bg": "#b16286" }
} },
"pomodoro": {
"paused": {
"fg": "#1d2021",
"bg": "#d79921"
},
"work": {
"fg": "#1d2021",
"bg": "#b8bb26"
},
"break": {
"fg": "#002b36",
"bg": "#859900"
}
}
} }

View file

@ -54,5 +54,19 @@
"modified": { "bg": "#458588" }, "modified": { "bg": "#458588" },
"deleted": { "bg": "#9d0006" }, "deleted": { "bg": "#9d0006" },
"new": { "bg": "#b16286" } "new": { "bg": "#b16286" }
} },
"pomodoro": {
"paused": {
"fg": "#1d2021",
"bg": "#d79921"
},
"work": {
"fg": "#1d2021",
"bg": "#b8bb26"
},
"break": {
"fg": "#002b36",
"bg": "#859900"
}
}
} }

View file

@ -54,5 +54,20 @@
"modified": { "bg": "#458588" }, "modified": { "bg": "#458588" },
"deleted": { "bg": "#9d0006" }, "deleted": { "bg": "#9d0006" },
"new": { "bg": "#b16286" } "new": { "bg": "#b16286" }
} },
"pomodoro": {
"paused": {
"fg": "#1d2021",
"bg": "#d79921"
},
"work": {
"fg": "#1d2021",
"bg": "#b8bb26"
},
"break": {
"fg": "#002b36",
"bg": "#859900"
}
}
} }

View file

@ -47,5 +47,20 @@
"fg": "#0f1117", "fg": "#0f1117",
"bg": "#84a0c6" "bg": "#84a0c6"
} }
},
"pomodoro": {
"paused": {
"fg": "#0f1117",
"bg": "#e2a478"
},
"work": {
"fg": "#1d2021",
"bg": "#b8bb26"
},
"break": {
"fg": "#b4be82",
"bg": "#161821"
}
} }
} }

View file

@ -46,5 +46,19 @@
"fg": "#0f1117", "fg": "#0f1117",
"bg": "#84a0c6" "bg": "#84a0c6"
} }
},
"pomodoro": {
"paused": {
"fg": "#0f1117",
"bg": "#e2a478"
},
"work": {
"fg": "#1d2021",
"bg": "#b8bb26"
},
"break": {
"fg": "#89b8c2",
"bg": "#161821"
}
} }
} }

View file

@ -46,5 +46,20 @@
"fg": "#89b8c2", "fg": "#89b8c2",
"bg": "#161821" "bg": "#161821"
} }
},
"pomodoro": {
"paused": {
"fg": "#e2a478",
"bg": "#c6c8d1"
},
"work": {
"fg": "#89b8c2",
"bg": "#161821"
},
"break": {
"fg": "#b4be82",
"bg": "#161821"
}
} }
} }

View file

@ -40,5 +40,20 @@
"fg": "#89b8c2", "fg": "#89b8c2",
"bg": "#161821" "bg": "#161821"
} }
},
"pomodoro": {
"paused": {
"fg": "#0f1117",
"bg": "#e2a478"
},
"work": {
"fg": "#1d2021",
"bg": "#b8bb26"
},
"break": {
"fg": "#89b8c2",
"bg": "#161821"
}
} }
} }

View file

@ -302,6 +302,10 @@
}, },
"system": { "system": {
"prefix": "system" "prefix": "system"
} },
"pomodoro": {
"off": { "prefix": "pom" },
"paused": { "prefix": "||" },
"on": { "prefix": " >" }
}
} }

View file

@ -222,5 +222,11 @@
}, },
"rss": { "rss": {
"prefix": "" "prefix": ""
},
"pomodoro": {
"off": { "prefix": "" },
"paused": { "prefix": "" },
"work": { "prefix": "" },
"break": { "prefix": "" }
} }
} }

View file

@ -185,5 +185,11 @@
}, },
"rss": { "rss": {
"prefix": "\uf1ea" "prefix": "\uf1ea"
},
"pomodoro": {
"off": { "prefix": "\uf24f" },
"paused": { "prefix": "\uf210" },
"on": { "prefix": "\uf488" }
} }
} }

View file

@ -36,5 +36,20 @@
"fg": "#282C34", "fg": "#282C34",
"bg": "#98C379" "bg": "#98C379"
} }
} },
"pomodoro": {
"paused": {
"fg": "#282C34",
"bg": "#E5C07B"
},
"work": {
"fg": "#98C379",
"bg": "#282C34"
},
"break": {
"fg": "#282C34",
"bg": "#98C379"
}
}
} }

View file

@ -43,5 +43,20 @@
"fg": "#494949", "fg": "#494949",
"bg": "#41db00" "bg": "#41db00"
} }
} },
"pomodoro": {
"paused": {
"fg": "#d75f00",
"bg": "#ffd700"
},
"work": {
"fg": "#ffd700",
"bg": "#d75f00"
},
"break": {
"fg": "#494949",
"bg": "#41db00"
}
}
} }

View file

@ -40,5 +40,20 @@
}, },
"cmus": { "cmus": {
"bg": "#C42021" "bg": "#C42021"
} },
"pomodoro": {
"paused": {
"fg": "#FDFFFC",
"bg": "#B91372"
},
"work": {
"fg": "#FDFFFC",
"bg": "#41EAD4"
},
"break": {
"fg": "#FDFFFC",
"bg": "#011627"
}
}
} }

View file

@ -21,29 +21,43 @@
}, },
"apt": { "apt": {
"good": { "good": {
"fg": "#002b36", "fg": "#002b36",
"bg": "#859900" "bg": "#859900"
} }
}, },
"pacman": { "pacman": {
"good": { "good": {
"fg": "#002b36", "fg": "#002b36",
"bg": "#859900" "bg": "#859900"
} }
}, },
"battery": { "battery": {
"charged": { "charged": {
"fg": "#002b36", "fg": "#002b36",
"bg": "#859900" "bg": "#859900"
}, },
"AC": { "AC": {
"fg": "#002b36", "fg": "#002b36",
"bg": "#859900" "bg": "#859900"
} }
}, },
"git": { "git": {
"modified": { "bg": "#2aa198" }, "modified": { "bg": "#2aa198" },
"deleted": { "bg": "#d33682" }, "deleted": { "bg": "#d33682" },
"new": { "bg": "#859900" } "new": { "bg": "#859900" }
} },
"pomodoro": {
"paused": {
"fg": "#002b36",
"bg": "#b58900"
},
"work": {
"fg": "#eee8d5",
"bg": "#586e75"
},
"break": {
"fg": "#002b36",
"bg": "#859900"
}
}
} }

View file

@ -47,5 +47,19 @@
"modified": { "bg": "#2aa198" }, "modified": { "bg": "#2aa198" },
"deleted": { "bg": "#d33682" }, "deleted": { "bg": "#d33682" },
"new": { "bg": "#859900" } "new": { "bg": "#859900" }
} },
"pomodoro": {
"paused": {
"fg": "#002b36",
"bg": "#b58900"
},
"work": {
"fg": "#eee8d5",
"bg": "#586e75"
},
"break": {
"fg": "#002b36",
"bg": "#859900"
}
}
} }

View file

@ -49,5 +49,20 @@
"modified": { "bg": "#2aa198" }, "modified": { "bg": "#2aa198" },
"deleted": { "bg": "#d33682" }, "deleted": { "bg": "#d33682" },
"new": { "bg": "#859900" } "new": { "bg": "#859900" }
} },
"pomodoro": {
"paused": {
"fg": "#002b36",
"bg": "#b58900"
},
"work": {
"fg": "#eee8d5",
"bg": "#586e75"
},
"break": {
"fg": "#002b36",
"bg": "#859900"
}
}
} }

View file

@ -44,5 +44,20 @@
"fg": "background", "fg": "background",
"bg": "color3" "bg": "color3"
} }
} },
"pomodoro": {
"paused": {
"fg": "cursor",
"bg": "color6"
},
"work": {
"fg": "background",
"bg": "foreground"
},
"break": {
"fg": "background",
"bg": "color3"
}
}
} }