2020-10-31 13:43:09 +00:00
|
|
|
#!/usr/bin/python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# key-mapper - GUI for device specific keyboard mappings
|
|
|
|
# Copyright (C) 2020 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/>.
|
|
|
|
|
|
|
|
|
|
|
|
"""Query settings, parse and write config files."""
|
|
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
from keymapper.logger import logger
|
|
|
|
|
|
|
|
|
2020-10-31 14:22:58 +00:00
|
|
|
# one config file per preset, one folder per device
|
|
|
|
_configs = {}
|
2020-10-31 13:43:09 +00:00
|
|
|
|
|
|
|
|
2020-10-31 14:03:31 +00:00
|
|
|
_defaults = {}
|
2020-10-31 13:43:09 +00:00
|
|
|
|
|
|
|
|
2020-10-31 17:48:03 +00:00
|
|
|
# TODO this works on xmodmaps instead of config files
|
|
|
|
|
|
|
|
|
2020-10-31 13:43:09 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2020-10-31 18:10:40 +00:00
|
|
|
def get_config_path(device, preset=None, path=None):
|
|
|
|
"""Get the path that leads to the coniguration of that preset.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
device : string
|
|
|
|
preset : string or None
|
|
|
|
If none, will return the folder of the device
|
|
|
|
path : string or None
|
|
|
|
If none, will default to '~/.config/key-mapper/'.
|
|
|
|
In that directory, a folder for the device and a file for
|
|
|
|
the preset will be created.
|
|
|
|
"""
|
|
|
|
path = path or os.path.expanduser('~/.config/key-mapper/')
|
|
|
|
return os.path.join(path, device, preset or '')
|
|
|
|
|
|
|
|
|
2020-10-31 13:43:09 +00:00
|
|
|
class Config:
|
|
|
|
"""Read and set config values."""
|
2020-10-31 14:22:58 +00:00
|
|
|
def __init__(self, device, preset, path=None):
|
2020-10-31 13:43:09 +00:00
|
|
|
"""Initialize the interface to the config file.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
2020-10-31 16:00:02 +00:00
|
|
|
device : string
|
|
|
|
preset : string
|
2020-10-31 13:43:09 +00:00
|
|
|
path : string or None
|
2020-10-31 14:22:58 +00:00
|
|
|
If none, will default to '~/.config/key-mapper/'.
|
|
|
|
In that directory, a folder for the device and a file for
|
|
|
|
the preset will be created.
|
2020-10-31 13:43:09 +00:00
|
|
|
"""
|
2020-10-31 18:10:40 +00:00
|
|
|
path = get_config_path(device, preset, path)
|
2020-10-31 13:43:09 +00:00
|
|
|
logger.debug('Using config file at %s', path)
|
|
|
|
|
2020-10-31 14:22:58 +00:00
|
|
|
self.device = device
|
|
|
|
self.preset = preset
|
2020-10-31 13:43:09 +00:00
|
|
|
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()
|
|
|
|
return self._config.get(key, _defaults[key])
|
|
|
|
|
|
|
|
def set(self, key, value):
|
2020-10-31 14:29:25 +00:00
|
|
|
"""Write a setting into memory and ~/.config/key-mapper/."""
|
2020-10-31 13:43:09 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2020-10-31 17:48:03 +00:00
|
|
|
def get_config(device, preset, path=None):
|
|
|
|
"""Ask for a config object.
|
|
|
|
|
|
|
|
There should not be multiple Config objects for the same preset, so make
|
|
|
|
sure to use this function insted of the Config constructor.
|
|
|
|
|
|
|
|
Creates a config file if it doesn't exist yet.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
device : string
|
|
|
|
preset : string
|
|
|
|
path : string or None
|
|
|
|
If none, will default to '~/.config/key-mapper/'.
|
|
|
|
In that directory, a folder for the device and a file for
|
|
|
|
the preset will be created.
|
2020-10-31 13:43:09 +00:00
|
|
|
"""
|
|
|
|
# don't initialize it right away in the global scope, to avoid having
|
|
|
|
# the wrong logging verbosity.
|
2020-10-31 14:22:58 +00:00
|
|
|
global _configs
|
|
|
|
if _configs.get(device) is None:
|
|
|
|
_configs[device] = {}
|
|
|
|
|
|
|
|
if _configs[device].get(preset) is None:
|
|
|
|
_configs[device][preset] = Config(device, preset, path)
|
|
|
|
|
|
|
|
return _configs[device][preset]
|