new cli features, more tests

flatpak
sezanzeb 4 years ago
parent 5a7623c26a
commit b316f09cfa

@ -30,15 +30,14 @@ from argparse import ArgumentParser
from keymapper.logger import logger
from keymapper.config import config
from keymapper.daemon import get_dbus_interface
from keymapper.mapping import Mapping
from keymapper.state import XMODMAP_FILENAME, system_mapping
from keymapper.paths import get_preset_path, get_config_path
from keymapper.state import system_mapping
from keymapper.getdevices import get_devices
AUTOLOAD = 'autoload'
START = 'start'
STOP = 'stop'
STOP_ALL = 'stop-all'
HELLO = 'hello'
@ -59,9 +58,24 @@ def group_exists(name):
return False
def main(options, daemon, config_path):
def main(options, daemon):
"""Do the stuff that the executable is supposed to do."""
# Is a function so that I can import it and test it
if options.config_dir is not None:
path = os.path.abspath(os.path.expanduser(os.path.join(
options.config_dir,
'config.json'
)))
if not os.path.exists(path):
logger.error('"%s" does not exist', path)
sys.exit(1)
logger.info('Using config from "%s" instead', path)
config.load_config(path)
config_dir = os.path.dirname(config.path)
presets_dir = os.path.join(config_dir, 'presets')
if options.list_devices:
get_devices()
sys.exit(0)
@ -70,7 +84,7 @@ def main(options, daemon, config_path):
print('\n'.join(system_mapping.list_names()))
sys.exit(0)
if options.command not in [AUTOLOAD, START, STOP, HELLO]:
if options.command not in [AUTOLOAD, START, STOP, HELLO, STOP_ALL]:
logger.error('Unknown command "%s"', options.command)
if options.command == AUTOLOAD:
@ -82,10 +96,8 @@ def main(options, daemon, config_path):
config.save_config()
continue
mapping = Mapping()
preset_path = get_preset_path(device, preset)
mapping.load(preset_path)
daemon.start_injecting(device, preset_path, config_path)
preset_path = os.path.join(presets_dir, device, preset + '.json')
daemon.start_injecting(device, preset_path, config_dir)
if options.command == START:
if options.device is None:
@ -97,7 +109,7 @@ def main(options, daemon, config_path):
sys.exit(1)
preset_path = os.path.abspath(os.path.expanduser(options.preset))
daemon.start_injecting(options.device, preset_path, config_path)
daemon.start_injecting(options.device, preset_path, config_dir)
if options.command == STOP:
if options.device is None:
@ -106,6 +118,9 @@ def main(options, daemon, config_path):
daemon.stop_injecting(options.device)
if options.command == STOP_ALL:
daemon.stop()
if options.command == HELLO:
response = daemon.hello('hello')
logger.info('Daemon answered with "%s"', response)
@ -116,11 +131,19 @@ if __name__ == '__main__':
parser.add_argument(
'--command', action='store', dest='command',
help=(
'start, stop, autoload (also stops all current injections) '
'or hello'
'start, stop, autoload (also stops all current injections), '
'hello or stop-all'
),
default=None, metavar='NAME'
)
parser.add_argument(
'--config-dir', action='store', dest='config_dir',
help=(
'path to the config directory containing config.json. '
'defaults to ~/.config/key-mapper/'
),
default=None, metavar='PATH',
)
parser.add_argument(
'--preset', action='store', dest='preset',
help='path to the preset .json file',
@ -149,6 +172,4 @@ if __name__ == '__main__':
if daemon is None:
sys.exit(0)
config_path = get_config_path()
main(options, daemon, config_path)
main(options, daemon)

@ -14,7 +14,7 @@
</object>
<object class="GtkDialog" id="error_dialog">
<property name="can-focus">False</property>
<property name="border-width">10</property>
<property name="border-width">4</property>
<property name="title" translatable="yes">Key Mapper</property>
<property name="modal">True</property>
<property name="window-position">center</property>
@ -138,7 +138,7 @@
</object>
<object class="GtkDialog" id="unsaved_changes">
<property name="can-focus">False</property>
<property name="border-width">10</property>
<property name="border-width">4</property>
<property name="title" translatable="yes">Key Mapper</property>
<property name="modal">True</property>
<property name="window-position">center</property>

@ -252,6 +252,10 @@ class GlobalConfig(ConfigBase):
def save_config(self):
"""Save the config to the file system."""
if USER == 'root':
logger.debug('Skipping config file creation for the root user')
return
touch(self.path)
with open(self.path, 'w') as file:

@ -168,6 +168,8 @@ class Daemon:
# reload the config, since it may have been changed
if config_dir is not None:
config_path = os.path.join(config_dir, 'config.json')
if not os.path.exists(config_path):
logger.error('"%s" does not exist', config_path)
config.load_config(config_path)
if device not in get_devices():
@ -212,10 +214,7 @@ class Daemon:
return True
def stop(self):
"""Stop all injections and end the service.
Raises dbus.exceptions.DBusException in your main process.
"""
"""Stop all injections."""
logger.info('Stopping all injections')
for injector in self.injectors.values():
injector.stop_injecting()

@ -471,7 +471,7 @@ class Injector:
def _macro_write(self, code, value, uinput):
"""Handler for macros."""
logger.spam('macro writes code:%s value:%d', code, value)
logger.spam('macro writes %s', (EV_KEY, code, value))
uinput.write(EV_KEY, code, value)
uinput.syn()

@ -1,47 +0,0 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# key-mapper - GUI for device specific keyboard mappings
# Copyright (C) 2021 sezanzeb <proxima@hip70890b.de>
#
# This file is part of key-mapper.
#
# key-mapper is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# key-mapper is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with key-mapper. If not, see <https://www.gnu.org/licenses/>.
"""Error dialog."""
from gi.repository import Gtk
from keymapper.data import get_data_path
CONTINUE = True
GO_BACK = False
def unsaved_changes_dialog():
"""Blocks until the user decided about an action."""
gladefile = get_data_path('key-mapper.glade')
builder = Gtk.Builder()
builder.add_from_file(gladefile)
dialog = builder.get_object('unsaved_changes')
dialog.show()
response = dialog.run()
dialog.hide()
if response == Gtk.ResponseType.ACCEPT:
return CONTINUE
return GO_BACK

@ -34,7 +34,6 @@ from keymapper.presets import get_presets, find_newest_preset, \
from keymapper.logger import logger
from keymapper.getdevices import get_devices
from keymapper.gtk.row import Row, to_string
from keymapper.gtk.unsaved import unsaved_changes_dialog, GO_BACK
from keymapper.dev.reader import keycode_reader
from keymapper.dev.injector import RUNNING, FAILED, NO_GRAB
from keymapper.daemon import get_dbus_interface
@ -54,6 +53,9 @@ CTX_APPLY = 1
CTX_ERROR = 3
CTX_WARNING = 4
CONTINUE = True
GO_BACK = False
def get_selected_row_bg():
"""Get the background color that a row is going to have when selected."""
@ -143,6 +145,8 @@ class Window:
builder.connect_signals(self)
self.builder = builder
self.unsaved_changes = builder.get_object('unsaved_changes')
window = self.get('window')
window.show()
# hide everything until stuff is populated
@ -184,6 +188,17 @@ class Window:
self.ctrl = 0
self.unreleased_warn = 0
def unsaved_changes_dialog(self):
"""Blocks until the user decided about an action."""
self.unsaved_changes.show()
response = self.unsaved_changes.run()
self.unsaved_changes.hide()
if response == Gtk.ResponseType.ACCEPT:
return CONTINUE
return GO_BACK
def key_press(self, _, event):
"""To execute shortcuts.
@ -522,7 +537,7 @@ class Window:
if dropdown.get_active_id() == self.selected_device:
return
if custom_mapping.changed and unsaved_changes_dialog() == GO_BACK:
if custom_mapping.changed and self.unsaved_changes_dialog() == GO_BACK:
dropdown.set_active_id(self.selected_device)
return
@ -594,7 +609,7 @@ class Window:
@with_selected_device
def on_create_preset_clicked(self, _):
"""Create a new preset and select it."""
if custom_mapping.changed and unsaved_changes_dialog() == GO_BACK:
if custom_mapping.changed and self.unsaved_changes_dialog() == GO_BACK:
return
try:
@ -614,7 +629,7 @@ class Window:
if dropdown.get_active_id() == self.selected_preset:
return
if custom_mapping.changed and unsaved_changes_dialog() == GO_BACK:
if custom_mapping.changed and self.unsaved_changes_dialog() == GO_BACK:
dropdown.set_active_id(self.selected_preset)
return

@ -68,7 +68,7 @@ logging.Logger.key_spam = key_spam
start = time.time()
LOG_PATH = '~/.log/key-mapper'
LOG_PATH = os.path.expanduser('~/.log/key-mapper')
class Formatter(logging.Formatter):

@ -86,7 +86,7 @@ class SystemMapping:
path = get_config_path(XMODMAP_FILENAME)
touch(path)
with open(path, 'w') as file:
logger.info('Writing "%s"', path)
logger.debug('Writing "%s"', path)
json.dump(xmodmap_dict, file, indent=4)
self._mapping.update(xmodmap_dict)

@ -170,6 +170,7 @@ running (or without sudo if your user has the appropriate permissions).
```bash
key-mapper-control --command autoload
key-mapper-control --command autoload --config "/home/user/.config/key-mapper/"
sudo key-mapper-control --list-devices
key-mapper-control --command stop --device "Razer Razer Naga Trinity"
key-mapper-control --command start --device "Razer Razer Naga Trinity" --preset "~/.config/key-mapper/presets/gamepad/a.json"

@ -372,12 +372,6 @@ def patch_evdev():
evdev.InputEvent = InputEvent
def patch_unsaved():
# don't block tests
from keymapper.gtk import unsaved
unsaved.unsaved_changes_dialog = lambda: unsaved.CONTINUE
def patch_events():
# improve logging of stuff
evdev.InputEvent.__str__ = lambda self: (
@ -397,7 +391,6 @@ def clear_write_history():
# the original versions
patch_paths()
patch_evdev()
patch_unsaved()
patch_select()
patch_events()
@ -407,6 +400,7 @@ from keymapper.config import config
from keymapper.dev.reader import keycode_reader
from keymapper.getdevices import refresh_devices
from keymapper.state import system_mapping, custom_mapping
from keymapper.paths import get_config_path
from keymapper.dev.keycode_mapper import active_macros, unreleased
# no need for a high number in tests
@ -438,12 +432,14 @@ def cleanup():
if os.path.exists(tmp):
shutil.rmtree(tmp)
config.path = os.path.join(get_config_path(), 'config.json')
config.clear_config()
config.save_config()
system_mapping.populate()
custom_mapping.empty()
custom_mapping.clear_config()
custom_mapping.changed = False
clear_write_history()

@ -30,11 +30,12 @@ from importlib.machinery import SourceFileLoader
from keymapper.state import custom_mapping
from keymapper.config import config
from keymapper.paths import get_config_path
from keymapper.daemon import Daemon
from keymapper.mapping import Mapping
from keymapper.paths import get_preset_path
from tests.test import cleanup
from tests.test import cleanup, tmp
def import_control():
@ -56,7 +57,7 @@ control = import_control()
options = collections.namedtuple(
'options',
['command', 'preset', 'device', 'list_devices', 'key_names']
['command', 'config_dir', 'preset', 'device', 'list_devices', 'key_names']
)
@ -71,7 +72,36 @@ class TestControl(unittest.TestCase):
get_preset_path(devices[0], presets[0]),
get_preset_path(devices[1], presets[1])
]
config_dir = '/foo/bar'
config_dir = get_config_path()
Mapping().save(paths[0])
Mapping().save(paths[1])
daemon = Daemon()
start_history = []
stop_history = []
daemon.start_injecting = lambda *args: start_history.append(args)
daemon.stop = lambda *args: stop_history.append(args)
config.set_autoload_preset(devices[0], presets[0])
config.set_autoload_preset(devices[1], presets[1])
control(options('autoload', None, None, None, False, False), daemon)
self.assertEqual(len(start_history), 2)
self.assertEqual(len(stop_history), 1)
self.assertEqual(start_history[0], (devices[0], os.path.expanduser(paths[0]), config_dir))
self.assertEqual(start_history[1], (devices[1], os.path.abspath(paths[1]), config_dir))
def test_autoload_other_path(self):
devices = ['device 1234', 'device 2345']
presets = ['preset', 'bar']
config_dir = os.path.join(tmp, 'foo', 'bar')
paths = [
os.path.join(config_dir, 'presets', devices[0], presets[0] + '.json'),
os.path.join(config_dir, 'presets', devices[1], presets[1] + '.json')
]
Mapping().save(paths[0])
Mapping().save(paths[1])
@ -83,10 +113,13 @@ class TestControl(unittest.TestCase):
daemon.start_injecting = lambda *args: start_history.append(args)
daemon.stop = lambda *args: stop_history.append(args)
config.path = os.path.join(config_dir, 'config.json')
config.load_config()
config.set_autoload_preset(devices[0], presets[0])
config.set_autoload_preset(devices[1], presets[1])
config.save_config()
control(options('autoload', None, None, False, False), daemon, config_dir)
control(options('autoload', config_dir, None, None, False, False), daemon)
self.assertEqual(len(start_history), 2)
self.assertEqual(len(stop_history), 1)
@ -96,23 +129,47 @@ class TestControl(unittest.TestCase):
def test_start_stop(self):
device = 'device 1234'
path = '~/a/preset.json'
config_dir = '/foo/bar'
config_dir = get_config_path()
daemon = Daemon()
start_history = []
stop_history = []
stop_all_history = []
daemon.start_injecting = lambda *args: start_history.append(args)
daemon.stop_injecting = lambda *args: stop_history.append(args)
daemon.stop = lambda *args: stop_all_history.append(args)
control(options('start', path, device, False, False), daemon, config_dir)
control(options('stop', None, device, False, False), daemon, None)
control(options('start', None, path, device, False, False), daemon)
self.assertEqual(len(start_history), 1)
self.assertEqual(len(stop_history), 1)
self.assertEqual(start_history[0], (device, os.path.expanduser(path), config_dir))
control(options('stop', None, None, device, False, False), daemon)
self.assertEqual(len(stop_history), 1)
self.assertEqual(stop_history[0], (device,))
control(options('stop-all', None, None, None, False, False), daemon)
self.assertEqual(len(stop_all_history), 1)
self.assertEqual(stop_all_history[0], ())
def test_config_not_found(self):
device = 'device 1234'
path = '~/a/preset.json'
config_dir = '/foo/bar'
daemon = Daemon()
start_history = []
stop_history = []
daemon.start_injecting = lambda *args: start_history.append(args)
daemon.stop_injecting = lambda *args: stop_history.append(args)
options_1 = options('start', config_dir, path, device, False, False)
self.assertRaises(SystemExit, lambda: control(options_1, daemon))
options_2 = options('stop', config_dir, None, device, False, False)
self.assertRaises(SystemExit, lambda: control(options_2, daemon))
if __name__ == "__main__":
unittest.main()

@ -67,8 +67,6 @@ Gtk.main_quit = lambda: None
def launch(argv=None):
"""Start key-mapper-gtk with the command line argument array argv."""
custom_mapping.empty()
bin_path = os.path.join(os.getcwd(), 'bin', 'key-mapper-gtk')
if not argv:
@ -82,6 +80,8 @@ def launch(argv=None):
gtk_iteration()
module.window.unsaved_changes.run = lambda: Gtk.ResponseType.ACCEPT
return module.window

Loading…
Cancel
Save