input-remapper/bin/key-mapper-gtk

211 lines
6.7 KiB
Plaintext
Raw Normal View History

2020-10-26 22:45:22 +00:00
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# key-mapper - GUI for device specific keyboard mappings
2020-10-26 22:45:22 +00:00
# Copyright (C) 2020 sezanzeb <proxima@hip70890b.de>
#
2020-10-31 13:02:59 +00:00
# This file is part of key-mapper.
2020-10-26 22:45:22 +00:00
#
2020-10-31 13:02:59 +00:00
# key-mapper is free software: you can redistribute it and/or modify
2020-10-26 22:45:22 +00:00
# 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.
#
2020-10-31 13:02:59 +00:00
# key-mapper is distributed in the hope that it will be useful,
2020-10-26 22:45:22 +00:00
# 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
2020-10-31 13:02:59 +00:00
# along with key-mapper. If not, see <https://www.gnu.org/licenses/>.
2020-10-26 22:45:22 +00:00
"""User Interface."""
import os
import sys
from argparse import ArgumentParser
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GLib', '2.0')
2020-10-31 14:03:31 +00:00
from gi.repository import Gtk
2020-10-26 22:45:22 +00:00
2020-10-31 14:03:31 +00:00
from keymapper.data import get_data_path
2020-10-31 17:48:03 +00:00
from keymapper.presets import find_devices, get_presets, get_mappings, \
find_newest_preset, create_preset
from keymapper.logger import logger, update_verbosity, log_info
2020-10-26 22:45:22 +00:00
2020-10-31 16:43:21 +00:00
window = None
2020-10-31 16:00:02 +00:00
class SingleKeyMapping:
"""A single, configurable key mapping."""
def __init__(self, delete_callback):
"""Construct a row and add it to the list in the GUI."""
self.delete_callback = delete_callback
self.put_together()
2020-10-31 16:43:21 +00:00
def get_widgets(self):
2020-10-31 16:00:02 +00:00
"""Return the widget that wraps all the widgets of the row."""
2020-10-31 16:43:21 +00:00
return self.widgets
2020-10-31 16:00:02 +00:00
def put_together(self):
"""Create all GTK widgets."""
delete_button = Gtk.Button()
destroy_icon = Gtk.Image.new_from_icon_name(
'window-close', Gtk.IconSize.BUTTON
)
delete_button.set_image(destroy_icon)
delete_button.connect('clicked', self.on_delete_button_clicked)
key_code = Gtk.Entry()
key_code.set_width_chars(4)
original_key = Gtk.Entry()
original_key.set_width_chars(4)
2020-10-31 16:43:21 +00:00
self.widgets = (delete_button, key_code, original_key)
2020-10-31 16:00:02 +00:00
def on_delete_button_clicked(self, button):
"""Destroy the row and remove it from the config."""
2020-10-31 16:43:21 +00:00
for widget in self.widgets:
widget.destroy()
2020-10-31 16:00:02 +00:00
self.delete_callback(self)
2020-10-26 22:45:22 +00:00
class Window:
"""User Interface."""
def __init__(self):
2020-10-31 16:43:21 +00:00
self.rows = 0
self.selected_device = None
2020-10-31 16:43:21 +00:00
2020-10-31 13:02:59 +00:00
gladefile = os.path.join(get_data_path(), 'key-mapper.glade')
2020-10-26 22:45:22 +00:00
builder = Gtk.Builder()
builder.add_from_file(gladefile)
builder.connect_signals(self)
self.builder = builder
2020-10-31 14:03:31 +00:00
window = builder.get_object('window')
2020-10-26 22:45:22 +00:00
window.show()
self.window = window
2020-10-31 16:00:02 +00:00
self.populate_devices()
2020-10-31 17:48:03 +00:00
# find an select the newest preset based on file modification dates
device, preset = find_newest_preset()
2020-10-31 18:12:27 +00:00
if device is not None:
self.on_select_device(device)
2020-10-31 18:12:27 +00:00
if preset is not None:
self.on_select_preset(preset)
2020-10-31 16:00:02 +00:00
def get(self, name):
"""Get a widget from the window"""
return self.builder.get_object(name)
2020-10-31 14:03:31 +00:00
def on_close(self, *_):
"""Safely close the application."""
Gtk.main_quit()
2020-10-31 16:00:02 +00:00
def populate_devices(self):
"""Make the devices selectable."""
devices = find_devices()
device_selection = self.get('device_selection')
for (id, device) in devices:
device_selection.append(device, device)
def populate_presets(self):
2020-10-31 17:48:03 +00:00
"""Show the available presets for the selected device."""
presets = get_presets(self.selected_device)
2020-10-31 17:48:03 +00:00
preset_selection = self.get('preset_selection')
2020-10-31 19:19:46 +00:00
preset_selection.remove_all()
2020-10-31 17:48:03 +00:00
for preset in presets:
preset_selection.append(preset, preset)
2020-10-31 19:19:46 +00:00
# and select the newest one (on the top)
preset_selection.set_active(0)
def on_select_device(self, device):
"""List all presets, create one if none exist yet."""
if isinstance(device, Gtk.ComboBoxText):
preset = device.get_active_text()
2020-10-31 17:48:03 +00:00
device = device.get_active_text()
2020-10-31 17:48:03 +00:00
presets = get_presets(device)
if len(presets) == 0:
create_preset(device)
self.selected_device = device
self.populate_presets()
def on_create_preset_clicked(self, button):
"""Create a new preset and select it."""
new_preset = create_preset(self.selected_device)
self.get('preset_selection').append(new_preset, new_preset)
2020-10-31 18:12:27 +00:00
self.get('preset_selection').set_active_id(new_preset)
2020-10-31 17:48:03 +00:00
def on_select_preset(self, preset):
"""Show the mappings of the preset"""
if isinstance(preset, Gtk.ComboBoxText):
preset = preset.get_active_text()
2020-10-31 16:00:02 +00:00
2020-10-31 16:43:21 +00:00
# prepare one empty input to add stuff, and to get the grid to
# the correct column width, otherwise it may jump if the user adds
# the first row.
key_list = self.get('key_list')
for i in range(self.rows):
# don't remove the header
key_list.remove_row(i + 1)
self.rows = 0
# TODO show all mapped keys from config
2020-10-31 16:43:21 +00:00
self.on_add_key_clicked()
2020-10-31 16:00:02 +00:00
2020-10-31 16:43:21 +00:00
def on_add_key_clicked(self, button=None):
2020-10-31 16:00:02 +00:00
"""Add a mapping to the list of mappings."""
single_key_mapping = SingleKeyMapping(self.on_row_removed)
2020-10-31 16:43:21 +00:00
key_list = self.get('key_list')
key_list.insert_row(1)
widgets = single_key_mapping.get_widgets()
key_list.attach(widgets[0], 0, 1, 1, 1)
key_list.attach(widgets[1], 1, 1, 1, 1)
key_list.attach(widgets[2], 2, 1, 1, 1)
key_list.show_all()
self.rows += 1
2020-10-31 16:00:02 +00:00
def on_row_removed(self, mapping):
"""Stuff to do when a row was removed
Parameters
----------
mapping : SingleKeyMapping
"""
# shrink the window down as much as possible, otherwise it
# will increase with each added mapping but won't go back when they
# are removed.
window = self.get('window')
window.resize(window.get_size()[0], 1)
2020-10-31 16:43:21 +00:00
# note, that the grid row still exist, it just shrank down to 0
# because there are no contents.
self.rows -= 1
if self.rows == 0:
# add back an empty row
self.on_add_key_clicked()
2020-10-26 22:45:22 +00:00
2020-10-31 17:48:03 +00:00
2020-10-26 22:45:22 +00:00
if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument(
'-d', '--debug', action='store_true', dest='debug',
help='Displays additional debug information',
default=False
)
options = parser.parse_args(sys.argv[1:])
update_verbosity(options.debug)
log_info()
2020-10-31 16:43:21 +00:00
window = Window()
2020-10-26 22:45:22 +00:00
Gtk.main()