%PDF- %PDF-
| Direktori : /lib/python3/dist-packages/orca/ |
| Current File : //lib/python3/dist-packages/orca/orca_modifier_manager.py |
# Orca
#
# 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
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
# Boston MA 02110-1301 USA.
"""Manages the Orca modifier key."""
__id__ = "$Id$"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2023 Igalia, S.L." \
"Copyright (c) 2023 GNOME Foundation Inc."
__license__ = "LGPL"
import os
import re
import subprocess
import gi
gi.require_version('Atspi', '2.0')
from gi.repository import Atspi
from . import debug
from . import keybindings
from . import orca_state
from . import settings_manager
class OrcaModifierManager:
"""Manages the Orca modifier."""
def __init__(self):
self._grabbed_modifiers = {}
# Related to hacks which will soon die.
self._original_xmodmap = ""
self._caps_lock_cleared = False
self._need_to_restore_orca_modifier = False
@staticmethod
def is_orca_modifier(modifier):
"""Returns True if modifier is one of the user's Orca modifier keys."""
return modifier in settings_manager.getManager().getSetting("orcaModifierKeys")
def is_modifier_grabbed(self, modifier):
"""Returns True if there is an existing grab for modifier."""
return modifier in self._grabbed_modifiers
def add_grabs_for_orca_modifiers(self):
"""Adds grabs for all of the user's Orca modifier keys."""
for modifier in settings_manager.getManager().getSetting("orcaModifierKeys"):
self.add_modifier_grab(modifier)
def remove_grabs_for_orca_modifiers(self):
"""Remove grabs for all of the user's Orca modifier keys."""
for modifier in settings_manager.getManager().getSetting("orcaModifierKeys"):
self.remove_modifier_grab(modifier)
def add_modifier_grab(self, modifier):
"""Adds a grab for modifier."""
if modifier in self._grabbed_modifiers:
return
if orca_state.device is None:
msg = "WARNING: Attempting to add modifier grabs without a device."
debug.printMessage(debug.LEVEL_WARNING, msg, True, True)
return
kd = Atspi.KeyDefinition()
kd.keycode = keybindings.getKeycode(modifier)
kd.modifiers = 0
self._grabbed_modifiers[modifier] = orca_state.device.add_key_grab(kd)
def remove_modifier_grab(self, modifier):
"""Removes the grab for modifier."""
if modifier not in self._grabbed_modifiers:
return
if orca_state.device is None:
msg = "WARNING: Attempting to remove modifier grabs without a device."
debug.printMessage(debug.LEVEL_WARNING, msg, True, True)
return
orca_state.device.remove_key_grab(self._grabbed_modifiers[modifier])
del self._grabbed_modifiers[modifier]
def refresh_orca_modifiers(self, reason=""):
"""Refreshes the Orca modifier keys."""
msg = "ORCA MODIFIER MANAGER: Refreshing Orca modifiers"
if reason:
msg += f": {reason}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
self.unset_orca_modifiers(reason)
self._original_xmodmap = subprocess.check_output(['xkbcomp', os.environ['DISPLAY'], '-'])
self._create_orca_xmodmap()
def update_key_map(self, keyboard_event):
"""Unsupported convenience method to call sad hacks which should go away."""
# TODO - JD: The only caller of this function is EventManager._processKeyboardEvent
msg = "ORCA MODIFIER MANAGER: Updating key map"
debug.printMessage(debug.LEVEL_INFO, msg, True)
if self.is_orca_modifier(keyboard_event.event_string) and orca_state.bypassNextCommand:
self.unset_orca_modifiers()
self._need_to_restore_orca_modifier = True
return
if self._need_to_restore_orca_modifier and not orca_state.bypassNextCommand:
self._create_orca_xmodmap()
self._need_to_restore_orca_modifier = False
def _create_orca_xmodmap(self):
"""Makes an Orca-specific Xmodmap so that the Orca modifier works."""
msg = "ORCA MODIFIER MANAGER: Creating Orca xmodmap"
debug.printMessage(debug.LEVEL_INFO, msg, True)
if self.is_orca_modifier("Caps_Lock") or self.is_orca_modifier("Shift_Lock"):
self.set_caps_lock_as_orca_modifier(True)
self._caps_lock_cleared = True
elif self._caps_lock_cleared:
self.set_caps_lock_as_orca_modifier(False)
self._caps_lock_cleared = False
def unset_orca_modifiers(self, reason=""):
"""Turns the Orca modifiers back into their original purpose."""
msg = "ORCA MODIFIER MANAGER: Attempting to restore original xmodmap"
if reason:
msg += f": {reason}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
if not self._original_xmodmap:
msg = "ORCA MODIFIER MANAGER: No stored xmodmap found"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return
self._caps_lock_cleared = False
p = subprocess.Popen(['xkbcomp', '-w0', '-', os.environ['DISPLAY']],
stdin=subprocess.PIPE, stdout=None, stderr=None)
p.communicate(self._original_xmodmap)
msg = "ORCA MODIFIER MANAGER: Original xmodmap restored"
debug.printMessage(debug.LEVEL_INFO, msg, True)
def set_caps_lock_as_orca_modifier(self, enable):
"""Enable or disable use of the caps lock key as an Orca modifier key."""
msg = "ORCA MODIFIER MANAGER: Setting caps lock as the Orca modifier"
debug.printMessage(debug.LEVEL_INFO, msg, True)
interpret_caps_line_prog = re.compile(
r'^\s*interpret\s+Caps[_+]Lock[_+]AnyOfOrNone\s*\(all\)\s*{\s*$', re.I)
normal_caps_line_prog = re.compile(
r'^\s*action\s*=\s*LockMods\s*\(\s*modifiers\s*=\s*Lock\s*\)\s*;\s*$', re.I)
interpret_shift_line_prog = re.compile(
r'^\s*interpret\s+Shift[_+]Lock[_+]AnyOf\s*\(\s*Shift\s*\+\s*Lock\s*\)\s*{\s*$', re.I)
normal_shift_line_prog = re.compile(
r'^\s*action\s*=\s*LockMods\s*\(\s*modifiers\s*=\s*Shift\s*\)\s*;\s*$', re.I)
disabled_mod_line_prog = re.compile(
r'^\s*action\s*=\s*NoAction\s*\(\s*\)\s*;\s*$', re.I)
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:
if enable:
if normal_caps_line_prog.match(line):
lines[i] = disabled_mod_line
modified = True
else:
if disabled_mod_line_prog.match(line):
lines[i] = normal_caps_line
modified = True
if line.find('}'):
found_caps_interpret_section = False
elif found_shift_interpret_section:
if enable:
if normal_shift_line_prog.match(line):
lines[i] = disabled_mod_line
modified = True
else:
if disabled_mod_line_prog.match(line):
lines[i] = normal_shift_line
modified = True
if line.find('}'):
found_shift_interpret_section = False
if modified:
msg = "ORCA MODIFIER MANAGER: Updating xmodmap"
debug.printMessage(debug.LEVEL_INFO, msg, True)
p = subprocess.Popen(['xkbcomp', '-w0', '-', os.environ['DISPLAY']],
stdin=subprocess.PIPE, stdout=None, stderr=None)
p.communicate(bytes('\n'.join(lines), 'UTF-8'))
else:
msg = "ORCA MODIFIER MANAGER: Not updating xmodmap"
debug.printMessage(debug.LEVEL_INFO, msg, True)
_manager = OrcaModifierManager()
def getManager():
return _manager