added keybinding extendable logic

This commit is contained in:
itdominator 2022-03-07 17:45:37 -06:00
parent 976318b958
commit 0cf3a8fe30
6 changed files with 189 additions and 8 deletions

View File

@ -5,7 +5,8 @@ import threading, subprocess, time
# Gtk imports # Gtk imports
import gi import gi
gi.require_version('Gtk', '3.0') 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 # Application imports
from .mixins.dummy_mixin import DummyMixin from .mixins.dummy_mixin import DummyMixin
@ -50,6 +51,27 @@ class Controller(DummyMixin, Controller_Data):
def handle_file_from_ipc(self, path): def handle_file_from_ipc(self, path):
print(f"Path From IPC: {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): def get_clipboard_data(self):
proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE) proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE)

View File

@ -18,6 +18,8 @@ class Controller_Data:
self.builder = self.settings.get_builder() self.builder = self.settings.get_builder()
self.window = self.settings.get_main_window() self.window = self.settings.get_main_window()
self.logger = self.settings.get_logger() self.logger = self.settings.get_logger()
self.keybindings = self.settings.get_keybindings()
self.home_path = self.settings.get_home_path() self.home_path = self.settings.get_home_path()
self.success_color = self.settings.get_success_color() self.success_color = self.settings.get_success_color()

127
src/utils/keybindings.py Normal file
View File

@ -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)

View File

@ -1,5 +1,5 @@
# Python imports # Python imports
import os import os, json
# Gtk imports # Gtk imports
import gi, cairo import gi, cairo
@ -12,7 +12,7 @@ from gi.repository import Gdk
# Application imports # Application imports
from .logger import Logger 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._CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}"
self._PLUGINS_PATH = f"{self._CONFIG_PATH}/plugins" self._PLUGINS_PATH = f"{self._CONFIG_PATH}/plugins"
self._GLADE_FILE = f"{self._CONFIG_PATH}/Main_Window.glade" 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._CSS_FILE = f"{self._CONFIG_PATH}/stylesheet.css"
self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons" self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons"
self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" 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): if not os.path.exists(self._GLADE_FILE):
self._GLADE_FILE = f"{self._USR_PATH}/Main_Window.glade" 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): if not os.path.exists(self._CSS_FILE):
self._CSS_FILE = f"{self._USR_PATH}/stylesheet.css" self._CSS_FILE = f"{self._USR_PATH}/stylesheet.css"
if not os.path.exists(self._WINDOW_ICON): if not os.path.exists(self._WINDOW_ICON):
@ -54,6 +57,11 @@ class Settings:
self._warning_color = "#ffa800" self._warning_color = "#ffa800"
self._error_color = "#ff0000" 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._main_window = None
self._logger = Logger(self._CONFIG_PATH).get_logger() self._logger = Logger(self._CONFIG_PATH).get_logger()
self._builder = Gtk.Builder() self._builder = Gtk.Builder()
@ -105,6 +113,7 @@ class Settings:
def get_builder(self): return self._builder def get_builder(self): return self._builder
def get_logger(self): return self._logger 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_main_window(self): return self._main_window
def get_home_path(self): return self._USER_HOME def get_home_path(self): return self._USER_HOME
def get_plugins_path(self): return self._PLUGINS_PATH def get_plugins_path(self): return self._PLUGINS_PATH

View File

@ -1,12 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 --> <!-- Generated with glade 3.38.2 -->
<interface> <interface>
<requires lib="gtk+" version="3.20"/> <requires lib="gtk+" version="3.20"/>
<object class="GtkApplicationWindow" id="Main_Window"> <object class="GtkApplicationWindow" id="Main_Window">
<property name="can_focus">False</property> <property name="can-focus">False</property>
<child> <signal name="key-release-event" handler="on_global_key_release_controller" swapped="no"/>
<placeholder/>
</child>
<child> <child>
<placeholder/> <placeholder/>
</child> </child>

View File

@ -0,0 +1,23 @@
{
"keybindings": {
"help" : "F1",
"rename_files" : ["F2", "<Control>e"],
"open_terminal" : "F4",
"refresh_tab" : ["F5", "<Control>r"],
"delete_files" : "Delete",
"tggl_top_main_menubar" : "<Alt>",
"trash_files" : "<Shift><Control>t",
"tear_down" : "<Control>q",
"go_up" : "<Control>Up",
"go_home" : "<Control>slash",
"grab_focus_path_entry" : "<Control>l",
"open_files" : "<Control>o",
"show_hide_hidden_files" : "<Control>h",
"keyboard_create_tab" : "<Control>t",
"keyboard_close_tab" : "<Control>w",
"keyboard_copy_files" : "<Control>c",
"keyboard_cut_files" : "<Control>x",
"paste_files" : "<Control>v",
"show_new_file_menu" : "<Control>n"
}
}