[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:
Tobi-wan Kenobi 2016-12-09 19:29:16 +01:00
parent fa30b9505b
commit e72c25b0bc
10 changed files with 274 additions and 19 deletions

View file

@ -6,11 +6,11 @@ from bumblebee.error import ModuleLoadError
from bumblebee.engine import Engine
from bumblebee.config import Config
from tests.util import MockOutput
from tests.util import MockOutput, MockInput
class TestEngine(unittest.TestCase):
def setUp(self):
self.engine = Engine(config=Config(), output=MockOutput())
self.engine = Engine(config=Config(), output=MockOutput(), inp=MockInput())
self.singleWidgetModule = [{"module": "test", "name": "a"}]
self.testModule = "test"
self.invalidModule = "no-such-module"

120
tests/test_i3barinput.py Normal file
View file

@ -0,0 +1,120 @@
# pylint: disable=C0103,C0111
import unittest
import json
import subprocess
import mock
import bumblebee.input
from bumblebee.input import I3BarInput
from tests.util import MockWidget, MockModule
class TestI3BarInput(unittest.TestCase):
def setUp(self):
self.inp = I3BarInput()
self.inp.need_event = True
self.anyModule = MockModule()
self.anyWidget = MockWidget("test")
self.anyModule.id = "test-module"
self._called = 0
def callback(self, event):
self._called += 1
@mock.patch("sys.stdin")
def test_basic_read_event(self, mock_input):
mock_input.readline.return_value = ""
self.inp.start()
self.inp.stop()
mock_input.readline.assert_any_call()
@mock.patch("sys.stdin")
def test_ignore_invalid_data(self, mock_input):
mock_input.readline.return_value = "garbage"
self.inp.start()
self.assertEquals(self.inp.alive(), True)
self.assertEquals(self.inp.stop(), True)
mock_input.readline.assert_any_call()
@mock.patch("sys.stdin")
def test_ignore_invalid_event(self, mock_input):
mock_input.readline.return_value = json.dumps({
"name": None,
"instance": None,
"button": None,
})
self.inp.start()
self.assertEquals(self.inp.alive(), True)
self.assertEquals(self.inp.stop(), True)
mock_input.readline.assert_any_call()
@mock.patch("sys.stdin")
def test_global_callback(self, mock_input):
mock_input.readline.return_value = json.dumps({
"name": "somename",
"instance": "someinstance",
"button": bumblebee.input.LEFT_MOUSE,
})
self.inp.register_callback(None, button=1, cmd=self.callback)
self.inp.start()
self.assertEquals(self.inp.stop(), True)
mock_input.readline.assert_any_call()
self.assertTrue(self._called > 0)
@mock.patch("sys.stdin")
def test_global_callback_button_missmatch(self, mock_input):
mock_input.readline.return_value = json.dumps({
"name": "somename",
"instance": "someinstance",
"button": bumblebee.input.RIGHT_MOUSE,
})
self.inp.register_callback(None, button=1, cmd=self.callback)
self.inp.start()
self.assertEquals(self.inp.stop(), True)
mock_input.readline.assert_any_call()
self.assertTrue(self._called == 0)
@mock.patch("sys.stdin")
def test_module_callback(self, mock_input):
mock_input.readline.return_value = json.dumps({
"name": self.anyModule.id,
"instance": None,
"button": bumblebee.input.LEFT_MOUSE,
})
self.inp.register_callback(self.anyModule, button=1, cmd=self.callback)
self.inp.start()
self.assertEquals(self.inp.stop(), True)
mock_input.readline.assert_any_call()
self.assertTrue(self._called > 0)
@mock.patch("sys.stdin")
def test_widget_callback(self, mock_input):
mock_input.readline.return_value = json.dumps({
"name": "test",
"instance": self.anyWidget.id,
"button": bumblebee.input.LEFT_MOUSE,
})
self.inp.register_callback(self.anyWidget, button=1, cmd=self.callback)
self.inp.start()
self.assertEquals(self.inp.stop(), True)
mock_input.readline.assert_any_call()
self.assertTrue(self._called > 0)
@mock.patch("subprocess.Popen")
@mock.patch("sys.stdin")
def test_widget_cmd_callback(self, mock_input, mock_output):
mock_input.readline.return_value = json.dumps({
"name": "test",
"instance": self.anyWidget.id,
"button": bumblebee.input.LEFT_MOUSE,
})
self.inp.register_callback(self.anyWidget, button=1, cmd="echo")
self.inp.start()
self.assertEquals(self.inp.stop(), True)
mock_input.readline.assert_any_call()
mock_output.assert_called_with(["echo"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -9,8 +9,7 @@ except ImportError:
from io import StringIO
from bumblebee.output import I3BarOutput
from tests.util import MockWidget
from tests.util import MockTheme
from tests.util import MockWidget, MockTheme, MockModule
class TestI3BarOutput(unittest.TestCase):
def setUp(self):
@ -19,6 +18,7 @@ class TestI3BarOutput(unittest.TestCase):
self.expectedStart = json.dumps({"version": 1, "click_events": True}) + "[\n"
self.expectedStop = "]\n"
self.someWidget = MockWidget("foo bar baz")
self.anyModule = MockModule(None, None)
self.anyColor = "#ababab"
self.anotherColor = "#cccccc"
@ -34,7 +34,7 @@ class TestI3BarOutput(unittest.TestCase):
@mock.patch("sys.stdout", new_callable=StringIO)
def test_draw_single_widget(self, stdout):
self.output.draw(self.someWidget)
self.output.draw(self.someWidget, self.anyModule)
self.output.flush()
result = json.loads(stdout.getvalue())[0]
self.assertEquals(result["full_text"], self.someWidget.full_text())
@ -42,7 +42,7 @@ class TestI3BarOutput(unittest.TestCase):
@mock.patch("sys.stdout", new_callable=StringIO)
def test_draw_multiple_widgets(self, stdout):
for widget in [self.someWidget, self.someWidget]:
self.output.draw(widget)
self.output.draw(widget, self.anyModule)
self.output.flush()
result = json.loads(stdout.getvalue())
for res in result:
@ -61,7 +61,7 @@ class TestI3BarOutput(unittest.TestCase):
@mock.patch("sys.stdout", new_callable=StringIO)
def test_prefix(self, stdout):
self.theme.attr_prefix = " - "
self.output.draw(self.someWidget)
self.output.draw(self.someWidget, self.anyModule)
self.output.flush()
result = json.loads(stdout.getvalue())[0]
self.assertEquals(result["full_text"], "{}{}".format(
@ -71,7 +71,7 @@ class TestI3BarOutput(unittest.TestCase):
@mock.patch("sys.stdout", new_callable=StringIO)
def test_suffix(self, stdout):
self.theme.attr_suffix = " - "
self.output.draw(self.someWidget)
self.output.draw(self.someWidget, self.anyModule)
self.output.flush()
result = json.loads(stdout.getvalue())[0]
self.assertEquals(result["full_text"], "{}{}".format(
@ -82,7 +82,7 @@ class TestI3BarOutput(unittest.TestCase):
def test_bothfix(self, stdout):
self.theme.attr_suffix = " - "
self.theme.attr_prefix = " * "
self.output.draw(self.someWidget)
self.output.draw(self.someWidget, self.anyModule)
self.output.flush()
result = json.loads(stdout.getvalue())[0]
self.assertEquals(result["full_text"], "{}{}{}".format(
@ -95,7 +95,7 @@ class TestI3BarOutput(unittest.TestCase):
def test_colors(self, stdout):
self.theme.attr_fg = self.anyColor
self.theme.attr_bg = self.anotherColor
self.output.draw(self.someWidget)
self.output.draw(self.someWidget, self.anyModule)
self.output.flush()
result = json.loads(stdout.getvalue())[0]
self.assertEquals(result["color"], self.anyColor)

View file

@ -6,8 +6,19 @@ def assertWidgetAttributes(test, widget):
test.assertTrue(isinstance(widget, Widget))
test.assertTrue(hasattr(widget, "full_text"))
class MockInput(object):
def start(self):
pass
def stop(self):
pass
def register_callback(self, obj, button, cmd):
pass
class MockEngine(object):
pass
def __init__(self):
self.input = MockInput()
class MockOutput(object):
def start(self):
@ -16,7 +27,7 @@ class MockOutput(object):
def stop(self):
pass
def draw(self, widget, engine):
def draw(self, widget, engine, module):
engine.stop()
def begin(self):
@ -28,12 +39,17 @@ class MockOutput(object):
def end(self):
pass
class MockModule(object):
def __init__(self, engine=None, config=None):
self.id = None
class MockWidget(Widget):
def __init__(self, text):
super(MockWidget, self).__init__(text)
self._text = text
self.module = None
self.attr_state = "state-default"
self.id = "none"
def state(self):
return self.attr_state