[core] Add input processing
Create infrastructure for input event handling and add i3bar event processing. For each event, callbacks can be registered in the input module. Modules and widgets both identify themselves using a unique ID (the module name for modules, a generated UUID for the widgets). This ID is then used for registering the callbacks. This is possible since both widgets and modules are statically allocated & do not change their IDs. Callback actions can be either callable Python objects (in which case the event is passed as parameter), or strings, in which case the string is interpreted as a shell command. see #23
This commit is contained in:
parent
fa30b9505b
commit
e72c25b0bc
10 changed files with 274 additions and 19 deletions
|
@ -30,6 +30,7 @@ class Module(object):
|
|||
self._config = config
|
||||
if "name" not in self._config:
|
||||
self._config["name"] = self.name
|
||||
self.id = self._config["name"]
|
||||
self._widgets = []
|
||||
if widgets:
|
||||
self._widgets = widgets if isinstance(widgets, list) else [widgets]
|
||||
|
@ -53,12 +54,14 @@ class Engine(object):
|
|||
This class connects input/output, instantiates all
|
||||
required modules and drives the "event loop"
|
||||
"""
|
||||
def __init__(self, config, output=None):
|
||||
def __init__(self, config, output=None, inp=None):
|
||||
self._output = output
|
||||
self._config = config
|
||||
self._running = True
|
||||
self._modules = []
|
||||
self.input = inp
|
||||
self.load_modules(config.modules())
|
||||
self.input.start()
|
||||
|
||||
def load_modules(self, modules):
|
||||
"""Load specified modules and return them as list"""
|
||||
|
@ -96,12 +99,13 @@ class Engine(object):
|
|||
module.update(module.widgets())
|
||||
for widget in module.widgets():
|
||||
widget.link_module(module)
|
||||
self._output.draw(widget=widget, engine=self)
|
||||
self._output.draw(widget=widget, module=module, engine=self)
|
||||
self._output.flush()
|
||||
self._output.end()
|
||||
if self.running():
|
||||
time.sleep(1)
|
||||
|
||||
self._output.stop()
|
||||
self.input.stop()
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
||||
|
|
77
bumblebee/input.py
Normal file
77
bumblebee/input.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
"""Input classes"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import uuid
|
||||
import time
|
||||
import threading
|
||||
import bumblebee.util
|
||||
|
||||
LEFT_MOUSE = 1
|
||||
RIGHT_MOUSE = 3
|
||||
|
||||
def read_input(inp):
|
||||
"""Read i3bar input and execute callbacks"""
|
||||
while inp.running:
|
||||
line = sys.stdin.readline().strip(",").strip()
|
||||
inp.has_event = True
|
||||
try:
|
||||
event = json.loads(line)
|
||||
inp.callback(event)
|
||||
except ValueError:
|
||||
pass
|
||||
inp.has_event = True
|
||||
inp.clean_exit = True
|
||||
|
||||
class I3BarInput(object):
|
||||
"""Process incoming events from the i3bar"""
|
||||
def __init__(self):
|
||||
self.running = True
|
||||
self._thread = threading.Thread(target=read_input, args=(self,))
|
||||
self._callbacks = {}
|
||||
self.clean_exit = False
|
||||
self.global_id = str(uuid.uuid4())
|
||||
self.need_event = False
|
||||
self.has_event = False
|
||||
|
||||
def start(self):
|
||||
"""Start asynchronous input processing"""
|
||||
self._thread.start()
|
||||
|
||||
def alive(self):
|
||||
"""Check whether the input processing is still active"""
|
||||
return self._thread.is_alive()
|
||||
|
||||
def stop(self):
|
||||
"""Stop asynchronous input processing"""
|
||||
if self.need_event:
|
||||
while not self.has_event:
|
||||
time.sleep(0.1)
|
||||
self.running = False
|
||||
self._thread.join()
|
||||
return self.clean_exit
|
||||
|
||||
def register_callback(self, obj, button, cmd):
|
||||
"""Register a callback function or system call"""
|
||||
uid = self.global_id
|
||||
if obj:
|
||||
uid = obj.id
|
||||
|
||||
if uid not in self._callbacks:
|
||||
self._callbacks[uid] = {}
|
||||
self._callbacks[uid][button] = cmd
|
||||
|
||||
def callback(self, event):
|
||||
"""Execute callback action for an incoming event"""
|
||||
cmd = self._callbacks.get(self.global_id, {})
|
||||
cmd = self._callbacks.get(event["name"], cmd)
|
||||
cmd = self._callbacks.get(event["instance"], cmd)
|
||||
cmd = cmd.get(event["button"], None)
|
||||
if cmd is None:
|
||||
return
|
||||
if callable(cmd):
|
||||
cmd(event)
|
||||
else:
|
||||
bumblebee.util.execute(cmd)
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -11,6 +11,8 @@ class Module(bumblebee.engine.Module):
|
|||
bumblebee.output.Widget(full_text=self.utilization)
|
||||
)
|
||||
self._utilization = psutil.cpu_percent(percpu=False)
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="gnome-system-monitor")
|
||||
|
||||
def utilization(self):
|
||||
return "{:05.02f}%".format(self._utilization)
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
|
||||
import sys
|
||||
import json
|
||||
import uuid
|
||||
|
||||
class Widget(object):
|
||||
"""Represents a single visible block in the status bar"""
|
||||
def __init__(self, full_text):
|
||||
self._full_text = full_text
|
||||
self.module = None
|
||||
self.id = str(uuid.uuid4())
|
||||
|
||||
def link_module(self, module):
|
||||
"""Set the module that spawned this widget
|
||||
|
@ -43,7 +45,7 @@ class I3BarOutput(object):
|
|||
"""Finish i3bar protocol"""
|
||||
sys.stdout.write("]\n")
|
||||
|
||||
def draw(self, widget, engine=None):
|
||||
def draw(self, widget, module=None, engine=None):
|
||||
"""Draw a single widget"""
|
||||
full_text = widget.full_text()
|
||||
padding = self._theme.padding(widget)
|
||||
|
@ -68,6 +70,8 @@ class I3BarOutput(object):
|
|||
"background": self._theme.bg(widget),
|
||||
"separator_block_width": self._theme.separator_block_width(widget),
|
||||
"separator": True if separator is None else False,
|
||||
"instance": widget.id,
|
||||
"name": module.id,
|
||||
})
|
||||
|
||||
def begin(self):
|
||||
|
|
21
bumblebee/util.py
Normal file
21
bumblebee/util.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import shlex
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
from exceptions import RuntimeError
|
||||
except ImportError:
|
||||
# Python3 doesn't require this anymore
|
||||
pass
|
||||
|
||||
def execute(cmd, wait=True):
|
||||
args = shlex.split(cmd)
|
||||
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
if wait:
|
||||
out, _ = proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError("{} exited with {}".format(cmd, proc.returncode))
|
||||
return out.decode("utf-8")
|
||||
return None
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
Loading…
Add table
Add a link
Reference in a new issue