From 0cf3a8fe30bec6e6db6c6663ec57aa2d657a0f77 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 7 Mar 2022 17:45:37 -0600 Subject: [PATCH] added keybinding extendable logic --- src/context/controller.py | 24 +++- src/context/controller_data.py | 2 + src/utils/keybindings.py | 127 ++++++++++++++++++ src/utils/settings.py | 13 +- .../usr/share/app_name/Main_Window.glade | 8 +- .../usr/share/app_name/key-bindings.json | 23 ++++ 6 files changed, 189 insertions(+), 8 deletions(-) create mode 100644 src/utils/keybindings.py create mode 100644 user_config/usr/share/app_name/key-bindings.json diff --git a/src/context/controller.py b/src/context/controller.py index e274d48..b800250 100644 --- a/src/context/controller.py +++ b/src/context/controller.py @@ -5,7 +5,8 @@ import threading, subprocess, time # Gtk imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GLib +gi.require_version('Gdk', '3.0') +from gi.repository import Gtk, Gdk, GLib # Application imports from .mixins.dummy_mixin import DummyMixin @@ -50,6 +51,27 @@ class Controller(DummyMixin, Controller_Data): def handle_file_from_ipc(self, path): print(f"Path From IPC: {path}") + def on_global_key_release_controller(self, widget, event): + """Handler for keyboard events""" + keyname = Gdk.keyval_name(event.keyval).lower() + if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: + if "control" in keyname: + self.ctrl_down = False + if "shift" in keyname: + self.shift_down = False + if "alt" in keyname: + self.alt_down = False + + + mapping = self.keybindings.lookup(event) + if mapping: + getattr(self, mapping)() + return True + else: + print(f"on_global_key_release_controller > key > {keyname}") + print(f"Add logic or remove this from: {self.__class__}") + + def get_clipboard_data(self): proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE) diff --git a/src/context/controller_data.py b/src/context/controller_data.py index f4604e2..828af14 100644 --- a/src/context/controller_data.py +++ b/src/context/controller_data.py @@ -18,6 +18,8 @@ class Controller_Data: self.builder = self.settings.get_builder() self.window = self.settings.get_main_window() self.logger = self.settings.get_logger() + self.keybindings = self.settings.get_keybindings() + self.home_path = self.settings.get_home_path() self.success_color = self.settings.get_success_color() diff --git a/src/utils/keybindings.py b/src/utils/keybindings.py new file mode 100644 index 0000000..a7bbbf6 --- /dev/null +++ b/src/utils/keybindings.py @@ -0,0 +1,127 @@ +# Python imports +import re + +# Gtk imports +import gi +gi.require_version('Gdk', '3.0') +from gi.repository import Gdk + +# Application imports + + + +def err(log = ""): + """Print an error message""" + print(log) + + +class KeymapError(Exception): + """Custom exception for errors in keybinding configurations""" + +MODIFIER = re.compile('<([^<]+)>') +class Keybindings: + """Class to handle loading and lookup of Terminator keybindings""" + + modifiers = { + 'ctrl': Gdk.ModifierType.CONTROL_MASK, + 'control': Gdk.ModifierType.CONTROL_MASK, + 'primary': Gdk.ModifierType.CONTROL_MASK, + 'shift': Gdk.ModifierType.SHIFT_MASK, + 'alt': Gdk.ModifierType.MOD1_MASK, + 'super': Gdk.ModifierType.SUPER_MASK, + 'hyper': Gdk.ModifierType.HYPER_MASK, + 'mod2': Gdk.ModifierType.MOD2_MASK + } + + empty = {} + keys = None + _masks = None + _lookup = None + + def __init__(self): + self.keymap = Gdk.Keymap.get_default() + self.configure({}) + + def configure(self, bindings): + """Accept new bindings and reconfigure with them""" + self.keys = bindings + self.reload() + + def reload(self): + """Parse bindings and mangle into an appropriate form""" + self._lookup = {} + self._masks = 0 + + for action, bindings in list(self.keys.items()): + if isinstance(bindings, list): + bindings = (*bindings,) + elif not isinstance(bindings, tuple): + bindings = (bindings,) + + + for binding in bindings: + if not binding or binding == "None": + continue + + try: + keyval, mask = self._parsebinding(binding) + # Does much the same, but with poorer error handling. + #keyval, mask = Gtk.accelerator_parse(binding) + except KeymapError as e: + err ("keybinding reload failed to parse binding '%s': %s" % (binding, e)) + else: + if mask & Gdk.ModifierType.SHIFT_MASK: + if keyval == Gdk.KEY_Tab: + keyval = Gdk.KEY_ISO_Left_Tab + mask &= ~Gdk.ModifierType.SHIFT_MASK + else: + keyvals = Gdk.keyval_convert_case(keyval) + if keyvals[0] != keyvals[1]: + keyval = keyvals[1] + mask &= ~Gdk.ModifierType.SHIFT_MASK + else: + keyval = Gdk.keyval_to_lower(keyval) + + self._lookup.setdefault(mask, {}) + self._lookup[mask][keyval] = action + self._masks |= mask + + def _parsebinding(self, binding): + """Parse an individual binding using Gtk's binding function""" + mask = 0 + modifiers = re.findall(MODIFIER, binding) + + if modifiers: + for modifier in modifiers: + mask |= self._lookup_modifier(modifier) + + key = re.sub(MODIFIER, '', binding) + if key == '': + raise KeymapError('No key found!') + + keyval = Gdk.keyval_from_name(key) + + if keyval == 0: + raise KeymapError("Key '%s' is unrecognised..." % key) + return (keyval, mask) + + def _lookup_modifier(self, modifier): + """Map modifier names to gtk values""" + try: + return self.modifiers[modifier.lower()] + except KeyError: + raise KeymapError("Unhandled modifier '<%s>'" % modifier) + + def lookup(self, event): + """Translate a keyboard event into a mapped key""" + try: + _found, keyval, _egp, _lvl, consumed = self.keymap.translate_keyboard_state( + event.hardware_keycode, + Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK), + event.group) + except TypeError: + err ("Keybinding lookup failed to translate keyboard event: %s" % dir(event)) + return None + + mask = (event.get_state() & ~consumed) & self._masks + return self._lookup.get(mask, self.empty).get(keyval, None) diff --git a/src/utils/settings.py b/src/utils/settings.py index a7cab15..a7b0ae0 100644 --- a/src/utils/settings.py +++ b/src/utils/settings.py @@ -1,5 +1,5 @@ # Python imports -import os +import os, json # Gtk imports import gi, cairo @@ -12,7 +12,7 @@ from gi.repository import Gdk # Application imports from .logger import Logger - +from .keybindings import Keybindings @@ -23,6 +23,7 @@ class Settings: self._CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}" self._PLUGINS_PATH = f"{self._CONFIG_PATH}/plugins" self._GLADE_FILE = f"{self._CONFIG_PATH}/Main_Window.glade" + self._KEY_BINDINGS = f"{self._CONFIG_PATH}/key-bindings.json" self._CSS_FILE = f"{self._CONFIG_PATH}/stylesheet.css" self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons" self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" @@ -35,6 +36,8 @@ class Settings: if not os.path.exists(self._GLADE_FILE): self._GLADE_FILE = f"{self._USR_PATH}/Main_Window.glade" + if not os.path.exists(self._KEY_BINDINGS): + self._KEY_BINDINGS = f"{self._USR_SOLARFM}/key-bindings.json" if not os.path.exists(self._CSS_FILE): self._CSS_FILE = f"{self._USR_PATH}/stylesheet.css" if not os.path.exists(self._WINDOW_ICON): @@ -54,6 +57,11 @@ class Settings: self._warning_color = "#ffa800" self._error_color = "#ff0000" + self._keybindings = Keybindings() + with open(self._KEY_BINDINGS) as file: + keybindings = json.load(file)["keybindings"] + self._keybindings.configure(keybindings) + self._main_window = None self._logger = Logger(self._CONFIG_PATH).get_logger() self._builder = Gtk.Builder() @@ -105,6 +113,7 @@ class Settings: def get_builder(self): return self._builder def get_logger(self): return self._logger + def get_keybindings(self): return self._keybindings def get_main_window(self): return self._main_window def get_home_path(self): return self._USER_HOME def get_plugins_path(self): return self._PLUGINS_PATH diff --git a/user_config/usr/share/app_name/Main_Window.glade b/user_config/usr/share/app_name/Main_Window.glade index 38409fa..381c685 100644 --- a/user_config/usr/share/app_name/Main_Window.glade +++ b/user_config/usr/share/app_name/Main_Window.glade @@ -1,12 +1,10 @@ - + - False - - - + False + diff --git a/user_config/usr/share/app_name/key-bindings.json b/user_config/usr/share/app_name/key-bindings.json new file mode 100644 index 0000000..487bc02 --- /dev/null +++ b/user_config/usr/share/app_name/key-bindings.json @@ -0,0 +1,23 @@ +{ + "keybindings": { + "help" : "F1", + "rename_files" : ["F2", "e"], + "open_terminal" : "F4", + "refresh_tab" : ["F5", "r"], + "delete_files" : "Delete", + "tggl_top_main_menubar" : "", + "trash_files" : "t", + "tear_down" : "q", + "go_up" : "Up", + "go_home" : "slash", + "grab_focus_path_entry" : "l", + "open_files" : "o", + "show_hide_hidden_files" : "h", + "keyboard_create_tab" : "t", + "keyboard_close_tab" : "w", + "keyboard_copy_files" : "c", + "keyboard_cut_files" : "x", + "paste_files" : "v", + "show_new_file_menu" : "n" + } +}