[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
|
@ -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
120
tests/test_i3barinput.py
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue