This commit is contained in:
sezanzeb 2020-11-25 21:55:04 +01:00
parent ed5726f761
commit ba929fce23
11 changed files with 75 additions and 39 deletions

View File

@ -8,25 +8,26 @@ Tool to change the mapping of your input device buttons.
# Running # Running
First, install the package: Installation:
```bash ```bash
git clone https://github.com/sezanzeb/key-mapper.git git clone https://github.com/sezanzeb/key-mapper.git
cd key-mapper cd key-mapper
sudo python3 setup.py install sudo python3 setup.py install
usermod -a -G input $USER
``` ```
To keep injecting the mapping after closing the window, the daemon needs to To keep injecting the mapping after closing the window, the daemon needs to
be running: be running. If it doesn't already after logging in, you can use:
```bash ```bash
sudo key-mapper-service -d sudo key-mapper-service
``` ```
To open the UI to modify the mappings, use: To open the UI to modify the mappings, use:
```bash ```bash
sudo key-mapper-gtk -d sudo key-mapper-gtk
``` ```
You can also start it via your applications menu. You can also start it via your applications menu.

View File

@ -33,7 +33,8 @@ from gi.repository import GLib
import dbus.mainloop.glib import dbus.mainloop.glib
from dbus.mainloop.glib import DBusGMainLoop from dbus.mainloop.glib import DBusGMainLoop
from keymapper.logger import logger, update_verbosity, log_info from keymapper.logger import logger, update_verbosity, log_info, \
add_filehandler
from keymapper.daemon import Daemon from keymapper.daemon import Daemon
@ -47,13 +48,14 @@ if __name__ == '__main__':
options = parser.parse_args(sys.argv[1:]) options = parser.parse_args(sys.argv[1:])
update_verbosity(options.debug) update_verbosity(options.debug)
add_filehandler()
log_info() log_info()
if getpass.getuser() != 'root' and 'unittest' not in sys.modules.keys(): if getpass.getuser() != 'root' and 'unittest' not in sys.modules.keys():
logger.warning('Without sudo, your devices may not be visible') logger.warning('Without sudo, your devices may not be visible')
session_bus = dbus.SessionBus(mainloop=DBusGMainLoop()) session_bus = dbus.SessionBus(mainloop=DBusGMainLoop())
name = dbus.service.BusName('com.keymapper.Control', session_bus) name = dbus.service.BusName('keymapper.Control', session_bus)
daemon = Daemon(session_bus, '/') daemon = Daemon(session_bus, '/')
atexit.register(daemon.stop) atexit.register(daemon.stop)

View File

@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=Key Mapper Service
Icon=mouse
Exec=key-mapper-service
Terminal=false
Categories=Settings;
Comment=The Key Mapper service to keep mappings alive after closing the UI

View File

@ -601,6 +601,7 @@
</child> </child>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="width_request">250</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
@ -608,16 +609,15 @@
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="homogeneous">True</property>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="width_request">50</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Click on a cell below and hit a key on your device. If you have your preset active, the reported keycodes may be wrong.</property> <property name="tooltip_text" translatable="yes">Click on a cell below and hit a key on your device. If you have your preset active, the reported keycodes may be wrong.</property>
<property name="margin_top">5</property> <property name="margin_top">5</property>
<property name="margin_bottom">5</property> <property name="margin_bottom">5</property>
<property name="label" translatable="yes">Key</property> <property name="label" translatable="yes">Key</property>
<property name="width_chars">10</property>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -637,16 +637,16 @@
<property name="margin_top">5</property> <property name="margin_top">5</property>
<property name="margin_bottom">5</property> <property name="margin_bottom">5</property>
<property name="label" translatable="yes">Mapping</property> <property name="label" translatable="yes">Mapping</property>
<property name="width_chars">10</property>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">True</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="width_request">50</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
</object> </object>
@ -656,6 +656,9 @@
<property name="position">2</property> <property name="position">2</property>
</packing> </packing>
</child> </child>
<style>
<class name="table-header"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -700,7 +703,7 @@
</child> </child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">True</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">2</property> <property name="position">2</property>
</packing> </packing>

View File

@ -5,7 +5,7 @@
> >
<policyconfig> <policyconfig>
<icon_name>mouse</icon_name> <icon_name>mouse</icon_name>
<action id="org.key-mapper"> <action id="keymapper">
<description>Run Key Mapper as root</description> <description>Run Key Mapper as root</description>
<message>Authentication is required to discover devices.</message> <message>Authentication is required to discover devices.</message>
<defaults> <defaults>

12
data/key-mapper.service Normal file
View File

@ -0,0 +1,12 @@
# https://wiki.archlinux.org/index.php/Systemd
[Unit]
Description=Service to inject keycodes without the GUI application
[Service]
Type=dbus
BusName=keymapper.Control
ExecStart=/usr/bin/key-mapper-service -d
[Install]
WantedBy=default.target

View File

@ -1,3 +1,11 @@
row {
padding: 0;
}
.table-header, .row-box {
padding: 2px;
}
.changed { .changed {
background: @selected_bg_color; background: @selected_bg_color;
} }

View File

@ -28,10 +28,8 @@ from dbus import service
import dbus.mainloop.glib import dbus.mainloop.glib
from keymapper.logger import logger from keymapper.logger import logger
from keymapper.config import config
from keymapper.injector import KeycodeInjector from keymapper.injector import KeycodeInjector
from keymapper.mapping import Mapping from keymapper.mapping import Mapping
from keymapper.paths import get_config_path
# TODO service file in data for a root daemon # TODO service file in data for a root daemon
@ -59,13 +57,13 @@ def get_dbus_interface():
'The daemon "key-mapper-service" is not running, mapping keys ' 'The daemon "key-mapper-service" is not running, mapping keys '
'only works as long as the window is open.' 'only works as long as the window is open.'
) )
return Daemon(autoload=False) return Daemon()
try: try:
logger.debug('Found the daemon process') logger.debug('Found the daemon process')
bus = dbus.SessionBus() bus = dbus.SessionBus()
remote_object = bus.get_object('com.keymapper.Control', '/') remote_object = bus.get_object('keymapper.Control', '/')
interface = dbus.Interface(remote_object, 'com.keymapper.Interface') interface = dbus.Interface(remote_object, 'keymapper.Interface')
logger.debug('Connected to dbus') logger.debug('Connected to dbus')
except Exception as error: except Exception as error:
logger.error( logger.error(
@ -74,7 +72,7 @@ def get_dbus_interface():
'key-mapper processes not running as root?' 'key-mapper processes not running as root?'
) )
logger.error(error) logger.error(error)
return Daemon(autoload=False) return Daemon()
return interface return interface
@ -83,20 +81,19 @@ class Daemon(service.Object):
"""Starts injecting keycodes based on the configuration. """Starts injecting keycodes based on the configuration.
Can be talked to either over dbus or by instantiating it. Can be talked to either over dbus or by instantiating it.
The Daemon may not have any knowledge about the logged in user, so it
can't read any config files. It has to be told what to do and will
continue to do so afterwards, but it can't decide to start injecting
on its own.
""" """
def __init__(self, *args, autoload=True, **kwargs): def __init__(self, *args, **kwargs):
"""Constructs the daemon. You still need to run the GLib mainloop.""" """Constructs the daemon. You still need to run the GLib mainloop."""
self.injectors = {} self.injectors = {}
if autoload:
for device, preset in config.iterate_autoload_presets():
mapping = Mapping()
mapping.load(get_config_path(device, preset))
self.injectors[device] = KeycodeInjector(device, mapping)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@dbus.service.method( @dbus.service.method(
'com.keymapper.Interface', 'keymapper.Interface',
in_signature='s' in_signature='s'
) )
def stop_injecting(self, device): def stop_injecting(self, device):
@ -113,7 +110,7 @@ class Daemon(service.Object):
# TODO if ss is the correct signature for multiple parameters, add an # 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 # example to https://gitlab.freedesktop.org/dbus/dbus-python/-/blob/master/doc/tutorial.txt # noqa pylint: disable=line-too-long
@dbus.service.method( @dbus.service.method(
'com.keymapper.Interface', 'keymapper.Interface',
in_signature='ss' in_signature='ss'
) )
def start_injecting(self, device, path): def start_injecting(self, device, path):
@ -143,9 +140,9 @@ class Daemon(service.Object):
return True return True
@dbus.service.method( @dbus.service.method(
'com.keymapper.Interface' 'keymapper.Interface'
) )
def stop(self): def stop(self):
"""Properly stop the daemon.""" """Stop all mapping injections."""
for injector in self.injectors.values(): for injector in self.injectors.values():
injector.stop_injecting() injector.stop_injecting()

View File

@ -132,10 +132,10 @@ class Row(Gtk.ListBoxRow):
'button-press-event', 'button-press-event',
self.on_delete_button_clicked self.on_delete_button_clicked
) )
delete_button.set_margin_start(5) delete_button.set_size_request(50, -1)
delete_button.set_margin_end(5)
keycode_input = Gtk.ToggleButton() keycode_input = Gtk.ToggleButton()
keycode_input.set_size_request(50, -1)
if keycode is not None: if keycode is not None:
keycode_input.set_label(str(keycode)) keycode_input.set_label(str(keycode))
@ -159,12 +159,13 @@ class Row(Gtk.ListBoxRow):
) )
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
box.set_homogeneous(True) box.set_homogeneous(False)
box.set_spacing(2) box.set_spacing(0)
box.pack_start(keycode_input, expand=True, fill=True, padding=0) box.pack_start(keycode_input, expand=False, fill=True, padding=0)
box.pack_start(character_input, expand=True, fill=True, padding=0) box.pack_start(character_input, expand=True, fill=True, padding=0)
box.pack_start(delete_button, expand=True, fill=False, padding=0) box.pack_start(delete_button, expand=False, fill=True, padding=0)
box.show_all() box.show_all()
box.get_style_context().add_class('row-box')
self.add(box) self.add(box)
self.show_all() self.show_all()

View File

@ -105,7 +105,6 @@ def update_verbosity(debug):
def add_filehandler(): def add_filehandler():
"""Clear the existing logfile and start logging to it.""" """Clear the existing logfile and start logging to it."""
# jack also logs to ~/.log
log_path = os.path.expanduser('~/.log/key-mapper') log_path = os.path.expanduser('~/.log/key-mapper')
log_file = os.path.join(log_path, 'log') log_file = os.path.join(log_path, 'log')
@ -118,4 +117,7 @@ def add_filehandler():
file_handler = logging.FileHandler(log_file) file_handler = logging.FileHandler(log_file)
file_handler.setFormatter(Formatter()) file_handler.setFormatter(Formatter())
logger.info('Logging to "%s"', log_file)
logger.addHandler(file_handler) logger.addHandler(file_handler)

View File

@ -34,7 +34,7 @@ class Install(DistUtilsExtra.auto.install_auto):
executable = os.path.join(self.install_data, 'bin/key-mapper-gtk') executable = os.path.join(self.install_data, 'bin/key-mapper-gtk')
assert os.path.exists(executable) assert os.path.exists(executable)
policy_path = '/usr/share/polkit-1/actions/org.key-mapper.policy' policy_path = '/usr/share/polkit-1/actions/key-mapper.policy'
with open(policy_path, 'r') as file: with open(policy_path, 'r') as file:
contents = file.read() contents = file.read()
@ -45,7 +45,7 @@ class Install(DistUtilsExtra.auto.install_auto):
with open(policy_path, 'w') as file: with open(policy_path, 'w') as file:
print( print(
f'Inserting the correct path "{executable}" into ' f'Inserting the correct path "{executable}" into '
'org.key-mapper.policy' 'keymapper.policy'
) )
file.write(contents.format( file.write(contents.format(
executable=executable executable=executable
@ -59,7 +59,9 @@ DistUtilsExtra.auto.setup(
license='GPL-3.0', license='GPL-3.0',
data_files=[ data_files=[
('share/applications/', ['data/key-mapper.desktop']), ('share/applications/', ['data/key-mapper.desktop']),
('/usr/share/polkit-1/actions/', ['data/org.key-mapper.policy']), ('/usr/share/polkit-1/actions/', ['data/key-mapper.policy']),
('/usr/lib/systemd/system', ['data/key-mapper.service']),
('/etc/xdg/autostart/', ['data/key-mapper-autoload']),
], ],
cmdclass={ cmdclass={
'install': Install 'install': Install