From 5c41d2ae2e7f61eaa154b03cd5c5b3bb9ef9c313 Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Sat, 31 Oct 2020 14:43:09 +0100 Subject: [PATCH] config, more renaming, some glade prototype --- .gitignore | 2 + bin/{setxkbmap-gtk => key-mapper-gtk} | 12 +- ...txkbmap-gtk.desktop => key-mapper.desktop} | 4 +- data/key-mapper.glade | 173 ++++++++++++++++ data/setxkbmap-gtk.glade | 0 keymapper/config.py | 196 ++++++++++++++++++ keymapper/logger.py | 8 +- keymapper/util.py | 4 +- setup.py | 8 +- tests/test.py | 2 +- tests/testcases/{skeleton.py => config.py} | 20 +- 11 files changed, 409 insertions(+), 20 deletions(-) rename bin/{setxkbmap-gtk => key-mapper-gtk} (83%) rename data/{setxkbmap-gtk.desktop => key-mapper.desktop} (53%) create mode 100644 data/key-mapper.glade delete mode 100644 data/setxkbmap-gtk.glade create mode 100644 keymapper/config.py rename tests/testcases/{skeleton.py => config.py} (54%) diff --git a/.gitignore b/.gitignore index b6e47617..8618ebfd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +*.glade~ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/bin/setxkbmap-gtk b/bin/key-mapper-gtk similarity index 83% rename from bin/setxkbmap-gtk rename to bin/key-mapper-gtk index 01d259c7..c836423f 100755 --- a/bin/setxkbmap-gtk +++ b/bin/key-mapper-gtk @@ -1,6 +1,6 @@ #!/usr/bin/python3 # -*- coding: utf-8 -*- -# key-mapper - GTK based GUI for device specific keyboard mappings +# key-mapper - GUI for device specific keyboard mappings # Copyright (C) 2020 sezanzeb # # This file is part of key-mapper. @@ -31,8 +31,8 @@ gi.require_version('Gtk', '3.0') gi.require_version('GLib', '2.0') from gi.repository import Gtk, GLib -from setxkbmapgtk.util import find_devices -from setxkbmapgtk.logger import logger, update_verbosity, log_info +from keymapper.util import find_devices +from keymapper.logger import logger, update_verbosity, log_info class Window: @@ -44,10 +44,14 @@ class Window: builder.connect_signals(self) self.builder = builder - window = builder.get_object('setxkbmap-gtk_window') + window = builder.get_object('key-mapper-gtk_window') window.show() self.window = window + def get(self, name): + """Get a widget from the window""" + return self.builder.get_object(name) + if __name__ == '__main__': parser = ArgumentParser() diff --git a/data/setxkbmap-gtk.desktop b/data/key-mapper.desktop similarity index 53% rename from data/setxkbmap-gtk.desktop rename to data/key-mapper.desktop index 78b9781a..ad1e34c9 100644 --- a/data/setxkbmap-gtk.desktop +++ b/data/key-mapper.desktop @@ -2,7 +2,7 @@ Type=Application Name=key-mapper Icon=mouse -Exec=setxkbmapgtk-gtk +Exec=key-mapper-gtk Terminal=false Categories=Settings -Comment=GTK based GUI for device specific keyboard mappings +Comment=GUI for device specific keyboard mappings diff --git a/data/key-mapper.glade b/data/key-mapper.glade new file mode 100644 index 00000000..0a7750ea --- /dev/null +++ b/data/key-mapper.glade @@ -0,0 +1,173 @@ + + + + + + False + + + True + False + vertical + + + True + False + 10 + vertical + 10 + + + True + False + 10 + + + 50 + True + False + Device + 0 + + + False + True + 0 + + + + + 200 + True + False + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + 10 + + + 50 + True + False + Profile + 0 + + + False + True + 0 + + + + + 200 + True + False + + + True + True + 1 + + + + + False + True + 1 + + + + + True + False + 10 + + + 50 + True + False + Mapping + 0 + + + True + True + 0 + + + + + Add + True + True + True + + + False + True + 1 + + + + + False + True + 2 + + + + + False + True + 0 + + + + + True + False + + + False + True + 1 + + + + + True + False + vertical + + + + + + True + True + 2 + + + + + + + + + diff --git a/data/setxkbmap-gtk.glade b/data/setxkbmap-gtk.glade deleted file mode 100644 index e69de29b..00000000 diff --git a/keymapper/config.py b/keymapper/config.py new file mode 100644 index 00000000..0f2aaa24 --- /dev/null +++ b/keymapper/config.py @@ -0,0 +1,196 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# key-mapper - GUI for device specific keyboard mappings +# Copyright (C) 2020 sezanzeb +# +# 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 . + + +"""Query settings, parse and write config files.""" + + +import os + +from keymapper.logger import logger + + +_config = None + + +_defaults = { + 'pcm_input': 'null', + 'input_use_dsnoop': True, + 'input_use_softvol': True, + 'input_plugin': 'hw', + 'pcm_output': 'null', + 'output_use_dmix': True, + 'output_use_softvol': True, + 'output_channels': 2, + 'output_plugin': 'hw' +} + + +def _modify_config(config_contents, key, value): + """Return a string representing the modified contents of the config file. + + Parameters + ---------- + config_contents : string + Contents of the config file in ~/.config/key-mapper/config. + It is not edited in place and the config file is not overwritten. + key : string + Settings key that should be modified + value : string, int + Value to write + """ + logger.info('Setting "%s" to "%s"', key, value) + + split = config_contents.split('\n') + if split[-1] == '': + split = split[:-1] + + found = False + setting = f'{key}={value}' + for i, line in enumerate(split): + strip = line.strip() + if strip.startswith('#'): + continue + if strip.startswith(f'{key}='): + # replace the setting + logger.debug('Overwriting "%s=%s" in config', key, value) + split[i] = setting + found = True + break + if not found: + logger.debug('Adding "%s=%s" to config', key, value) + split.append(setting) + return '\n'.join(split) + + +class Config: + """Read and set config values.""" + def __init__(self, path=None): + """Initialize the interface to the config file. + + Parameters + ---------- + path : string or None + If none, will default to '~/.config/key-mapper/config' + """ + if path is None: + path = os.path.expanduser('~/.config/key-mapper/config') + logger.debug('Using config file at %s', path) + + self._path = path + self._config = {} + self.mtime = 0 + + self.create_config_file() + + self.load_config() + + def create_config_file(self): + """Create an empty config if it doesn't exist.""" + if not os.path.exists(os.path.dirname(self._path)): + os.makedirs(os.path.dirname(self._path)) + if not os.path.exists(self._path): + logger.info('Creating config file "%s"', self._path) + os.mknod(self._path) + + def load_config(self): + """Read the config file.""" + logger.debug('Loading configuration') + self._config = {} + # load config + self.mtime = os.path.getmtime(self._path) + with open(self._path, 'r') as config_file: + for line in config_file: + line = line.strip() + if not line.startswith('#'): + split = line.split('=', 1) + if len(split) == 2: + key = split[0] + value = split[1] + else: + key = split[0] + value = None + if value.isdigit(): + value = int(value) + else: + try: + value = float(value) + except ValueError: + pass + if value == 'True': + value = True + if value == 'False': + value = False + self._config[key] = value + + def check_mtime(self): + """Check if the config file has been modified and reload if needed.""" + if os.path.getmtime(self._path) != self.mtime: + logger.info('Config changed, reloading') + self.load_config() + + def get(self, key): + """Read a value from the configuration or get the default.""" + self.check_mtime() + if key not in _defaults: + logger.error('Unknown setting %s', key) + return None + return self._config.get(key, _defaults[key]) + + def set(self, key, value): + """Write a setting into memory and ~/.config/key-mapper/config.""" + if key not in _defaults: + logger.error('Unknown setting %s', key) + return None + + self.check_mtime() + + if key in self._config and self._config[key] == value: + logger.debug('Setting "%s" is already "%s"', key, value) + return False + + self._config[key] = value + + with open(self._path, 'r+') as config_file: + config_contents = config_file.read() + config_contents = _modify_config(config_contents, key, value) + + # overwrite completely + with open(self._path, 'w') as config_file: + if not config_contents.endswith('\n'): + config_contents += '\n' + config_file.write(config_contents) + + self.mtime = os.path.getmtime(self._path) + return True + + +def get_config(*args, **kwargs): + """Ask for the config. Initialize it if not yet done so. + + Will pass any parameters to the config constructor. Only needed in tests + to avoid writing the users config. + """ + # don't initialize it right away in the global scope, to avoid having + # the wrong logging verbosity. + global _config + if _config is None: + _config = Config(*args, **kwargs) + return _config diff --git a/keymapper/logger.py b/keymapper/logger.py index 6296dacf..5b8612a9 100644 --- a/keymapper/logger.py +++ b/keymapper/logger.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 # -*- coding: utf-8 -*- -# key-mapper - GTK based GUI for device specific keyboard mappings +# key-mapper - GUI for device specific keyboard mappings # Copyright (C) 2020 sezanzeb # # This file is part of key-mapper. @@ -66,8 +66,8 @@ logger.setLevel(logging.INFO) def log_info(): """Log version and name to the console""" # read values from setup.py - version = pkg_resources.require('setxkbmapgtk')[0].version - name = pkg_resources.require('setxkbmapgtk')[0].project_name + version = pkg_resources.require('key-mapper')[0].version + name = pkg_resources.require('key-mapper')[0].project_name logger.info('%s %s', version, name) @@ -86,7 +86,7 @@ def debug_log_on(): def add_filehandler(): """Clear the existing logfile and start logging to it.""" # jack also logs to ~/.log - log_path = os.path.expanduser('~/.log/setxkbmapgtk') + log_path = os.path.expanduser('~/.log/key-mapper') log_file = os.path.join(log_path, 'log') if not os.path.exists(log_path): diff --git a/keymapper/util.py b/keymapper/util.py index 9f3640cc..42efd2d9 100644 --- a/keymapper/util.py +++ b/keymapper/util.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 # -*- coding: utf-8 -*- -# key-mapper - GTK based GUI for device specific keyboard mappings +# key-mapper - GUI for device specific keyboard mappings # Copyright (C) 2020 sezanzeb # # This file is part of key-mapper. @@ -22,7 +22,7 @@ """Helperfunctions to find device ids, names, and to load configs.""" -from setxkbmapgtk.logger import logger +from keymapper.logger import logger def find_devices(): diff --git a/setup.py b/setup.py index 4ae6464f..a1e6ba52 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 # -*- coding: utf-8 -*- -# key-mapper - GTK based GUI for device specific keyboard mappings +# key-mapper - GUI for device specific keyboard mappings # Copyright (C) 2020 sezanzeb # # This file is part of key-mapper. @@ -23,11 +23,11 @@ import DistUtilsExtra.auto DistUtilsExtra.auto.setup( - name='setxkbmapgtk', + name='key-mapper', version='0.1.0', - description='GTK based GUI for device specific keyboard mappings', + description='GUI for device specific keyboard mappings', license='GPL-3.0', data_files=[ - ('share/applications/', ['data/setxkbmapgtk.desktop']), + ('share/applications/', ['data/key-mapper.desktop']), ], ) diff --git a/tests/test.py b/tests/test.py index e82d3e88..e87fa20a 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 # -*- coding: utf-8 -*- -# key-mapper - GTK based GUI for device specific keyboard mappings +# key-mapper - GUI for device specific keyboard mappings # Copyright (C) 2020 sezanzeb # # This file is part of key-mapper. diff --git a/tests/testcases/skeleton.py b/tests/testcases/config.py similarity index 54% rename from tests/testcases/skeleton.py rename to tests/testcases/config.py index dacf40f1..2b84ee21 100644 --- a/tests/testcases/skeleton.py +++ b/tests/testcases/config.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 # -*- coding: utf-8 -*- -# key-mapper - GTK based GUI for device specific keyboard mappings +# key-mapper - GUI for device specific keyboard mappings # Copyright (C) 2020 sezanzeb # # This file is part of key-mapper. @@ -21,10 +21,24 @@ import unittest +from keymapper.config import _modify_config + class ConfigTest(unittest.TestCase): - def test_nothing(self): - pass + def test_first_line(self): + contents = """a=1\n # test=3\n abc=123""" + contents = _modify_config(contents, 'a', 3) + self.assertEqual(contents, """a=3\n # test=3\n abc=123""") + + def test_last_line(self): + contents = """a=1\n # test=3\n abc=123""" + contents = _modify_config(contents, 'abc', 'foo') + self.assertEqual(contents, """a=1\n # test=3\nabc=foo""") + + def test_new_line(self): + contents = """a=1\n # test=3\n abc=123""" + contents = _modify_config(contents, 'test', '1234') + self.assertEqual(contents, """a=1\n # test=3\n abc=123\ntest=1234""") if __name__ == "__main__":