talks to the daemon

pull/14/head
sezanzeb 4 years ago
parent 4019388264
commit 48ca89cc39

@ -47,3 +47,4 @@ sudo python3 setup.py install && python3 tests/test.py
- [x] ask for administrator permissions using polkit
- [ ] make it work on wayland
- [ ] add to the AUR, provide .deb and .appimage files
- [ ] support timed macros, maybe using some sort of syntax

@ -32,9 +32,10 @@ gi.require_version('Gtk', '3.0')
gi.require_version('GLib', '2.0')
from gi.repository import Gtk
from keymapper.logger import logger, update_verbosity, log_info
from keymapper.logger import update_verbosity, log_info
from keymapper.gtk.error import ErrorDialog
from keymapper.gtk.window import Window
from keymapper.daemon import Daemon
if __name__ == '__main__':
@ -49,19 +50,21 @@ if __name__ == '__main__':
update_verbosity(options.debug)
log_info()
if getpass.getuser() != 'root' and 'unittest' not in sys.modules.keys():
ErrorDialog(
'Error',
'Sudo is required to talk to the daemon and discover devices'
)
raise SystemExit(1)
window = Window()
def stop_injecting():
if window.keycode_injector is not None:
window.keycode_injector.stop_injecting()
if isinstance(window.dbus, Daemon):
# it created its own temporary daemon inside the process
# because none was running
window.dbus.stop()
atexit.register(stop_injecting)
if getpass.getuser() != 'root' and 'unittest' not in sys.modules.keys():
logger.warning('Without sudo, your devices may not be visible')
ErrorDialog(
'Warning',
'Without sudo, your devices may not be visible'
)
Gtk.main()

@ -25,13 +25,13 @@
import sys
import atexit
import getpass
import dbus.mainloop.glib
from dbus.mainloop.glib import DBusGMainLoop
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 keymapper.logger import logger, update_verbosity, log_info
from keymapper.daemon import Daemon
@ -53,7 +53,7 @@ if __name__ == '__main__':
logger.warning('Without sudo, your devices may not be visible')
session_bus = dbus.SessionBus(mainloop=DBusGMainLoop())
name = dbus.service.BusName('com.keymapper.control', session_bus)
name = dbus.service.BusName('com.keymapper.Control', session_bus)
daemon = Daemon(session_bus, '/')
atexit.register(daemon.stop)

@ -345,6 +345,7 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Presets need to be saved before they can be applied</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_apply_preset_clicked" swapped="no"/>
</object>

@ -22,6 +22,8 @@
"""Starts injecting keycodes based on the configuration."""
import subprocess
from dbus import service
import dbus.mainloop.glib
@ -31,25 +33,59 @@ from keymapper.injector import KeycodeInjector
from keymapper.mapping import Mapping
def is_service_running():
"""Check if the daemon is running."""
try:
subprocess.check_output(['pgrep', '-f', 'key-mapper-service'])
except subprocess.CalledProcessError:
return False
return True
def get_dbus_interface():
"""Get an interface to start and stop injecting keystrokes."""
if not is_service_running():
logger.error(
'The daemon is not running, mapping keys only works as '
'long as the window is open.'
)
return Daemon(autoload=False)
else:
logger.debug('Found the daemon process')
bus = dbus.SessionBus()
remote_object = bus.get_object('com.keymapper.Control', '/')
interface = dbus.Interface(remote_object, 'com.keymapper.Interface')
logger.debug('Connected to dbus')
return interface
class Daemon(service.Object):
def __init__(self, *args, **kwargs):
"""Starts injecting keycodes based on the configuration.
Can be talked to either over dbus or by instantiating it.
"""
def __init__(self, *args, autoload=True, **kwargs):
"""Constructs the daemon. You still need to run the GLib mainloop."""
self.injectors = {}
for device, preset in config.iterate_autoload_presets():
mapping = Mapping()
mapping.load(device, preset)
self.injectors[device] = KeycodeInjector(device, mapping)
if autoload:
for device, preset in config.iterate_autoload_presets():
mapping = Mapping()
mapping.load(device, preset)
self.injectors[device] = KeycodeInjector(device, mapping)
super().__init__(*args, **kwargs)
@dbus.service.method(
'com.keymapper.control',
'com.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:
logger.error('No injector running for device %s', device)
logger.error(
'Tried to stop injector, but none is running for device "%s"',
device
)
return
self.injectors[device].stop_injecting()
@ -57,20 +93,28 @@ class Daemon(service.Object):
# 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
@dbus.service.method(
'com.keymapper.control',
'com.keymapper.Interface',
in_signature='ss'
)
def start_injecting(self, device, preset):
"""Start injecting the preset for the device."""
"""Start injecting the preset for the device.
Returns True on success.
"""
if self.injectors.get(device) is not None:
self.injectors[device].stop_injecting()
mapping = Mapping()
mapping.load(device, preset)
self.injectors[device] = KeycodeInjector(device, mapping)
try:
self.injectors[device] = KeycodeInjector(device, mapping)
except OSError:
return False
return True
@dbus.service.method(
'com.keymapper.control'
'com.keymapper.Interface'
)
def stop(self):
"""Properly stop the daemon."""

@ -28,6 +28,7 @@ gi.require_version('GLib', '2.0')
from gi.repository import Gtk
from keymapper.data import get_data_path
from keymapper.logger import logger
class ErrorDialog:
@ -39,6 +40,7 @@ class ErrorDialog:
primary : string
secondary : string
"""
logger.error(secondary)
gladefile = get_data_path('key-mapper.glade')
builder = Gtk.Builder()
builder.add_from_file(gladefile)

@ -22,6 +22,7 @@
"""User Interface."""
import dbus
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GLib', '2.0')
@ -32,11 +33,11 @@ from keymapper.state import custom_mapping
from keymapper.presets import get_presets, find_newest_preset, \
delete_preset, rename_preset, get_available_preset_name
from keymapper.logger import logger
from keymapper.injector import KeycodeInjector
from keymapper.getdevices import get_devices
from keymapper.gtk.row import Row
from keymapper.gtk.unsaved import unsaved_changes_dialog, GO_BACK
from keymapper.reader import keycode_reader
from keymapper.daemon import get_dbus_interface
def gtk_iteration():
@ -71,9 +72,10 @@ def get_selected_row_bg():
class Window:
"""User Interface."""
def __init__(self):
self.dbus = get_dbus_interface()
self.selected_device = None
self.selected_preset = None
self.keycode_injector = None
css_provider = Gtk.CssProvider()
with open(get_data_path('style.css'), 'r') as f:
@ -166,7 +168,11 @@ class Window:
custom_mapping.save(self.selected_device, new_preset)
presets = [new_preset]
else:
logger.debug('Presets for "%s": %s', device, ', '.join(presets))
logger.debug(
'Presets for "%s": "%s"',
device,
'", "'.join(presets)
)
preset_selection = self.get('preset_selection')
preset_selection.handler_block_by_func(self.on_select_preset)
@ -217,8 +223,7 @@ class Window:
def on_apply_system_layout_clicked(self, button):
"""Load the mapping."""
if self.keycode_injector is not None:
self.keycode_injector.stop_injecting()
self.dbus.stop_injecting(self.selected_device)
self.get('status_bar').push(
CTX_APPLY,
f'Applied the system default'
@ -269,16 +274,15 @@ class Window:
CTX_APPLY,
f'Applied "{self.selected_preset}"'
)
# TODO write dbus.py and call apply_preset on that one instead
# which sends a message to key-mapper-service
if self.keycode_injector is not None:
self.keycode_injector.stop_injecting()
try:
self.keycode_injector = KeycodeInjector(self.selected_device)
except OSError:
success = self.dbus.start_injecting(
self.selected_device,
self.selected_preset
)
if not success:
self.get('status_bar').push(
CTX_ERROR,
f'Could not grab device "{self.selected_device}"'
'Error: Could not grab devices!'
)
# restart reading because after injecting the device landscape

@ -104,10 +104,10 @@ class Mapping:
def load(self, device, preset):
"""Load a dumped JSON from home to overwrite the mappings."""
path = get_config_path(device, preset)
logger.info('Loading preset from %s', path)
logger.info('Loading preset from "%s"', path)
if not os.path.exists(path):
logger.error('Tried to load non-existing preset %s', path)
logger.error('Tried to load non-existing preset "%s"', path)
return
with open(path, 'r') as f:

@ -22,13 +22,14 @@
"""Keeps reading keycodes in the background for the UI to use."""
import os
import evdev
from keymapper.logger import logger
from keymapper.getdevices import get_devices, refresh_devices
# offset between xkb and linux keycodes
# offset between xkb and linux keycodes. linux keycodes are lower
KEYCODE_OFFSET = 8
@ -68,10 +69,11 @@ class _KeycodeReader:
# Watch over each one of the potentially multiple devices per
# hardware
self.virtual_devices += [
evdev.InputDevice(path)
for path in group['paths']
]
for path in group['paths']:
try:
self.virtual_devices.append(evdev.InputDevice(path))
except FileNotFoundError:
continue
logger.debug(
'Starting reading keycodes from "%s"',
@ -83,7 +85,17 @@ class _KeycodeReader:
newest_keycode = None
for virtual_device in self.virtual_devices:
while True:
event = virtual_device.read_one()
try:
event = virtual_device.read_one()
except OSError:
# can happen if a device disappears
logger.debug(
'%s cannot be read anymore',
virtual_device.name
)
self.virtual_devices.remove(virtual_device)
break
if event is None:
break

@ -192,11 +192,22 @@ def patch_unsaved():
unsaved.unsaved_changes_dialog = lambda: unsaved.CONTINUE
def patch_dbus():
"""Make sure that the dbus interface is just an instance of Daemon.
Don't talk to an actual daemon if one is running.
"""
import dbus
from keymapper.daemon import Daemon
dbus.Interface = lambda *args: Daemon()
# quickly fake some stuff before any other file gets a chance to import
# the original versions
patch_paths()
patch_evdev()
patch_unsaved()
patch_dbus()
if __name__ == "__main__":

Loading…
Cancel
Save