2016-12-04 08:37:01 +01:00
|
|
|
"""Core application engine"""
|
|
|
|
|
2016-12-09 07:27:01 +01:00
|
|
|
import os
|
2017-10-21 13:06:36 +02:00
|
|
|
import json
|
2018-01-07 20:25:32 +01:00
|
|
|
import time
|
2016-12-09 07:27:01 +01:00
|
|
|
import pkgutil
|
2017-07-14 09:00:47 +02:00
|
|
|
import logging
|
2016-12-04 11:09:10 +01:00
|
|
|
import importlib
|
2016-12-04 16:23:44 +01:00
|
|
|
import bumblebee.error
|
2016-12-09 07:27:01 +01:00
|
|
|
import bumblebee.modules
|
|
|
|
|
2017-07-14 09:00:47 +02:00
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
2017-07-12 18:37:59 +02:00
|
|
|
try:
|
2017-07-14 09:01:13 +02:00
|
|
|
from ConfigParser import RawConfigParser
|
2017-07-12 18:37:59 +02:00
|
|
|
except ImportError:
|
2017-07-14 09:01:13 +02:00
|
|
|
from configparser import RawConfigParser
|
2017-07-12 18:37:59 +02:00
|
|
|
|
2016-12-09 08:43:14 +01:00
|
|
|
def all_modules():
|
2016-12-09 07:27:01 +01:00
|
|
|
"""Return a list of available modules"""
|
|
|
|
result = []
|
|
|
|
path = os.path.dirname(bumblebee.modules.__file__)
|
|
|
|
for mod in [name for _, name, _ in pkgutil.iter_modules([path])]:
|
|
|
|
result.append({
|
|
|
|
"name": mod
|
|
|
|
})
|
|
|
|
return result
|
2016-12-04 11:09:10 +01:00
|
|
|
|
|
|
|
class Module(object):
|
|
|
|
"""Module instance base class
|
|
|
|
|
|
|
|
Objects of this type represent the modules that
|
|
|
|
the user configures. Concrete module implementations
|
|
|
|
(e.g. CPU utilization, disk usage, etc.) derive from
|
|
|
|
this base class.
|
|
|
|
"""
|
2016-12-09 08:23:53 +01:00
|
|
|
def __init__(self, engine, config={}, widgets=[]):
|
2016-12-11 07:18:06 +01:00
|
|
|
self.name = config.get("name", self.__module__.split(".")[-1])
|
2016-12-09 08:23:53 +01:00
|
|
|
self._config = config
|
2016-12-11 07:18:06 +01:00
|
|
|
self.id = self.name
|
2018-01-12 18:39:36 +01:00
|
|
|
self.error = None
|
2018-01-07 20:25:32 +01:00
|
|
|
self._next = int(time.time())
|
|
|
|
self._default_interval = 0
|
2019-08-01 20:41:13 +02:00
|
|
|
self._engine = engine
|
2017-07-12 18:37:59 +02:00
|
|
|
|
|
|
|
self._configFile = None
|
2017-10-13 17:06:18 +02:00
|
|
|
for cfg in [os.path.expanduser("~/.bumblebee-status.conf"), os.path.expanduser("~/.config/bumblebee-status.conf")]:
|
2017-07-12 18:37:59 +02:00
|
|
|
if os.path.exists(cfg):
|
2017-07-14 09:01:13 +02:00
|
|
|
self._configFile = RawConfigParser()
|
2017-07-12 18:37:59 +02:00
|
|
|
self._configFile.read(cfg)
|
2017-07-14 17:11:20 +02:00
|
|
|
log.debug("reading configuration file {}".format(cfg))
|
2017-07-12 18:37:59 +02:00
|
|
|
break
|
|
|
|
|
2017-07-14 09:00:47 +02:00
|
|
|
if self._configFile is not None and self._configFile.has_section("module-parameters"):
|
|
|
|
log.debug(self._configFile.items("module-parameters"))
|
2016-12-08 08:44:54 +01:00
|
|
|
self._widgets = []
|
|
|
|
if widgets:
|
|
|
|
self._widgets = widgets if isinstance(widgets, list) else [widgets]
|
|
|
|
|
2019-08-01 20:41:13 +02:00
|
|
|
def theme(self):
|
|
|
|
return self._engine.theme()
|
|
|
|
|
2018-11-17 13:38:35 +01:00
|
|
|
def widgets(self, widgets=None):
|
2016-12-08 08:44:54 +01:00
|
|
|
"""Return the widgets to draw for this module"""
|
2018-11-17 13:38:35 +01:00
|
|
|
if widgets:
|
2019-08-24 15:01:40 +02:00
|
|
|
self._widgets = widgets if isinstance(widgets, list) else [widgets]
|
2016-12-08 08:44:54 +01:00
|
|
|
return self._widgets
|
2016-12-04 11:09:10 +01:00
|
|
|
|
2017-08-12 17:03:04 +02:00
|
|
|
def hidden(self):
|
|
|
|
return False
|
|
|
|
|
2016-12-10 08:37:04 +01:00
|
|
|
def widget(self, name):
|
|
|
|
for widget in self._widgets:
|
|
|
|
if widget.name == name:
|
|
|
|
return widget
|
|
|
|
|
2018-01-12 18:39:36 +01:00
|
|
|
def errorWidget(self):
|
2019-01-26 19:40:08 +01:00
|
|
|
msg = self.error
|
2018-01-12 18:39:36 +01:00
|
|
|
if len(msg) > 10:
|
|
|
|
msg = "{}...".format(msg[0:7])
|
|
|
|
return bumblebee.output.Widget(full_text="error: {}".format(msg))
|
|
|
|
|
2016-12-11 12:23:33 +01:00
|
|
|
def widget_by_id(self, uid):
|
|
|
|
for widget in self._widgets:
|
|
|
|
if widget.id == uid:
|
|
|
|
return widget
|
|
|
|
return None
|
|
|
|
|
2016-12-09 07:11:23 +01:00
|
|
|
def update(self, widgets):
|
|
|
|
"""By default, update() is a NOP"""
|
|
|
|
pass
|
|
|
|
|
2018-01-07 20:25:32 +01:00
|
|
|
def update_wrapper(self, widgets):
|
|
|
|
if self._next > int(time.time()):
|
|
|
|
return
|
2018-01-12 18:39:36 +01:00
|
|
|
try:
|
|
|
|
self.error = None
|
|
|
|
self.update(self._widgets)
|
|
|
|
except Exception as e:
|
|
|
|
log.error("error updating '{}': {}".format(self.name, str(e)))
|
|
|
|
self.error = str(e)
|
2018-01-07 20:25:32 +01:00
|
|
|
self._next += int(self.parameter("interval", self._default_interval))*60
|
|
|
|
|
|
|
|
def interval(self, intvl):
|
|
|
|
self._default_interval = intvl
|
|
|
|
self._next = int(time.time())
|
|
|
|
|
|
|
|
def update_all(self):
|
|
|
|
self.update_wrapper(self._widgets)
|
2017-03-05 09:35:56 +01:00
|
|
|
|
2017-08-02 05:52:53 +02:00
|
|
|
def has_parameter(self, name):
|
|
|
|
v = self.parameter(name)
|
|
|
|
return v is not None
|
|
|
|
|
2016-12-09 07:57:21 +01:00
|
|
|
def parameter(self, name, default=None):
|
|
|
|
"""Return the config parameter 'name' for this module"""
|
2016-12-11 07:18:06 +01:00
|
|
|
name = "{}.{}".format(self.name, name)
|
2017-07-12 18:37:59 +02:00
|
|
|
value = self._config["config"].get(name, default)
|
|
|
|
if value == default:
|
|
|
|
try:
|
|
|
|
value = self._configFile.get("module-parameters", name)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
return value
|
2016-12-09 07:57:21 +01:00
|
|
|
|
2016-12-11 08:25:54 +01:00
|
|
|
def threshold_state(self, value, warn, crit):
|
|
|
|
if value > float(self.parameter("critical", crit)):
|
|
|
|
return "critical"
|
|
|
|
if value > float(self.parameter("warning", warn)):
|
|
|
|
return "warning"
|
|
|
|
return None
|
|
|
|
|
2016-12-04 08:37:01 +01:00
|
|
|
class Engine(object):
|
|
|
|
"""Engine for driving the application
|
|
|
|
|
|
|
|
This class connects input/output, instantiates all
|
|
|
|
required modules and drives the "event loop"
|
|
|
|
"""
|
2018-05-30 10:42:31 +02:00
|
|
|
def __init__(self, config, output=None, inp=None, theme=None):
|
2016-12-04 12:26:20 +01:00
|
|
|
self._output = output
|
2016-12-09 07:57:21 +01:00
|
|
|
self._config = config
|
2016-12-04 08:37:01 +01:00
|
|
|
self._running = True
|
2016-12-04 11:09:10 +01:00
|
|
|
self._modules = []
|
2016-12-09 19:29:16 +01:00
|
|
|
self.input = inp
|
2016-12-11 07:18:06 +01:00
|
|
|
self._aliases = self._read_aliases()
|
2016-12-04 11:09:10 +01:00
|
|
|
self.load_modules(config.modules())
|
2017-06-10 14:08:49 +02:00
|
|
|
self._current_module = None
|
2018-05-30 10:42:31 +02:00
|
|
|
self._theme = theme
|
2016-12-10 12:14:12 +01:00
|
|
|
|
2017-10-21 08:46:48 +02:00
|
|
|
if bumblebee.util.asbool(config.get("engine.workspacewheel", "true")):
|
2017-10-21 13:06:36 +02:00
|
|
|
if bumblebee.util.asbool(config.get("engine.workspacewrap", "true")):
|
|
|
|
self.input.register_callback(None, bumblebee.input.WHEEL_UP,
|
|
|
|
"i3-msg workspace prev_on_output")
|
|
|
|
self.input.register_callback(None, bumblebee.input.WHEEL_DOWN,
|
|
|
|
"i3-msg workspace next_on_output")
|
|
|
|
else:
|
|
|
|
self.input.register_callback(None, bumblebee.input.WHEEL_UP,
|
|
|
|
cmd=self._prev_workspace)
|
|
|
|
self.input.register_callback(None, bumblebee.input.WHEEL_DOWN,
|
|
|
|
cmd=self._next_workspace)
|
2018-05-30 10:42:31 +02:00
|
|
|
if bumblebee.util.asbool(config.get("engine.collapsible", "true")):
|
|
|
|
self.input.register_callback(None, bumblebee.input.MIDDLE_MOUSE,
|
|
|
|
cmd=self._toggle_minimize)
|
2016-12-10 12:14:12 +01:00
|
|
|
|
2016-12-09 19:29:16 +01:00
|
|
|
self.input.start()
|
2016-12-04 11:09:10 +01:00
|
|
|
|
2019-08-01 20:41:13 +02:00
|
|
|
def theme(self):
|
|
|
|
return self._theme
|
|
|
|
|
2018-05-30 10:42:31 +02:00
|
|
|
def _toggle_minimize(self, event):
|
|
|
|
for module in self._modules:
|
2018-09-15 14:40:52 +02:00
|
|
|
widget = module.widget_by_id(event["instance"])
|
|
|
|
if widget:
|
2018-05-30 10:42:31 +02:00
|
|
|
log.debug("module {} found - toggle minimize".format(module.id))
|
2018-09-15 14:40:52 +02:00
|
|
|
widget.toggle_minimize()
|
2018-05-30 10:42:31 +02:00
|
|
|
|
2017-10-21 13:06:36 +02:00
|
|
|
def _prev_workspace(self, event):
|
|
|
|
self._change_workspace(-1)
|
|
|
|
|
|
|
|
def _next_workspace(self, event):
|
|
|
|
self._change_workspace(1)
|
|
|
|
|
|
|
|
def _change_workspace(self, amount):
|
|
|
|
try:
|
|
|
|
active_output = None
|
|
|
|
active_index = -1
|
|
|
|
output_workspaces = {}
|
|
|
|
data = json.loads(bumblebee.util.execute("i3-msg -t get_workspaces"))
|
|
|
|
for workspace in data:
|
|
|
|
output_workspaces.setdefault(workspace["output"], []).append(workspace)
|
|
|
|
if workspace["focused"]:
|
|
|
|
active_output = workspace["output"]
|
|
|
|
active_index = len(output_workspaces[workspace["output"]]) - 1
|
|
|
|
if (active_index + amount) < 0:
|
|
|
|
return
|
|
|
|
if (active_index + amount) >= len(output_workspaces[active_output]):
|
|
|
|
return
|
|
|
|
|
|
|
|
while amount != 0:
|
|
|
|
if amount > 0:
|
|
|
|
bumblebee.util.execute("i3-msg workspace next_on_output")
|
|
|
|
amount = amount - 1
|
|
|
|
if amount < 0:
|
|
|
|
bumblebee.util.execute("i3-msg workspace prev_on_output")
|
|
|
|
amount = amount + 1
|
|
|
|
except Exception as e:
|
|
|
|
log.error("failed to change workspace: {}".format(e))
|
|
|
|
|
2016-12-15 19:41:50 +01:00
|
|
|
def modules(self):
|
|
|
|
return self._modules
|
|
|
|
|
2016-12-04 11:09:10 +01:00
|
|
|
def load_modules(self, modules):
|
|
|
|
"""Load specified modules and return them as list"""
|
|
|
|
for module in modules:
|
2016-12-15 19:41:50 +01:00
|
|
|
mod = self._load_module(module["module"], module["name"])
|
|
|
|
self._modules.append(mod)
|
|
|
|
self._register_module_callbacks(mod)
|
2016-12-04 11:09:10 +01:00
|
|
|
return self._modules
|
|
|
|
|
2016-12-15 19:41:50 +01:00
|
|
|
def _register_module_callbacks(self, module):
|
|
|
|
buttons = [
|
2017-10-13 17:06:18 +02:00
|
|
|
{"name": "left-click", "id": bumblebee.input.LEFT_MOUSE},
|
|
|
|
{"name": "middle-click", "id": bumblebee.input.MIDDLE_MOUSE},
|
|
|
|
{"name": "right-click", "id": bumblebee.input.RIGHT_MOUSE},
|
|
|
|
{"name": "wheel-up", "id": bumblebee.input.WHEEL_UP},
|
|
|
|
{"name": "wheel-down", "id": bumblebee.input.WHEEL_DOWN},
|
2016-12-15 19:41:50 +01:00
|
|
|
]
|
|
|
|
for button in buttons:
|
|
|
|
if module.parameter(button["name"], None):
|
|
|
|
self.input.register_callback(obj=module,
|
|
|
|
button=button["id"], cmd=module.parameter(button["name"]))
|
|
|
|
|
2016-12-11 07:18:06 +01:00
|
|
|
def _read_aliases(self):
|
|
|
|
result = {}
|
|
|
|
for module in all_modules():
|
2018-02-01 18:44:48 +01:00
|
|
|
try:
|
|
|
|
mod = importlib.import_module("bumblebee.modules.{}".format(module["name"]))
|
|
|
|
for alias in getattr(mod, "ALIASES", []):
|
|
|
|
result[alias] = module["name"]
|
|
|
|
except Exception as error:
|
|
|
|
log.warning("failed to import {}: {}".format(module["name"], str(error)))
|
2016-12-11 07:18:06 +01:00
|
|
|
return result
|
|
|
|
|
2016-12-09 11:49:59 +01:00
|
|
|
def _load_module(self, module_name, config_name=None):
|
2016-12-04 11:09:10 +01:00
|
|
|
"""Load specified module and return it as object"""
|
2016-12-11 07:18:06 +01:00
|
|
|
if module_name in self._aliases:
|
|
|
|
config_name is config_name if config_name else module_name
|
|
|
|
module_name = self._aliases[module_name]
|
2016-12-09 08:43:14 +01:00
|
|
|
if config_name is None:
|
2016-12-09 07:57:21 +01:00
|
|
|
config_name = module_name
|
2019-03-01 21:02:51 +01:00
|
|
|
err = None
|
2016-12-04 16:23:44 +01:00
|
|
|
try:
|
|
|
|
module = importlib.import_module("bumblebee.modules.{}".format(module_name))
|
|
|
|
except ImportError as error:
|
2019-03-01 21:02:51 +01:00
|
|
|
err = error
|
|
|
|
log.fatal("failed to import {}: {}".format(module_name, str(error)))
|
|
|
|
if err:
|
|
|
|
raise bumblebee.error.ModuleLoadError("unable to load module {}: {}".format(module_name, str(err)))
|
2016-12-09 08:23:53 +01:00
|
|
|
return getattr(module, "Module")(self, {
|
|
|
|
"name": config_name,
|
|
|
|
"config": self._config
|
|
|
|
})
|
2016-12-04 08:37:01 +01:00
|
|
|
|
|
|
|
def running(self):
|
|
|
|
"""Check whether the event loop is running"""
|
|
|
|
return self._running
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
"""Stop the event loop"""
|
|
|
|
self._running = False
|
|
|
|
|
2017-06-10 14:08:49 +02:00
|
|
|
def current_module(self):
|
|
|
|
return self._current_module.__module__
|
|
|
|
|
2016-12-04 08:37:01 +01:00
|
|
|
def run(self):
|
|
|
|
"""Start the event loop"""
|
2016-12-04 12:26:20 +01:00
|
|
|
self._output.start()
|
2016-12-04 08:37:01 +01:00
|
|
|
while self.running():
|
2019-01-26 19:40:08 +01:00
|
|
|
self.write_output()
|
2016-12-04 17:45:42 +01:00
|
|
|
if self.running():
|
2019-01-26 19:40:08 +01:00
|
|
|
self.input.wait(float(self._config.get("interval", 1)))
|
2016-12-04 08:37:01 +01:00
|
|
|
|
2016-12-04 12:26:20 +01:00
|
|
|
self._output.stop()
|
2016-12-09 19:29:16 +01:00
|
|
|
self.input.stop()
|
2016-12-04 12:26:20 +01:00
|
|
|
|
2018-05-06 10:31:46 +02:00
|
|
|
def write_output(self):
|
|
|
|
self._output.begin()
|
|
|
|
for module in self._modules:
|
|
|
|
self._current_module = module
|
|
|
|
module.update_wrapper(module.widgets())
|
2018-06-04 14:50:51 +02:00
|
|
|
if module.error is None:
|
2018-09-15 14:40:52 +02:00
|
|
|
for widget in module.widgets():
|
2018-05-06 10:31:46 +02:00
|
|
|
widget.link_module(module)
|
|
|
|
self._output.draw(widget=widget, module=module, engine=self)
|
|
|
|
else:
|
|
|
|
self._output.draw(widget=module.errorWidget(), module=module, engine=self)
|
|
|
|
self._output.flush()
|
|
|
|
self._output.end()
|
|
|
|
|
2016-12-04 08:37:01 +01:00
|
|
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|