From 3d9279c444926ba4e3bcf44977989afb8366c3e2 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sat, 19 Jan 2019 14:07:17 +0100 Subject: [PATCH 1/4] [core] Update only affected widgets on input event When receiving an input event, only update affected widgets, identified by their instance ID. see #353 --- bumblebee/engine.py | 24 +++++++++++++++++++++--- bumblebee/input.py | 5 +++++ bumblebee/output.py | 23 +++++++++++++++++++++++ bumblebee/util.py | 2 -- 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/bumblebee/engine.py b/bumblebee/engine.py index f515470..6c4cff5 100644 --- a/bumblebee/engine.py +++ b/bumblebee/engine.py @@ -71,7 +71,7 @@ class Module(object): return widget def errorWidget(self): - msg = self.error + msg = self.error or "n/a" if len(msg) > 10: msg = "{}...".format(msg[0:7]) return bumblebee.output.Widget(full_text="error: {}".format(msg)) @@ -264,14 +264,32 @@ class Engine(object): def run(self): """Start the event loop""" self._output.start() + event = None while self.running(): - self.write_output() + if event: + self.patch_output(event) + else: + self.write_output() if self.running(): - self.input.wait(float(self._config.get("interval", 1))) + event = self.input.wait(float(self._config.get("interval", 1))) self._output.stop() self.input.stop() + def patch_output(self, event): + for module in self._modules: + widget = module.widget_by_id(event["instance"]) + if not widget: continue + # this widget was affected by the event -> update + module.update_wrapper(module.widgets()) + widget = module.errorWidget() + if module.error is None: + widget = module.widget_by_id(event["instance"]) + widget.link_module(module) + self._output.replace(event, module, widget) + self._output.flush() + self._output.end() + def write_output(self): self._output.begin() for module in self._modules: diff --git a/bumblebee/input.py b/bumblebee/input.py index 5fb3032..9b760bc 100644 --- a/bumblebee/input.py +++ b/bumblebee/input.py @@ -45,6 +45,7 @@ def read_input(inp): try: event = json.loads(line) if "instance" in event: + inp.event = event inp.callback(event) inp.redraw() else: @@ -66,6 +67,7 @@ class I3BarInput(object): self.global_id = str(uuid.uuid4()) self.need_event = False self.has_event = False + self.event = None self._condition = threading.Condition() def start(self): @@ -87,6 +89,9 @@ class I3BarInput(object): def wait(self, timeout): self._condition.wait(timeout) + rv = self.event if self.has_event else None + self.has_event = False + return rv def _wait(self): while not self.has_event: diff --git a/bumblebee/output.py b/bumblebee/output.py index 35c1895..733aba4 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -105,6 +105,29 @@ class I3BarOutput(object): self._started = False self._config = config + def replace(self, event, module, new_widget): + full_text = new_widget.full_text() + padding = self._theme.padding(new_widget) + prefix = self._theme.prefix(new_widget, padding) + suffix = self._theme.suffix(new_widget, padding) + if prefix: + full_text = u"{}{}".format(prefix, full_text) + if suffix: + full_text = u"{}{}".format(full_text, suffix) + separator = self._theme.separator(new_widget) + widget_data = { + u"full_text": full_text, + "color": self._theme.fg(new_widget), + "background": self._theme.bg(new_widget), + "separator_block_width": self._theme.separator_block_width(new_widget), + "separator": True if separator is None else False, + "min_width": None, + "align": self._theme.align(new_widget), + "instance": new_widget.id, + "name": module.id, + } + self._widgets = [w if not "instance" in w or w["instance"] != event["instance"] else widget_data for w in self._widgets] + def started(self): return self._started diff --git a/bumblebee/util.py b/bumblebee/util.py index f3545ac..d41d024 100644 --- a/bumblebee/util.py +++ b/bumblebee/util.py @@ -20,7 +20,6 @@ def asbool(val): return val in ("t", "true", "y", "yes", "on", "1") def execute(cmd, wait=True): - logging.info("executing command '{}'".format(cmd)) args = shlex.split(cmd) proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) rv = None @@ -35,7 +34,6 @@ def execute(cmd, wait=True): else: rv = out - logging.info(u"command returned '{}'".format("" if not rv else rv)) return rv def bytefmt(num): From 2b91ce586116e497aff02ae98915383db683b97a Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sat, 19 Jan 2019 14:12:32 +0100 Subject: [PATCH 2/4] [core/output] Refactor full/partial refresh Creating the "raw", cached widget data out of the widget object was done two places now. see #353 --- bumblebee/output.py | 68 +++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 733aba4..b52857a 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -106,27 +106,8 @@ class I3BarOutput(object): self._config = config def replace(self, event, module, new_widget): - full_text = new_widget.full_text() - padding = self._theme.padding(new_widget) - prefix = self._theme.prefix(new_widget, padding) - suffix = self._theme.suffix(new_widget, padding) - if prefix: - full_text = u"{}{}".format(prefix, full_text) - if suffix: - full_text = u"{}{}".format(full_text, suffix) - separator = self._theme.separator(new_widget) - widget_data = { - u"full_text": full_text, - "color": self._theme.fg(new_widget), - "background": self._theme.bg(new_widget), - "separator_block_width": self._theme.separator_block_width(new_widget), - "separator": True if separator is None else False, - "min_width": None, - "align": self._theme.align(new_widget), - "instance": new_widget.id, - "name": module.id, - } - self._widgets = [w if not "instance" in w or w["instance"] != event["instance"] else widget_data for w in self._widgets] + data = self.widget_data(module, new_widget) + self._widgets = [w if not "instance" in w or w["instance"] != event["instance"] else data for w in self._widgets] def started(self): return self._started @@ -140,19 +121,37 @@ class I3BarOutput(object): """Finish i3bar protocol""" sys.stdout.write("]\n") - def draw(self, widget, module=None, engine=None): - """Draw a single widget""" + def widget_data(self, module, widget): full_text = widget.full_text() - if widget.get_module() and widget.get_module().hidden(): - return padding = self._theme.padding(widget) prefix = self._theme.prefix(widget, padding) suffix = self._theme.suffix(widget, padding) - if prefix: full_text = u"{}{}".format(prefix, full_text) if suffix: full_text = u"{}{}".format(full_text, suffix) + separator = self._theme.separator(widget) + width = self._theme.minwidth(widget) + + if width: + full_text = full_text.ljust(len(width) + len(prefix) + len(suffix)) + return { + u"full_text": full_text, + "color": self._theme.fg(widget), + "background": self._theme.bg(widget), + "separator_block_width": self._theme.separator_block_width(widget), + "separator": True if separator is None else False, + "min_width": None, + "align": self._theme.align(widget), + "instance": widget.id, + "name": module.id, + } + + + def draw(self, widget, module=None, engine=None): + """Draw a single widget""" + if widget.get_module() and widget.get_module().hidden(): + return separator = self._theme.separator(widget) if separator: @@ -163,23 +162,8 @@ class I3BarOutput(object): "background": self._theme.separator_bg(widget), "separator_block_width": self._theme.separator_block_width(widget), }) - width = self._theme.minwidth(widget) - if width: - full_text = full_text.ljust(len(width) + len(prefix) + len(suffix)) - - self._widgets.append({ - u"full_text": full_text, - "color": self._theme.fg(widget), - "background": self._theme.bg(widget), - "separator_block_width": self._theme.separator_block_width(widget), - "separator": True if separator is None else False, - "min_width": None, -# "min_width": width + "A"*(len(prefix) + len(suffix)) if width else None, - "align": self._theme.align(widget), - "instance": widget.id, - "name": module.id, - }) + self._widgets.append(self.widget_data(module, widget)) def begin(self): """Start one output iteration""" From 23be352ec30ce5aeb9423d8aad8ecfca77cb7d12 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sat, 19 Jan 2019 14:18:48 +0100 Subject: [PATCH 3/4] [core/engine] Ensure that full updates still take place regularly Ensure that a full update still happens, even if continuous scrolling triggers new events (and therefore, partial updates) all the time. see #353 --- bumblebee/engine.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bumblebee/engine.py b/bumblebee/engine.py index 6c4cff5..fb6a7d3 100644 --- a/bumblebee/engine.py +++ b/bumblebee/engine.py @@ -265,13 +265,16 @@ class Engine(object): """Start the event loop""" self._output.start() event = None + last_full = time.time() + interval = float(self._config.get("interval", 1)) while self.running(): - if event: + if event and time.time() - last_full < interval: self.patch_output(event) else: + last_full = time.time() self.write_output() if self.running(): - event = self.input.wait(float(self._config.get("interval", 1))) + event = self.input.wait(interval) self._output.stop() self.input.stop() From b377a93e49fe7d49c89deecf9fbbf9fa39f67990 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sun, 20 Jan 2019 07:34:27 +0100 Subject: [PATCH 4/4] [core] Add debug output for partial bar update see #353 --- bumblebee/engine.py | 2 ++ bumblebee/input.py | 1 + 2 files changed, 3 insertions(+) diff --git a/bumblebee/engine.py b/bumblebee/engine.py index fb6a7d3..f441269 100644 --- a/bumblebee/engine.py +++ b/bumblebee/engine.py @@ -269,8 +269,10 @@ class Engine(object): interval = float(self._config.get("interval", 1)) while self.running(): if event and time.time() - last_full < interval: + log.debug("partial output update ({} {} {})".format(event, time.time(), last_full)) self.patch_output(event) else: + log.debug("full update: {} {} ({})".format(time.time(), last_full, time.time() - last_full)) last_full = time.time() self.write_output() if self.running(): diff --git a/bumblebee/input.py b/bumblebee/input.py index 9b760bc..77c0693 100644 --- a/bumblebee/input.py +++ b/bumblebee/input.py @@ -90,6 +90,7 @@ class I3BarInput(object): def wait(self, timeout): self._condition.wait(timeout) rv = self.event if self.has_event else None + log.debug("received input event: {}".format(rv)) self.has_event = False return rv