diff --git a/.coveragerc b/.coveragerc index 5dbd5583..283080e8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,5 +1,5 @@ [run] branch = True -source = keymapper +source = /usr/lib/python3.8/site-packages/keymapper concurrency = multiprocessing debug = multiproc diff --git a/DEBIAN/control b/DEBIAN/control index 3458fa73..e371f6eb 100644 --- a/DEBIAN/control +++ b/DEBIAN/control @@ -2,5 +2,5 @@ Package: key-mapper Version: 0.1.0 Architecture: all Maintainer: Sezanzeb -Depends: build-essential, libpython3-dev, libdbus-1-dev, python3, python3-setuptools, python3-evdev, python3-dbus, python3-gi +Depends: build-essential, libpython3-dev, libdbus-1-dev, python3, python3-setuptools, python3-evdev, python3-pydbus, python3-gi Description: A tool to change the mapping of your input device buttons diff --git a/README.md b/README.md index 855420fc..be6ad0d6 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,7 @@ groups ##### Git/pip ```bash -git clone https://github.com/sezanzeb/key-mapper.git -cd key-mapper && sudo python3 setup.py install +sudo pip install git+https://github.com/sezanzeb/key-mapper.git ``` ##### Manjaro/Arch @@ -100,6 +99,6 @@ sudo dpkg -i python3-key-mapper_0.1.0-1_all.deb ```bash pylint keymapper --extension-pkg-whitelist=evdev -sudo pip install -e . && coverage run tests/test.py +sudo pip install . && coverage run tests/test.py coverage combine && coverage report -m ``` diff --git a/bin/key-mapper-service b/bin/key-mapper-service index 7df401c8..2635e715 100755 --- a/bin/key-mapper-service +++ b/bin/key-mapper-service @@ -29,12 +29,11 @@ from argparse import ArgumentParser import gi gi.require_version('GLib', '2.0') from gi.repository import GLib -import dbus.mainloop.glib -from dbus.mainloop.glib import DBusGMainLoop +from pydbus import SessionBus -from keymapper.logger import logger, update_verbosity, log_info, \ +from keymapper.logger import update_verbosity, log_info, \ add_filehandler -from keymapper.daemon import Daemon +from keymapper.daemon import Daemon, BUS_NAME from keymapper.dev.permissions import can_read_devices @@ -60,12 +59,11 @@ if __name__ == '__main__': can_read_devices() - session_bus = dbus.SessionBus(mainloop=DBusGMainLoop()) - name = dbus.service.BusName('keymapper.Control', session_bus) - daemon = Daemon(session_bus, '/') + bus = SessionBus() + loop = GLib.MainLoop() + daemon = Daemon(True, loop) + bus.publish(BUS_NAME, daemon) - atexit.register(daemon.stop) + atexit.register(lambda: daemon.stop(True)) - logger.info('Daemon running, waiting for dbus messages') - mainloop = GLib.MainLoop() - mainloop.run() + loop.run() diff --git a/keymapper/daemon.py b/keymapper/daemon.py index 937f2a97..84bd89d0 100644 --- a/keymapper/daemon.py +++ b/keymapper/daemon.py @@ -19,16 +19,15 @@ # along with key-mapper. If not, see . -"""Starts injecting keycodes based on the configuration.""" +"""Starts injecting keycodes based on the configuration. + +https://github.com/LEW21/pydbus/tree/cc407c8b1d25b7e28a6d661a29f9e661b1c9b964/examples/clientserver # noqa pylint: disable=line-too-long +""" import subprocess -# TODO https://www.freedesktop.org/wiki/Software/DBusBindings/#python -# says "New applications should use pydbus" -import dbus -from dbus import service -import dbus.mainloop.glib +from pydbus import SessionBus from keymapper.logger import logger from keymapper.dev.injector import KeycodeInjector @@ -36,6 +35,9 @@ from keymapper.mapping import Mapping from keymapper.config import config +BUS_NAME = 'keymapper.Control' + + def is_service_running(): """Check if the daemon is running.""" try: @@ -54,23 +56,13 @@ def get_dbus_interface(): ) return Daemon(autoload=False) - try: - bus = dbus.SessionBus() - remote_object = bus.get_object('keymapper.Control', '/') - interface = dbus.Interface(remote_object, 'keymapper.Interface') - logger.debug('Connected to dbus') - except dbus.exceptions.DBusException as error: - logger.warning( - 'Could not connect to the dbus of "key-mapper-service", mapping ' - 'keys only works as long as the window is open.' - ) - logger.debug(error) - return Daemon(autoload=False) + bus = SessionBus() + interface = bus.get(BUS_NAME) return interface -class Daemon(service.Object): +class Daemon: """Starts injecting keycodes based on the configuration. Can be talked to either over dbus or by instantiating it. @@ -80,9 +72,34 @@ class Daemon(service.Object): continue to do so afterwards, but it can't decide to start injecting on its own. """ - def __init__(self, *args, autoload=True, **kwargs): + + dbus = f""" + + + + + + + + + + + + + + + + + + + + """ + + def __init__(self, autoload=True, loop=None): """Constructs the daemon. You still need to run the GLib mainloop.""" + logger.debug('Creating daemon') self.injectors = {} + self.loop = loop if autoload: for device, preset in config.iterate_autoload_presets(): mapping = Mapping() @@ -93,9 +110,7 @@ class Daemon(service.Object): self.injectors[device] = injector except OSError as error: logger.error(error) - super().__init__(*args, **kwargs) - @dbus.service.method('keymapper.Interface', in_signature='s') def stop_injecting(self, device): """Stop injecting the mapping for a single device.""" if self.injectors.get(device) is None: @@ -107,9 +122,6 @@ class Daemon(service.Object): self.injectors[device].stop_injecting() - # TODO if ss is the correct signature for multiple parameters, add an - # example to https://gitlab.freedesktop.org/dbus/dbus-python/-/blob/master/doc/tutorial.txt # noqa pylint: disable=line-too-long - @dbus.service.method('keymapper.Interface', in_signature='ss') def start_injecting(self, device, preset): """Start injecting the preset for the device. @@ -138,7 +150,6 @@ class Daemon(service.Object): return True - @dbus.service.method('keymapper.Interface', in_signature='b') def stop(self, terminate=False): """Stop all injections and end the service. @@ -147,5 +158,10 @@ class Daemon(service.Object): for injector in self.injectors.values(): injector.stop_injecting() - if terminate: - exit(0) + if terminate and self.loop: + logger.debug('Daemon stops') + self.loop.quit() + + def hello(self, out): + """Used for tests.""" + return out diff --git a/setup.py b/setup.py index 74fe66a8..03baeb7f 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,6 @@ setup( install_requires=[ 'setuptools', 'evdev', - 'dbus-python' + 'pydbus' ] ) diff --git a/tests/test.py b/tests/test.py index 00cb4d98..d74dd995 100644 --- a/tests/test.py +++ b/tests/test.py @@ -37,7 +37,21 @@ from keymapper.logger import update_verbosity assert not os.getcwd().endswith('tests') -sys.path = [os.path.abspath('.')] + sys.path + +def is_service_running(): + """Check if the daemon is running.""" + try: + subprocess.check_output(['pgrep', '-f', 'key-mapper-service']) + except subprocess.CalledProcessError: + return + # let tests control daemon existance + raise Exception('Expected the service not to be running already.') + + +is_service_running() + +# make sure the "tests" module visible +sys.path.append(os.getcwd()) # give tests some time to test stuff while the process # is still running @@ -178,12 +192,6 @@ def patch_evdev(): def list_devices(): return fixtures.keys() - """ - rlist = {device.fd: device for device in self.virtual_devices} - while True: - ready = select.select(rlist, [], [])[0] - """ - class InputDevice: # expose as existing attribute, otherwise the patch for # evdev < 1.0.0 will crash the test @@ -281,19 +289,6 @@ def clear_write_history(): while uinput_write_history_pipe[0].poll(): uinput_write_history_pipe[0].recv() - -def is_service_running(): - """Check if the daemon is running.""" - try: - subprocess.check_output(['pgrep', '-f', 'key-mapper-service']) - except subprocess.CalledProcessError: - return - # let tests control daemon existance - raise Exception('Expected the service not to be running already.') - - -is_service_running() - # quickly fake some stuff before any other file gets a chance to import # the original versions patch_paths() @@ -321,15 +316,17 @@ def main(): 'testcases', pattern='*.py' ) - # add a newline to each "qux (foo.bar)..." output, because the logs - # will be on the same line otherwise - originalStartTest = unittest.TextTestResult.startTest - def startTest(self, test): - originalStartTest(self, test) + # add a newline to each "qux (foo.bar)..." output before each test, + # because the first log will be on the same line otherwise + original_start_test = unittest.TextTestResult.startTest + + def start_test(self, test): + original_start_test(self, test) print() - unittest.TextTestResult.startTest = startTest - testrunner = unittest.TextTestRunner(verbosity=2).run(testsuite) + unittest.TextTestResult.startTest = start_test + + unittest.TextTestRunner(verbosity=2).run(testsuite) if __name__ == "__main__": diff --git a/tests/testcases/daemon.py b/tests/testcases/daemon.py index 9979b039..045f4e75 100644 --- a/tests/testcases/daemon.py +++ b/tests/testcases/daemon.py @@ -24,7 +24,6 @@ import multiprocessing import unittest import time -import dbus import evdev import gi gi.require_version('Gtk', '3.0') @@ -33,9 +32,9 @@ from gi.repository import Gtk from keymapper.state import custom_mapping, system_mapping, \ clear_system_mapping from keymapper.config import config -from keymapper.daemon import Daemon, get_dbus_interface +from keymapper.daemon import Daemon, get_dbus_interface, BUS_NAME -from test import uinput_write_history_pipe, Event, pending_events +from tests.test import uinput_write_history_pipe, Event, pending_events def gtk_iteration(): @@ -49,7 +48,7 @@ class TestDBusDaemon(unittest.TestCase): def setUpClass(cls): cls.process = multiprocessing.Process( target=os.system, - args=('key-mapper-service',) + args=('key-mapper-service -d',) ) cls.process.start() time.sleep(0.5) @@ -57,13 +56,13 @@ class TestDBusDaemon(unittest.TestCase): @classmethod def tearDownClass(cls): - try: - cls.interface.stop(True) - except dbus.exceptions.DBusException: - pass + cls.interface.stop(True) def test_can_connect(self): - self.assertIsInstance(self.interface, dbus.Interface) + # it's a remote dbus object + self.assertEqual(self.interface._bus_name, BUS_NAME) + self.assertFalse(isinstance(self.interface, Daemon)) + self.assertEqual(self.interface.hello('foo'), 'foo') class TestDaemon(unittest.TestCase): diff --git a/tests/testcases/injector.py b/tests/testcases/injector.py index da5b4412..f8497841 100644 --- a/tests/testcases/injector.py +++ b/tests/testcases/injector.py @@ -32,7 +32,7 @@ from keymapper.state import custom_mapping, system_mapping, \ from keymapper.mapping import Mapping from keymapper.config import config -from test import uinput_write_history, Event, pending_events, fixtures, \ +from tests.test import uinput_write_history, Event, pending_events, fixtures, \ clear_write_history, EVENT_READ_TIMEOUT, uinput_write_history_pipe, \ MAX_ABS diff --git a/tests/testcases/integration.py b/tests/testcases/integration.py index 7e5a5f9f..5d5ccb96 100644 --- a/tests/testcases/integration.py +++ b/tests/testcases/integration.py @@ -40,7 +40,7 @@ from keymapper.paths import CONFIG, get_config_path from keymapper.config import config from keymapper.dev.reader import keycode_reader -from test import tmp, pending_events, Event, uinput_write_history_pipe, \ +from tests.test import tmp, pending_events, Event, uinput_write_history_pipe, \ clear_write_history diff --git a/tests/testcases/logger.py b/tests/testcases/logger.py index f0d12f25..40b38682 100644 --- a/tests/testcases/logger.py +++ b/tests/testcases/logger.py @@ -27,7 +27,7 @@ import logging from keymapper.logger import logger, add_filehandler, update_verbosity, \ log_info -from test import tmp +from tests.test import tmp class TestLogger(unittest.TestCase): diff --git a/tests/testcases/presets.py b/tests/testcases/presets.py index 61d3e1eb..52e3606e 100644 --- a/tests/testcases/presets.py +++ b/tests/testcases/presets.py @@ -29,7 +29,7 @@ from keymapper.presets import find_newest_preset, rename_preset, \ from keymapper.paths import CONFIG from keymapper.state import custom_mapping -from test import tmp +from tests.test import tmp def create_preset(device, name='new preset'): diff --git a/tests/testcases/reader.py b/tests/testcases/reader.py index cab85731..758df44a 100644 --- a/tests/testcases/reader.py +++ b/tests/testcases/reader.py @@ -26,7 +26,7 @@ import time from keymapper.dev.reader import keycode_reader -from test import Event, pending_events, EVENT_READ_TIMEOUT +from tests.test import Event, pending_events, EVENT_READ_TIMEOUT CODE_1 = 100