Port Orca 50 command and input managers
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# Cthulhu
|
||||
#
|
||||
# Copyright (c) 2026 Stormux
|
||||
# Copyright (c) 2023 Igalia, S.L.
|
||||
# Copyright 2023 Igalia, S.L.
|
||||
# Copyright 2023 GNOME Foundation Inc.
|
||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
@@ -18,212 +18,449 @@
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||
# Boston MA 02110-1301 USA.
|
||||
#
|
||||
# Forked from Orca screen reader.
|
||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||
|
||||
"""Manages the Cthulhu modifier key(s)."""
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-arguments
|
||||
# pylint: disable=too-many-positional-arguments
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
|
||||
__id__ = "$Id$"
|
||||
__version__ = "$Revision$"
|
||||
__date__ = "$Date$"
|
||||
__copyright__ = "Copyright (c) 2026 Stormux"
|
||||
__license__ = "LGPL"
|
||||
"""Manages the Cthulhu modifier key."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import gi
|
||||
gi.require_version("Atspi", "2.0")
|
||||
from gi.repository import Atspi
|
||||
from gi.repository import GLib
|
||||
|
||||
import cthulhu.debug as debug
|
||||
import cthulhu.settings as settings
|
||||
gi.require_version("Atspi", "2.0")
|
||||
gi.require_version("Gdk", "3.0")
|
||||
from gi.repository import (
|
||||
Atspi,
|
||||
Gdk, # pylint: disable=no-name-in-module
|
||||
GLib,
|
||||
)
|
||||
|
||||
from . import debug, gsettings_registry, input_event_manager, keybindings
|
||||
|
||||
DESKTOP_MODIFIER_KEYS: list[str] = ["Insert", "KP_Insert"]
|
||||
LAPTOP_MODIFIER_KEYS: list[str] = ["Caps_Lock", "Shift_Lock"]
|
||||
_SCHEMA = "keybindings"
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .input_event import KeyboardEvent
|
||||
|
||||
|
||||
class CthulhuModifierManager:
|
||||
"""Manages the Cthulhu modifier."""
|
||||
|
||||
def __init__(self):
|
||||
self._originalXmodmap = b""
|
||||
self._capsLockCleared = False
|
||||
def __init__(self) -> None:
|
||||
self._modifier_keys_override: list[str] | None = None
|
||||
self._applied_modifier_keys: list[str] = []
|
||||
self._grabbed_modifiers: dict = {}
|
||||
self._is_pressed: bool = False
|
||||
self._modifiers_are_set: bool = False
|
||||
|
||||
def refreshCthulhuModifiers(self, reason=""):
|
||||
"""Refreshes the Cthulhu modifier keys."""
|
||||
# Related to hacks which will soon die.
|
||||
self._original_xmodmap: bytes = b""
|
||||
self._caps_lock_cleared: bool = False
|
||||
self._need_to_restore_cthulhu_modifier: bool = False
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Refreshing Cthulhu modifiers"
|
||||
if reason:
|
||||
msg += f": {reason}"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
display = os.environ.get("DISPLAY")
|
||||
if not display:
|
||||
msg = "CTHULHU MODIFIER MANAGER: DISPLAY not set, skipping xkbcomp operations"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
return
|
||||
|
||||
self.unsetCthulhuModifiers(reason)
|
||||
with subprocess.Popen(["xkbcomp", display, "-"],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) as process:
|
||||
self._originalXmodmap, _ = process.communicate()
|
||||
self._createCthulhuXmodmap()
|
||||
|
||||
def unsetCthulhuModifiers(self, reason=""):
|
||||
"""Turns the Cthulhu modifiers back into their original purpose."""
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Attempting to restore original xmodmap"
|
||||
if reason:
|
||||
msg += f": {reason}"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
if not self._originalXmodmap:
|
||||
msg = "CTHULHU MODIFIER MANAGER: No stored xmodmap found"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
return
|
||||
|
||||
display = os.environ.get("DISPLAY")
|
||||
if not display:
|
||||
msg = "CTHULHU MODIFIER MANAGER: DISPLAY not set, skipping xmodmap restoration"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
return
|
||||
|
||||
self._capsLockCleared = False
|
||||
with subprocess.Popen(["xkbcomp", "-w0", "-", display],
|
||||
stdin=subprocess.PIPE, stdout=None, stderr=None) as process:
|
||||
process.communicate(self._originalXmodmap)
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Original xmodmap restored"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def _createCthulhuXmodmap(self):
|
||||
"""Makes a Cthulhu-specific Xmodmap so that the modifier works."""
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Creating Cthulhu xmodmap"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
if "Caps_Lock" in settings.cthulhuModifierKeys \
|
||||
or "Shift_Lock" in settings.cthulhuModifierKeys:
|
||||
self.setCapsLockAsCthulhuModifier(True)
|
||||
self._capsLockCleared = True
|
||||
elif self._capsLockCleared:
|
||||
self.setCapsLockAsCthulhuModifier(False)
|
||||
self._capsLockCleared = False
|
||||
|
||||
def setCapsLockAsCthulhuModifier(self, enable):
|
||||
"""Enable or disable use of the caps lock key as a Cthulhu modifier key."""
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Setting caps lock as the Cthulhu modifier"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
display = os.environ.get("DISPLAY")
|
||||
if not display:
|
||||
msg = "CTHULHU MODIFIER MANAGER: DISPLAY not set, cannot modify caps lock"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
return
|
||||
|
||||
if not self._originalXmodmap:
|
||||
msg = "CTHULHU MODIFIER MANAGER: No xmodmap available, cannot modify caps lock"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
return
|
||||
|
||||
interpretCapsLineProg = re.compile(
|
||||
r'^\s*interpret\s+Caps[_+]Lock[_+]AnyOfOrNone\s*\(all\)\s*{\s*$', re.I)
|
||||
normalCapsLineProg = re.compile(
|
||||
r'^\s*action\s*=\s*LockMods\s*\(\s*modifiers\s*=\s*Lock\s*\)\s*;\s*$', re.I)
|
||||
interpretShiftLineProg = re.compile(
|
||||
r'^\s*interpret\s+Shift[_+]Lock[_+]AnyOf\s*\(\s*Shift\s*\+\s*Lock\s*\)\s*{\s*$', re.I)
|
||||
normalShiftLineProg = re.compile(
|
||||
r'^\s*action\s*=\s*LockMods\s*\(\s*modifiers\s*=\s*Shift\s*\)\s*;\s*$', re.I)
|
||||
disabledModLineProg = re.compile(
|
||||
r'^\s*action\s*=\s*NoAction\s*\(\s*\)\s*;\s*$', re.I)
|
||||
normalCapsLine = ' action= LockMods(modifiers=Lock);'
|
||||
normalShiftLine = ' action= LockMods(modifiers=Shift);'
|
||||
disabledModLine = ' action= NoAction();'
|
||||
lines = self._originalXmodmap.decode('UTF-8').split('\n')
|
||||
foundCapsInterpretSection = False
|
||||
foundShiftInterpretSection = False
|
||||
modified = False
|
||||
for i, line in enumerate(lines):
|
||||
if not foundCapsInterpretSection and not foundShiftInterpretSection:
|
||||
if interpretCapsLineProg.match(line):
|
||||
foundCapsInterpretSection = True
|
||||
elif interpretShiftLineProg.match(line):
|
||||
foundShiftInterpretSection = True
|
||||
elif foundCapsInterpretSection:
|
||||
if enable:
|
||||
if normalCapsLineProg.match(line):
|
||||
lines[i] = disabledModLine
|
||||
modified = True
|
||||
else:
|
||||
if disabledModLineProg.match(line):
|
||||
lines[i] = normalCapsLine
|
||||
modified = True
|
||||
if line.find('}'):
|
||||
foundCapsInterpretSection = False
|
||||
elif foundShiftInterpretSection:
|
||||
if enable:
|
||||
if normalShiftLineProg.match(line):
|
||||
lines[i] = disabledModLine
|
||||
modified = True
|
||||
else:
|
||||
if disabledModLineProg.match(line):
|
||||
lines[i] = normalShiftLine
|
||||
modified = True
|
||||
if line.find('}'):
|
||||
foundShiftInterpretSection = False
|
||||
if modified:
|
||||
msg = "CTHULHU MODIFIER MANAGER: Updating xmodmap"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
with subprocess.Popen(["xkbcomp", "-w0", "-", display],
|
||||
stdin=subprocess.PIPE, stdout=None, stderr=None) as process:
|
||||
process.communicate(bytes('\n'.join(lines), 'UTF-8'))
|
||||
# Event handlers for input devices being plugged in/unplugged.
|
||||
display = Gdk.Display.get_default() # pylint: disable=no-value-for-parameter
|
||||
if display is not None:
|
||||
device_manager = display.get_device_manager()
|
||||
device_manager.connect("device-added", self._on_device_changed)
|
||||
device_manager.connect("device-removed", self._on_device_changed)
|
||||
else:
|
||||
msg = "CTHULHU MODIFIER MANAGER: Not updating xmodmap"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
msg = "CTHULHU MODIFIER MANAGER: Cannot listen for input device changes."
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def toggleModifier(self, keyboardEvent):
|
||||
def _on_device_changed(self, _device_manager, device: Gdk.Device) -> None:
|
||||
"""Handles device-* signals."""
|
||||
|
||||
source = device.get_source()
|
||||
tokens = ["CTHULHU MODIFIER MANAGER: Device changed", source]
|
||||
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
|
||||
if source == Gdk.InputSource.KEYBOARD:
|
||||
self.refresh_cthulhu_modifiers("Keyboard change detected.")
|
||||
|
||||
def is_cthulhu_modifier(self, modifier: str) -> bool:
|
||||
"""Returns True if modifier is one of the user's Cthulhu modifier keys."""
|
||||
|
||||
if modifier not in self.get_cthulhu_modifier_keys():
|
||||
return False
|
||||
|
||||
if modifier in ["Insert", "KP_Insert"]:
|
||||
return self.is_modifier_grabbed(modifier)
|
||||
|
||||
return self._modifiers_are_set
|
||||
|
||||
def get_pressed_state(self) -> bool:
|
||||
"""Returns True if the Cthulhu modifier has been pressed but not yet released."""
|
||||
|
||||
return self._is_pressed
|
||||
|
||||
def set_pressed_state(self, is_pressed: bool) -> None:
|
||||
"""Updates the pressed state of the modifier based on event."""
|
||||
|
||||
msg = f"CTHULHU MODIFIER MANAGER: Setting pressed state to {is_pressed}"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
self._is_pressed = is_pressed
|
||||
|
||||
def is_modifier_grabbed(self, modifier: str) -> bool:
|
||||
"""Returns True if there is an existing grab for modifier."""
|
||||
|
||||
return modifier in self._grabbed_modifiers
|
||||
|
||||
def add_grabs_for_cthulhu_modifiers(self) -> None:
|
||||
"""Adds grabs for all of the user's Cthulhu modifier keys."""
|
||||
|
||||
for modifier in self.get_cthulhu_modifier_keys():
|
||||
if modifier in ["Insert", "KP_Insert"]:
|
||||
self.add_modifier_grab(modifier)
|
||||
|
||||
def remove_grabs_for_cthulhu_modifiers(self) -> None:
|
||||
"""Removes grabs for all of the user's Cthulhu modifier keys."""
|
||||
|
||||
for modifier in self.get_cthulhu_modifier_keys():
|
||||
if modifier in ["Insert", "KP_Insert"]:
|
||||
self.remove_modifier_grab(modifier)
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Setting pressed state to False for grab removal"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
self._is_pressed = False
|
||||
|
||||
def add_modifier_grab(self, modifier: str) -> None:
|
||||
"""Adds a grab for modifier."""
|
||||
|
||||
if modifier in self._grabbed_modifiers:
|
||||
return
|
||||
|
||||
keyval, keycode = keybindings.get_keycodes(modifier)
|
||||
grab_id = input_event_manager.get_manager().add_grab_for_modifier(modifier, keyval, keycode)
|
||||
if grab_id != -1:
|
||||
self._grabbed_modifiers[modifier] = grab_id
|
||||
|
||||
def remove_modifier_grab(self, modifier: str) -> None:
|
||||
"""Removes the grab for modifier."""
|
||||
|
||||
grab_id = self._grabbed_modifiers.get(modifier)
|
||||
if grab_id is None:
|
||||
return
|
||||
|
||||
input_event_manager.get_manager().remove_grab_for_modifier(modifier, grab_id)
|
||||
del self._grabbed_modifiers[modifier]
|
||||
|
||||
def toggle_modifier(self, keyboard_event: KeyboardEvent) -> None:
|
||||
"""Toggles the modifier to enable double-clicking causing normal behavior."""
|
||||
|
||||
if keyboardEvent.keyval_name in ["Caps_Lock", "Shift_Lock"]:
|
||||
self._toggleModifierLock(keyboardEvent)
|
||||
if keyboard_event.keyval_name in ["Caps_Lock", "Shift_Lock"]:
|
||||
self._toggle_modifier_lock(keyboard_event)
|
||||
return
|
||||
|
||||
def _toggleModifierLock(self, keyboardEvent):
|
||||
self._toggle_modifier_grab(keyboard_event)
|
||||
|
||||
def _toggle_modifier_grab(self, keyboard_event: KeyboardEvent) -> None:
|
||||
"""Toggles the grab for a modifier to enable double-clicking causing normal behavior."""
|
||||
|
||||
# Because we will synthesize another press and release, wait until the real release.
|
||||
if keyboard_event.is_pressed_key():
|
||||
return
|
||||
|
||||
def toggle(hw_code):
|
||||
Atspi.generate_keyboard_event(hw_code, "", Atspi.KeySynthType.PRESSRELEASE)
|
||||
return False
|
||||
|
||||
def restore_grab(modifier):
|
||||
self.add_modifier_grab(modifier)
|
||||
return False
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Removing grab pre-toggle"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
self.remove_modifier_grab(keyboard_event.keyval_name)
|
||||
|
||||
msg = f"CTHULHU MODIFIER MANAGER: Scheduling toggle of {keyboard_event.keyval_name}"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
GLib.timeout_add(1, toggle, keyboard_event.hw_code)
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Scheduling re-adding grab post-toggle"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
GLib.timeout_add(500, restore_grab, keyboard_event.keyval_name)
|
||||
|
||||
def _toggle_modifier_lock(self, keyboard_event: KeyboardEvent) -> None:
|
||||
"""Toggles the lock for a modifier to enable double-clicking causing normal behavior."""
|
||||
|
||||
if not keyboardEvent.is_pressed_key():
|
||||
if not keyboard_event.is_pressed_key():
|
||||
return
|
||||
|
||||
def toggle(modifiers, modifier):
|
||||
if modifiers & modifier:
|
||||
lock = Atspi.KeySynthType.UNLOCKMODIFIERS
|
||||
msg = "CTHULHU MODIFIER MANAGER: Unlocking CapsLock"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
else:
|
||||
lock = Atspi.KeySynthType.LOCKMODIFIERS
|
||||
msg = "CTHULHU MODIFIER MANAGER: Locking CapsLock"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
Atspi.generate_keyboard_event(modifier, "", lock)
|
||||
|
||||
if keyboardEvent.keyval_name == "Caps_Lock":
|
||||
if keyboard_event.keyval_name == "Caps_Lock":
|
||||
modifier = 1 << Atspi.ModifierType.SHIFTLOCK
|
||||
elif keyboardEvent.keyval_name == "Shift_Lock":
|
||||
elif keyboard_event.keyval_name == "Shift_Lock":
|
||||
modifier = 1 << Atspi.ModifierType.SHIFT
|
||||
else:
|
||||
return
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Scheduling lock change"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
GLib.timeout_add(1, toggle, keyboardEvent.modifiers, modifier)
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
GLib.timeout_add(1, toggle, keyboard_event.modifiers, modifier)
|
||||
|
||||
_manager = None
|
||||
def getManager():
|
||||
"""Returns the Cthulhu Modifier Manager"""
|
||||
def get_cthulhu_modifier_keys(self) -> list[str]:
|
||||
"""Returns the active Cthulhu modifier keys via override or layered lookup."""
|
||||
|
||||
if self._modifier_keys_override is not None:
|
||||
return self._modifier_keys_override
|
||||
return self._lookup_modifier_keys()
|
||||
|
||||
def _lookup_modifier_keys(self) -> list[str]:
|
||||
"""Returns modifier keys via two-part layered lookup: layout then per-layout keys."""
|
||||
|
||||
registry = gsettings_registry.get_registry()
|
||||
layout = registry.layered_lookup(
|
||||
_SCHEMA,
|
||||
"keyboard-layout",
|
||||
"",
|
||||
genum="org.stormux.Cthulhu.KeyboardLayout",
|
||||
default="desktop",
|
||||
)
|
||||
if layout == "desktop":
|
||||
return registry.layered_lookup(
|
||||
_SCHEMA,
|
||||
"desktop-modifier-keys",
|
||||
"as",
|
||||
default=DESKTOP_MODIFIER_KEYS,
|
||||
)
|
||||
return registry.layered_lookup(
|
||||
_SCHEMA,
|
||||
"laptop-modifier-keys",
|
||||
"as",
|
||||
default=LAPTOP_MODIFIER_KEYS,
|
||||
)
|
||||
|
||||
def set_modifier_keys_override(self, keys: list[str] | None) -> None:
|
||||
"""Sets or clears a temporary override for the modifier keys."""
|
||||
|
||||
self._modifier_keys_override = keys
|
||||
|
||||
def needs_modifier_refresh(self) -> bool:
|
||||
"""Returns True if the current modifier keys differ from what was last applied."""
|
||||
|
||||
return self.get_cthulhu_modifier_keys() != self._applied_modifier_keys
|
||||
|
||||
def set_modifiers_for_layout(self) -> None:
|
||||
"""Unsets and refreshes modifier keys for the current layout."""
|
||||
|
||||
self.unset_cthulhu_modifiers("Keyboard layout changing.")
|
||||
self.refresh_cthulhu_modifiers("Keyboard layout changed.")
|
||||
|
||||
def refresh_cthulhu_modifiers(self, reason: str = "") -> None:
|
||||
"""Refreshes the Cthulhu modifier keys, including grabs and xmodmap."""
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Refreshing Cthulhu modifiers"
|
||||
if reason:
|
||||
msg += f": {reason}"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
for modifier in list(self._grabbed_modifiers.keys()):
|
||||
self.remove_modifier_grab(modifier)
|
||||
self._is_pressed = False
|
||||
self._applied_modifier_keys = list(self.get_cthulhu_modifier_keys())
|
||||
self.add_grabs_for_cthulhu_modifiers()
|
||||
self._modifiers_are_set = True
|
||||
|
||||
display = os.environ.get("DISPLAY")
|
||||
if not display:
|
||||
msg = "CTHULHU MODIFIER MANAGER: DISPLAY not set, skipping xkbcomp operations"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
return
|
||||
|
||||
self._restore_original_xkbcomp()
|
||||
with subprocess.Popen(
|
||||
["xkbcomp", display, "-"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
) as p:
|
||||
self._original_xmodmap, _ = p.communicate()
|
||||
self._create_cthulhu_xmodmap()
|
||||
|
||||
def _create_cthulhu_xmodmap(self) -> None:
|
||||
"""Makes an Cthulhu-specific Xmodmap so that the Cthulhu modifier works."""
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Creating Cthulhu xmodmap"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
cthulhu_modifiers = self.get_cthulhu_modifier_keys()
|
||||
if "Caps_Lock" in cthulhu_modifiers or "Shift_Lock" in cthulhu_modifiers:
|
||||
self.set_caps_lock_as_cthulhu_modifier(True)
|
||||
self._caps_lock_cleared = True
|
||||
elif self._caps_lock_cleared:
|
||||
self.set_caps_lock_as_cthulhu_modifier(False)
|
||||
self._caps_lock_cleared = False
|
||||
|
||||
def unset_cthulhu_modifiers(self, reason: str = "") -> None:
|
||||
"""Turns the Cthulhu modifiers back into their original purpose."""
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Unsetting Cthulhu modifiers"
|
||||
if reason:
|
||||
msg += f": {reason}"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
self._modifiers_are_set = False
|
||||
self._restore_original_xkbcomp()
|
||||
input_event_manager.get_manager().unmap_all_modifiers()
|
||||
|
||||
def _restore_original_xkbcomp(self) -> None:
|
||||
"""Restores the original xkbcomp keymap."""
|
||||
|
||||
if not self._original_xmodmap:
|
||||
msg = "CTHULHU MODIFIER MANAGER: No stored xmodmap found"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
return
|
||||
|
||||
display = os.environ.get("DISPLAY")
|
||||
if not display:
|
||||
msg = "CTHULHU MODIFIER MANAGER: DISPLAY not set, skipping xmodmap restoration"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
return
|
||||
|
||||
self._caps_lock_cleared = False
|
||||
with subprocess.Popen(
|
||||
["xkbcomp", "-w0", "-", display],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=None,
|
||||
stderr=None,
|
||||
) as p:
|
||||
p.communicate(self._original_xmodmap)
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Original xmodmap restored"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
@staticmethod
|
||||
def _update_xkb_line(
|
||||
line: str,
|
||||
enable: bool,
|
||||
normal_pattern: re.Pattern[str],
|
||||
normal_line: str,
|
||||
disabled_pattern: re.Pattern[str],
|
||||
disabled_line: str,
|
||||
) -> tuple[str, bool]:
|
||||
"""Returns the possibly-updated line and whether it was modified."""
|
||||
|
||||
if enable and normal_pattern.match(line):
|
||||
return disabled_line, True
|
||||
if not enable and disabled_pattern.match(line):
|
||||
return normal_line, True
|
||||
return line, False
|
||||
|
||||
def set_caps_lock_as_cthulhu_modifier(self, enable: bool) -> None:
|
||||
"""Enable or disable use of the caps lock key as an Cthulhu modifier key."""
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Setting caps lock as the Cthulhu modifier"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
display = os.environ.get("DISPLAY")
|
||||
if not display:
|
||||
msg = "CTHULHU MODIFIER MANAGER: DISPLAY not set, cannot modify caps lock"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
return
|
||||
|
||||
if not self._original_xmodmap:
|
||||
msg = "CTHULHU MODIFIER MANAGER: No xmodmap available, cannot modify caps lock"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
return
|
||||
|
||||
interpret_caps_line_prog = re.compile(
|
||||
r"^\s*interpret\s+Caps[_+]Lock[_+]AnyOfOrNone\s*\(all\)\s*{\s*$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
normal_caps_line_prog = re.compile(
|
||||
r"^\s*action\s*=\s*LockMods\s*\(\s*modifiers\s*=\s*Lock\s*\)\s*;\s*$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
interpret_shift_line_prog = re.compile(
|
||||
r"^\s*interpret\s+Shift[_+]Lock[_+]AnyOf\s*\(\s*Shift\s*\+\s*Lock\s*\)\s*{\s*$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
normal_shift_line_prog = re.compile(
|
||||
r"^\s*action\s*=\s*LockMods\s*\(\s*modifiers\s*=\s*Shift\s*\)\s*;\s*$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
disabled_mod_line_prog = re.compile(
|
||||
r"^\s*action\s*=\s*NoAction\s*\(\s*\)\s*;\s*$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
normal_caps_line = " action= LockMods(modifiers=Lock);"
|
||||
normal_shift_line = " action= LockMods(modifiers=Shift);"
|
||||
disabled_mod_line = " action= NoAction();"
|
||||
lines = self._original_xmodmap.decode("UTF-8").split("\n")
|
||||
found_caps_interpret_section = False
|
||||
found_shift_interpret_section = False
|
||||
modified = False
|
||||
for i, line in enumerate(lines):
|
||||
if not found_caps_interpret_section and not found_shift_interpret_section:
|
||||
if interpret_caps_line_prog.match(line):
|
||||
found_caps_interpret_section = True
|
||||
elif interpret_shift_line_prog.match(line):
|
||||
found_shift_interpret_section = True
|
||||
elif found_caps_interpret_section:
|
||||
lines[i], changed = self._update_xkb_line(
|
||||
line,
|
||||
enable,
|
||||
normal_caps_line_prog,
|
||||
normal_caps_line,
|
||||
disabled_mod_line_prog,
|
||||
disabled_mod_line,
|
||||
)
|
||||
modified = modified or changed
|
||||
if line.find("}"):
|
||||
found_caps_interpret_section = False
|
||||
elif found_shift_interpret_section:
|
||||
lines[i], changed = self._update_xkb_line(
|
||||
line,
|
||||
enable,
|
||||
normal_shift_line_prog,
|
||||
normal_shift_line,
|
||||
disabled_mod_line_prog,
|
||||
disabled_mod_line,
|
||||
)
|
||||
modified = modified or changed
|
||||
if line.find("}"):
|
||||
found_shift_interpret_section = False
|
||||
|
||||
if not modified:
|
||||
msg = "CTHULHU MODIFIER MANAGER: Not updating xmodmap"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
return
|
||||
|
||||
msg = "CTHULHU MODIFIER MANAGER: Updating xmodmap"
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
with subprocess.Popen(
|
||||
["xkbcomp", "-w0", "-", display],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=None,
|
||||
stderr=None,
|
||||
) as p:
|
||||
p.communicate(bytes("\n".join(lines), "UTF-8"))
|
||||
|
||||
|
||||
_manager: CthulhuModifierManager = CthulhuModifierManager()
|
||||
|
||||
|
||||
def get_manager() -> CthulhuModifierManager:
|
||||
"""Returns the CthulhuModifierManager singleton."""
|
||||
|
||||
global _manager
|
||||
if _manager is None:
|
||||
_manager = CthulhuModifierManager()
|
||||
return _manager
|
||||
|
||||
|
||||
+599
-1010
File diff suppressed because it is too large
Load Diff
+197
-315
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,7 @@ cthulhu_python_sources = files([
|
||||
'clipboard.py',
|
||||
'cmdnames.py',
|
||||
'colornames.py',
|
||||
'command_manager.py',
|
||||
'compositor_state_adapter.py',
|
||||
'compositor_state_types.py',
|
||||
'compositor_state_wayland.py',
|
||||
|
||||
Reference in New Issue
Block a user