2016-12-04 12:26:20 +01:00
|
|
|
# pylint: disable=R0201
|
|
|
|
|
|
|
|
"""Output classes"""
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import json
|
2016-12-09 19:29:16 +01:00
|
|
|
import uuid
|
2019-12-25 13:40:02 +01:00
|
|
|
import logging
|
2016-12-04 12:26:20 +01:00
|
|
|
|
2016-12-10 11:25:02 +01:00
|
|
|
import bumblebee.store
|
2019-10-03 17:36:26 +02:00
|
|
|
import bumblebee.util
|
2019-10-02 22:30:55 +02:00
|
|
|
|
2020-01-01 21:22:31 +01:00
|
|
|
MAX_PERCENTS = 100.
|
|
|
|
CHARS = 8
|
|
|
|
HBARS = [
|
|
|
|
u"\u2581",
|
|
|
|
u"\u2582",
|
|
|
|
u"\u2583",
|
|
|
|
u"\u2584",
|
|
|
|
u"\u2585",
|
|
|
|
u"\u2586",
|
|
|
|
u"\u2587",
|
|
|
|
u"\u2588"]
|
|
|
|
VBARS = [
|
|
|
|
u"\u258f",
|
|
|
|
u"\u258e",
|
|
|
|
u"\u258d",
|
|
|
|
u"\u258c",
|
|
|
|
u"\u258b",
|
|
|
|
u"\u258a",
|
|
|
|
u"\u2589",
|
|
|
|
u"\u2588"]
|
2020-01-19 11:32:12 +01:00
|
|
|
BRAILLE = {
|
|
|
|
(0, 0): u" ",
|
|
|
|
(1, 0): u"\u2840",
|
|
|
|
(2, 0): u"\u2844",
|
|
|
|
(3, 0): u"\u2846",
|
|
|
|
(4, 0): u"\u2847",
|
|
|
|
(0, 1): u"\u2880",
|
|
|
|
(0, 2): u"\u28a0",
|
|
|
|
(0, 3): u"\u28b0",
|
|
|
|
(0, 4): u"\u28b8",
|
|
|
|
(1, 1): u"\u28c0",
|
|
|
|
(2, 1): u"\u28c4",
|
|
|
|
(3, 1): u"\u28c6",
|
|
|
|
(4, 1): u"\u28c7",
|
|
|
|
(1, 2): u"\u28e0",
|
|
|
|
(2, 2): u"\u28e4",
|
|
|
|
(3, 2): u"\u28e6",
|
|
|
|
(4, 2): u"\u28e7",
|
|
|
|
(1, 3): u"\u28f0",
|
|
|
|
(2, 3): u"\u28f4",
|
|
|
|
(3, 3): u"\u28f6",
|
|
|
|
(4, 3): u"\u28f7",
|
|
|
|
(1, 4): u"\u28f8",
|
|
|
|
(2, 4): u"\u28fc",
|
|
|
|
(3, 4): u"\u28fe",
|
|
|
|
(4, 4): u"\u28ff"
|
|
|
|
}
|
2020-01-01 21:22:31 +01:00
|
|
|
|
2019-12-25 13:40:02 +01:00
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
2017-04-22 13:07:50 +02:00
|
|
|
def scrollable(func):
|
|
|
|
def wrapper(module, widget):
|
|
|
|
text = func(module, widget)
|
2019-11-10 20:30:46 +01:00
|
|
|
if not text:
|
|
|
|
return text
|
|
|
|
width = widget.get("theme.width", int(module.parameter("width", 30)))
|
2019-10-03 17:36:26 +02:00
|
|
|
if bumblebee.util.asbool(module.parameter("scrolling.makewide", "true")):
|
2019-10-02 22:30:55 +02:00
|
|
|
widget.set("theme.minwidth", "A"*width)
|
2019-11-10 20:30:46 +01:00
|
|
|
if width < 0:
|
|
|
|
return text
|
2017-04-22 13:07:50 +02:00
|
|
|
if len(text) <= width:
|
|
|
|
return text
|
|
|
|
# we need to shorten
|
2019-11-10 20:30:46 +01:00
|
|
|
|
2018-12-28 22:27:38 +01:00
|
|
|
try:
|
|
|
|
bounce = int(module.parameter("scrolling.bounce", 1))
|
|
|
|
except ValueError:
|
|
|
|
bounce = 1
|
|
|
|
try:
|
|
|
|
scroll_speed = int(module.parameter("scrolling.speed", 1))
|
|
|
|
except ValueError:
|
|
|
|
scroll_speed = 1
|
2017-04-22 13:07:50 +02:00
|
|
|
start = widget.get("scrolling.start", -1)
|
|
|
|
direction = widget.get("scrolling.direction", "right")
|
2018-12-28 22:27:38 +01:00
|
|
|
start += scroll_speed if direction == "right" else -(scroll_speed)
|
2019-11-10 20:30:46 +01:00
|
|
|
|
2018-12-28 22:27:38 +01:00
|
|
|
if width + start > len(text) + (scroll_speed -1):
|
|
|
|
if bounce:
|
|
|
|
widget.set("scrolling.direction", "left")
|
|
|
|
else:
|
|
|
|
start = 0
|
|
|
|
elif start <= 0:
|
|
|
|
if bounce:
|
|
|
|
widget.set("scrolling.direction", "right")
|
|
|
|
else:
|
|
|
|
start = len(text)
|
2017-04-22 13:07:50 +02:00
|
|
|
widget.set("scrolling.start", start)
|
|
|
|
text = text[start:width+start]
|
|
|
|
|
|
|
|
return text
|
|
|
|
return wrapper
|
|
|
|
|
2020-01-01 21:22:31 +01:00
|
|
|
|
|
|
|
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 = HBARS
|
|
|
|
|
|
|
|
def __init__(self, value):
|
|
|
|
"""
|
|
|
|
Args:
|
|
|
|
|
|
|
|
value (float): value between 0. and 100. meaning percents
|
|
|
|
"""
|
|
|
|
super(HBar, self).__init__(value)
|
|
|
|
self.step = MAX_PERCENTS / CHARS
|
|
|
|
|
|
|
|
def get_char(self):
|
|
|
|
"""
|
|
|
|
Decide which char to draw
|
|
|
|
|
|
|
|
Return: str
|
|
|
|
"""
|
|
|
|
for i in range(CHARS):
|
|
|
|
left = i * self.step
|
|
|
|
right = (i + 1) * self.step
|
|
|
|
if left <= self.value < right:
|
|
|
|
return self.bars[i]
|
|
|
|
return self.bars[-1]
|
|
|
|
|
|
|
|
|
|
|
|
def hbar(value):
|
|
|
|
"""wrapper function"""
|
|
|
|
return HBar(value).get_char()
|
|
|
|
|
|
|
|
|
|
|
|
class VBar(Bar):
|
|
|
|
"""vertical bar (can be more than 1 char)"""
|
|
|
|
bars = VBARS
|
|
|
|
|
|
|
|
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 / (CHARS * width)
|
|
|
|
self.width = width
|
|
|
|
|
|
|
|
def get_chars(self):
|
|
|
|
"""
|
|
|
|
Decide which char to draw
|
|
|
|
|
|
|
|
Return: str
|
|
|
|
"""
|
|
|
|
if self.value == 100:
|
|
|
|
return self.bars[-1] * self.width
|
|
|
|
if self.width == 1:
|
|
|
|
for i in range(CHARS):
|
|
|
|
left = i * self.step
|
|
|
|
right = (i + 1) * self.step
|
|
|
|
if left <= self.value < right:
|
|
|
|
return self.bars[i]
|
|
|
|
else:
|
|
|
|
full_parts = int(self.value // (self.step * CHARS))
|
|
|
|
remainder = self.value - full_parts * self.step * CHARS
|
|
|
|
empty_parts = self.width - full_parts
|
|
|
|
if remainder >= 0:
|
|
|
|
empty_parts -= 1
|
|
|
|
part_vbar = VBar(remainder * self.width) # scale to width
|
|
|
|
chars = self.bars[-1] * full_parts
|
|
|
|
chars += part_vbar.get_chars()
|
|
|
|
chars += " " * empty_parts
|
|
|
|
return chars
|
|
|
|
|
|
|
|
|
|
|
|
def vbar(value, width):
|
|
|
|
"""wrapper function"""
|
|
|
|
return VBar(value, width).get_chars()
|
|
|
|
|
|
|
|
|
2020-01-19 11:32:12 +01:00
|
|
|
class BrailleGraph(object):
|
|
|
|
"""
|
|
|
|
graph using Braille chars
|
|
|
|
scaled to passed values
|
|
|
|
"""
|
|
|
|
def __init__(self, values):
|
|
|
|
"""
|
|
|
|
Args:
|
|
|
|
|
|
|
|
values (list): list of values
|
|
|
|
"""
|
|
|
|
self.values = values
|
|
|
|
# length of values list must be even
|
|
|
|
# because one Braille char displays two values
|
|
|
|
if len(self.values) % 2 == 1:
|
|
|
|
self.values.append(0)
|
|
|
|
self.steps = self.get_steps()
|
|
|
|
self.parts = [tuple(self.steps[i:i+2])
|
|
|
|
for i in range(len(self.steps))[::2]]
|
|
|
|
|
|
|
|
@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.:
|
|
|
|
return 0
|
|
|
|
elif value <= unit:
|
|
|
|
return 1
|
|
|
|
elif value <= unit * 2:
|
|
|
|
return 2
|
|
|
|
elif value <= unit * 3:
|
|
|
|
return 3
|
|
|
|
else:
|
|
|
|
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.
|
|
|
|
if unit == 0:
|
|
|
|
return [0] * len(self.values)
|
|
|
|
stepslist = []
|
|
|
|
for value in self.values:
|
|
|
|
stepslist.append(self.get_height(value, unit))
|
|
|
|
return stepslist
|
|
|
|
|
|
|
|
def get_chars(self):
|
|
|
|
"""
|
|
|
|
Decide which chars to draw
|
|
|
|
|
|
|
|
Return: str
|
|
|
|
"""
|
|
|
|
chars = []
|
|
|
|
for part in self.parts:
|
|
|
|
chars.append(BRAILLE[part])
|
|
|
|
return "".join(chars)
|
|
|
|
|
|
|
|
|
|
|
|
def bgraph(values):
|
|
|
|
"""wrapper function"""
|
|
|
|
return BrailleGraph(values).get_chars()
|
|
|
|
|
|
|
|
|
2016-12-10 11:25:02 +01:00
|
|
|
class Widget(bumblebee.store.Store):
|
2016-12-04 17:45:42 +01:00
|
|
|
"""Represents a single visible block in the status bar"""
|
2016-12-10 07:47:24 +01:00
|
|
|
def __init__(self, full_text="", name=""):
|
2016-12-10 11:25:02 +01:00
|
|
|
super(Widget, self).__init__()
|
2016-12-04 17:45:42 +01:00
|
|
|
self._full_text = full_text
|
2016-12-09 11:49:59 +01:00
|
|
|
self.module = None
|
2016-12-10 08:09:13 +01:00
|
|
|
self._module = None
|
2018-09-15 14:40:52 +02:00
|
|
|
self._minimized = False
|
2016-12-10 07:47:24 +01:00
|
|
|
self.name = name
|
2016-12-09 19:29:16 +01:00
|
|
|
self.id = str(uuid.uuid4())
|
2016-12-08 12:44:52 +01:00
|
|
|
|
2017-08-12 17:03:04 +02:00
|
|
|
def get_module(self):
|
|
|
|
return self._module
|
|
|
|
|
2018-09-15 14:40:52 +02:00
|
|
|
def toggle_minimize(self):
|
|
|
|
self._minimized = not self._minimized
|
|
|
|
|
2016-12-09 11:49:59 +01:00
|
|
|
def link_module(self, module):
|
2016-12-09 07:11:23 +01:00
|
|
|
"""Set the module that spawned this widget
|
|
|
|
|
|
|
|
This is done outside the constructor to avoid having to
|
|
|
|
pass in the module name in every concrete module implementation"""
|
2016-12-09 11:49:59 +01:00
|
|
|
self.module = module.name
|
2016-12-10 08:09:13 +01:00
|
|
|
self._module = module
|
2016-12-04 17:45:42 +01:00
|
|
|
|
2017-05-10 20:01:29 +02:00
|
|
|
def cls(self):
|
|
|
|
if not self._module:
|
|
|
|
return None
|
|
|
|
return self._module.__module__.replace("bumblebee.modules.", "")
|
|
|
|
|
2016-12-09 13:32:22 +01:00
|
|
|
def state(self):
|
2016-12-09 16:33:29 +01:00
|
|
|
"""Return the widget's state"""
|
2016-12-10 08:09:13 +01:00
|
|
|
if self._module and hasattr(self._module, "state"):
|
2016-12-10 11:25:02 +01:00
|
|
|
states = self._module.state(self)
|
|
|
|
if not isinstance(states, list):
|
|
|
|
return [states]
|
|
|
|
return states
|
|
|
|
return []
|
2016-12-09 13:32:22 +01:00
|
|
|
|
2016-12-10 11:25:02 +01:00
|
|
|
def full_text(self, value=None):
|
|
|
|
"""Set or retrieve the full text to display in the widget"""
|
|
|
|
if value:
|
|
|
|
self._full_text = value
|
2016-12-08 08:44:54 +01:00
|
|
|
else:
|
2018-09-15 14:40:52 +02:00
|
|
|
if self._minimized:
|
|
|
|
return u"\u2026"
|
2016-12-10 11:25:02 +01:00
|
|
|
if callable(self._full_text):
|
2016-12-11 11:37:24 +01:00
|
|
|
return self._full_text(self)
|
2016-12-10 11:25:02 +01:00
|
|
|
else:
|
|
|
|
return self._full_text
|
2016-12-04 17:45:42 +01:00
|
|
|
|
2020-01-31 10:16:26 +01:00
|
|
|
|
|
|
|
class WidgetDrawer(object):
|
|
|
|
"""
|
|
|
|
Wrapper for I3BarOutput.draw(),
|
|
|
|
because that function is getting too big
|
|
|
|
"""
|
2018-09-22 14:40:32 +02:00
|
|
|
def __init__(self, theme, config=None):
|
2020-01-31 10:16:26 +01:00
|
|
|
"""
|
|
|
|
Keep the same signature as I3BarOutput.__init__()
|
|
|
|
"""
|
2016-12-08 11:31:20 +01:00
|
|
|
self._theme = theme
|
2018-09-22 14:40:32 +02:00
|
|
|
self._config = config
|
2020-01-31 10:16:26 +01:00
|
|
|
self._widgets = []
|
2020-01-31 10:44:07 +01:00
|
|
|
self._prefix = None
|
|
|
|
self._suffix = None
|
2016-12-04 12:26:20 +01:00
|
|
|
|
2020-01-31 10:30:48 +01:00
|
|
|
def add_separator(self, widget, separator):
|
|
|
|
"""Add separator (if theme has one)"""
|
|
|
|
if separator:
|
|
|
|
self._widgets.append({
|
|
|
|
u"full_text": separator,
|
|
|
|
"separator": False,
|
|
|
|
"color": self._theme.separator_fg(widget),
|
|
|
|
"background": self._theme.separator_bg(widget),
|
|
|
|
"separator_block_width": self._theme.separator_block_width(widget),
|
|
|
|
})
|
|
|
|
|
2019-01-26 19:40:08 +01:00
|
|
|
def draw(self, widget, module=None, engine=None):
|
2020-01-31 09:57:30 +01:00
|
|
|
"""
|
2020-01-31 10:16:26 +01:00
|
|
|
Keep the same argument signature as I3BarOutput.draw()
|
|
|
|
Return: list
|
|
|
|
list[0] - optional if the theme has a separator
|
|
|
|
list[1] - JSON text for the widget
|
2020-01-31 09:57:30 +01:00
|
|
|
"""
|
2020-01-31 10:34:17 +01:00
|
|
|
|
2019-01-26 19:40:08 +01:00
|
|
|
if widget.get_module() and widget.get_module().hidden():
|
2020-01-31 10:19:59 +01:00
|
|
|
return []
|
2019-01-26 19:40:08 +01:00
|
|
|
if widget.get_module() and widget.get_module().name in self._config.autohide():
|
|
|
|
if not any(state in widget.state() for state in ["warning", "critical"]):
|
2020-01-31 10:19:59 +01:00
|
|
|
return []
|
2020-01-31 10:37:42 +01:00
|
|
|
|
|
|
|
separator = self._theme.separator(widget)
|
|
|
|
self.add_separator(widget, separator)
|
|
|
|
|
2016-12-09 13:06:08 +01:00
|
|
|
padding = self._theme.padding(widget)
|
2020-01-11 13:54:53 +01:00
|
|
|
|
2020-01-31 10:44:07 +01:00
|
|
|
self._prefix = self._theme.prefix(widget, padding)
|
|
|
|
self._suffix = self._theme.suffix(widget, padding)
|
2019-01-26 19:40:08 +01:00
|
|
|
|
2020-01-31 10:53:25 +01:00
|
|
|
markup = "none" if not self._config else self._config.markup()
|
|
|
|
|
|
|
|
if markup == "pango":
|
2020-01-11 13:54:53 +01:00
|
|
|
# add prefix/suffix colors
|
|
|
|
fg = self._theme.prefix_fg(widget)
|
|
|
|
bg = self._theme.prefix_bg(widget)
|
2020-01-31 10:44:07 +01:00
|
|
|
self._prefix = "<span {} {}>{}</span>".format(
|
2020-01-11 13:54:53 +01:00
|
|
|
"foreground='{}'".format(fg) if fg else "",
|
|
|
|
"background='{}'".format(bg) if bg else "",
|
2020-01-31 10:44:07 +01:00
|
|
|
self._prefix
|
2020-01-11 13:54:53 +01:00
|
|
|
)
|
|
|
|
|
2020-01-31 10:47:21 +01:00
|
|
|
full_text = widget.full_text()
|
|
|
|
|
2020-01-31 10:44:07 +01:00
|
|
|
if self._prefix:
|
|
|
|
full_text = u"{}{}".format(self._prefix, full_text)
|
|
|
|
if self._suffix:
|
|
|
|
full_text = u"{}{}".format(full_text, self._suffix)
|
2019-01-26 19:40:08 +01:00
|
|
|
|
2017-04-22 13:07:50 +02:00
|
|
|
width = self._theme.minwidth(widget)
|
2018-11-25 17:50:05 +01:00
|
|
|
|
|
|
|
if width:
|
2020-01-31 10:44:07 +01:00
|
|
|
full_text = full_text.ljust(len(width) + len(self._prefix) + len(self._suffix))
|
2019-01-26 19:40:08 +01:00
|
|
|
|
2019-12-30 13:29:34 +01:00
|
|
|
if markup == "pango":
|
|
|
|
full_text = full_text.replace("&", "&")
|
|
|
|
|
2019-01-26 19:40:08 +01:00
|
|
|
self._widgets.append({
|
2018-01-11 20:31:48 +01:00
|
|
|
u"full_text": full_text,
|
2016-12-09 08:58:45 +01:00
|
|
|
"color": self._theme.fg(widget),
|
|
|
|
"background": self._theme.bg(widget),
|
2016-12-09 12:55:16 +01:00
|
|
|
"separator_block_width": self._theme.separator_block_width(widget),
|
|
|
|
"separator": True if separator is None else False,
|
2018-11-25 17:50:05 +01:00
|
|
|
"min_width": None,
|
2020-01-31 10:44:07 +01:00
|
|
|
# "min_width": width + "A"*(len(self._prefix) + len(self._suffix)) if width else None,
|
2017-04-22 08:24:52 +02:00
|
|
|
"align": self._theme.align(widget),
|
2016-12-09 19:29:16 +01:00
|
|
|
"instance": widget.id,
|
|
|
|
"name": module.id,
|
2019-12-30 13:29:34 +01:00
|
|
|
"markup": markup,
|
2019-01-26 19:40:08 +01:00
|
|
|
})
|
2020-01-31 10:16:26 +01:00
|
|
|
return self._widgets
|
|
|
|
|
|
|
|
|
|
|
|
class I3BarOutput(object):
|
|
|
|
"""Manage output according to the i3bar protocol"""
|
|
|
|
def __init__(self, theme, config=None):
|
|
|
|
self._theme = theme
|
|
|
|
self._widgets = []
|
|
|
|
self._started = False
|
|
|
|
self._config = config
|
|
|
|
|
|
|
|
def started(self):
|
|
|
|
return self._started
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
"""Print start preamble for i3bar protocol"""
|
|
|
|
self._started = True
|
|
|
|
sys.stdout.write(json.dumps({"version": 1, "click_events": True}) + "\n[\n")
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
"""Finish i3bar protocol"""
|
|
|
|
sys.stdout.write("]\n")
|
|
|
|
|
|
|
|
def draw(self, widget, module=None, engine=None):
|
|
|
|
"""
|
|
|
|
Draw a single widget
|
|
|
|
|
|
|
|
Note: technically, this method doesn't draw anything. It only adds
|
|
|
|
blocks of JSON text to self._widgets: one for separator, if the
|
|
|
|
theme contains a separator and one for the widget itself
|
|
|
|
"""
|
|
|
|
widget_drawer = WidgetDrawer(self._theme, self._config)
|
|
|
|
self._widgets.extend(widget_drawer.draw(widget, module, engine))
|
2016-12-08 09:04:47 +01:00
|
|
|
|
2016-12-08 12:44:52 +01:00
|
|
|
def begin(self):
|
|
|
|
"""Start one output iteration"""
|
|
|
|
self._widgets = []
|
2016-12-09 12:28:39 +01:00
|
|
|
self._theme.reset()
|
2016-12-04 12:53:18 +01:00
|
|
|
|
2016-12-04 16:14:43 +01:00
|
|
|
def flush(self):
|
|
|
|
"""Flushes output"""
|
2018-09-22 14:40:32 +02:00
|
|
|
widgets = self._widgets
|
|
|
|
if self._config and self._config.reverse():
|
|
|
|
widgets = list(reversed(widgets))
|
|
|
|
sys.stdout.write(json.dumps(widgets))
|
2019-12-25 13:40:02 +01:00
|
|
|
if len(self._config.unused_keys()) > 0:
|
|
|
|
for key in self._config.unused_keys():
|
|
|
|
log.warning("unused parameter {} - please check the documentation of the affected module to ensure the parameter exists".format(key))
|
2016-12-08 12:44:52 +01:00
|
|
|
|
|
|
|
def end(self):
|
|
|
|
"""Finalizes output"""
|
2016-12-04 16:14:43 +01:00
|
|
|
sys.stdout.write(",\n")
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
2016-12-04 12:26:20 +01:00
|
|
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|