mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-06-18 16:03:12 +03:00
hotkey: Basic conversion to Abstract class, and implementation per platform
This commit is contained in:
parent
8aa0337ff4
commit
e0a3f1ad53
109
hotkey.py
109
hotkey.py
@ -1,9 +1,11 @@
|
|||||||
"""Handle keyboard input for manual update triggering."""
|
"""Handle keyboard input for manual update triggering."""
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import abc
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import sys
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from sys import platform
|
from abc import abstractmethod
|
||||||
from typing import Optional, Tuple, Union
|
from typing import Optional, Tuple, Union
|
||||||
|
|
||||||
from config import config
|
from config import config
|
||||||
@ -11,7 +13,32 @@ from EDMCLogging import get_main_logger
|
|||||||
|
|
||||||
logger = get_main_logger()
|
logger = get_main_logger()
|
||||||
|
|
||||||
if platform == 'darwin': # noqa: C901
|
|
||||||
|
class AbstractHotkeyMgr(abc.abstractmethod):
|
||||||
|
"""Abstract root class of all platforms specific HotKeyMgr."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def register(self, root, keycode, modifiers) -> None:
|
||||||
|
"""Register the hotkey handler."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def unregister(self) -> None:
|
||||||
|
"""Unregister the hotkey handling."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def play_good(self) -> None:
|
||||||
|
"""Play the 'good' sound."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def play_bad(self) -> None:
|
||||||
|
"""Play the 'bad' sound."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
|
||||||
import objc
|
import objc
|
||||||
from AppKit import (
|
from AppKit import (
|
||||||
@ -20,7 +47,8 @@ if platform == 'darwin': # noqa: C901
|
|||||||
NSFlagsChanged, NSKeyDown, NSKeyDownMask, NSKeyUp, NSNumericPadKeyMask, NSShiftKeyMask, NSSound, NSWorkspace
|
NSFlagsChanged, NSKeyDown, NSKeyDownMask, NSKeyUp, NSNumericPadKeyMask, NSShiftKeyMask, NSSound, NSWorkspace
|
||||||
)
|
)
|
||||||
|
|
||||||
class HotkeyMgr:
|
|
||||||
|
class MacHotkeyMgr(AbstractHotkeyMgr):
|
||||||
"""Hot key management."""
|
"""Hot key management."""
|
||||||
|
|
||||||
MODIFIERMASK = NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask | NSNumericPadKeyMask
|
MODIFIERMASK = NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask | NSNumericPadKeyMask
|
||||||
@ -46,7 +74,7 @@ if platform == 'darwin': # noqa: C901
|
|||||||
self.observer = None
|
self.observer = None
|
||||||
|
|
||||||
self.acquire_key = 0
|
self.acquire_key = 0
|
||||||
self.acquire_state = HotkeyMgr.ACQUIRE_INACTIVE
|
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||||
|
|
||||||
self.tkProcessKeyEvent_old = None
|
self.tkProcessKeyEvent_old = None
|
||||||
|
|
||||||
@ -73,7 +101,7 @@ if platform == 'darwin': # noqa: C901
|
|||||||
if keycode:
|
if keycode:
|
||||||
if not self.observer:
|
if not self.observer:
|
||||||
self.root.after_idle(self._observe)
|
self.root.after_idle(self._observe)
|
||||||
self.root.after(HotkeyMgr.POLL, self._poll)
|
self.root.after(MacHotkeyMgr.POLL, self._poll)
|
||||||
|
|
||||||
# Monkey-patch tk (tkMacOSXKeyEvent.c)
|
# Monkey-patch tk (tkMacOSXKeyEvent.c)
|
||||||
if not self.tkProcessKeyEvent_old:
|
if not self.tkProcessKeyEvent_old:
|
||||||
@ -103,7 +131,7 @@ if platform == 'darwin': # noqa: C901
|
|||||||
if self.acquire_state:
|
if self.acquire_state:
|
||||||
if the_event.type() == NSFlagsChanged:
|
if the_event.type() == NSFlagsChanged:
|
||||||
self.acquire_key = the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask
|
self.acquire_key = the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask
|
||||||
self.acquire_state = HotkeyMgr.ACQUIRE_NEW
|
self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW
|
||||||
# suppress the event by not chaining the old function
|
# suppress the event by not chaining the old function
|
||||||
return the_event
|
return the_event
|
||||||
|
|
||||||
@ -111,7 +139,7 @@ if platform == 'darwin': # noqa: C901
|
|||||||
c = the_event.charactersIgnoringModifiers()
|
c = the_event.charactersIgnoringModifiers()
|
||||||
self.acquire_key = (c and ord(c[0]) or 0) | \
|
self.acquire_key = (c and ord(c[0]) or 0) | \
|
||||||
(the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask)
|
(the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask)
|
||||||
self.acquire_state = HotkeyMgr.ACQUIRE_NEW
|
self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW
|
||||||
# suppress the event by not chaining the old function
|
# suppress the event by not chaining the old function
|
||||||
return the_event
|
return the_event
|
||||||
|
|
||||||
@ -147,7 +175,7 @@ if platform == 'darwin': # noqa: C901
|
|||||||
self.root.event_generate('<<Invoke>>', when="tail")
|
self.root.event_generate('<<Invoke>>', when="tail")
|
||||||
|
|
||||||
if self.keycode or self.modifiers:
|
if self.keycode or self.modifiers:
|
||||||
self.root.after(HotkeyMgr.POLL, self._poll)
|
self.root.after(MacHotkeyMgr.POLL, self._poll)
|
||||||
|
|
||||||
def unregister(self) -> None:
|
def unregister(self) -> None:
|
||||||
"""Remove hotkey registration."""
|
"""Remove hotkey registration."""
|
||||||
@ -157,7 +185,7 @@ if platform == 'darwin': # noqa: C901
|
|||||||
@objc.callbackFor(NSEvent.addGlobalMonitorForEventsMatchingMask_handler_)
|
@objc.callbackFor(NSEvent.addGlobalMonitorForEventsMatchingMask_handler_)
|
||||||
def _handler(self, event) -> None:
|
def _handler(self, event) -> None:
|
||||||
# use event.charactersIgnoringModifiers to handle composing characters like Alt-e
|
# use event.charactersIgnoringModifiers to handle composing characters like Alt-e
|
||||||
if ((event.modifierFlags() & HotkeyMgr.MODIFIERMASK) == self.modifiers
|
if ((event.modifierFlags() & MacHotkeyMgr.MODIFIERMASK) == self.modifiers
|
||||||
and ord(event.charactersIgnoringModifiers()[0]) == self.keycode):
|
and ord(event.charactersIgnoringModifiers()[0]) == self.keycode):
|
||||||
if config.get_int('hotkey_always'):
|
if config.get_int('hotkey_always'):
|
||||||
self.activated = True
|
self.activated = True
|
||||||
@ -169,12 +197,12 @@ if platform == 'darwin': # noqa: C901
|
|||||||
|
|
||||||
def acquire_start(self) -> None:
|
def acquire_start(self) -> None:
|
||||||
"""Start acquiring hotkey state via polling."""
|
"""Start acquiring hotkey state via polling."""
|
||||||
self.acquire_state = HotkeyMgr.ACQUIRE_ACTIVE
|
self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE
|
||||||
self.root.after_idle(self._acquire_poll)
|
self.root.after_idle(self._acquire_poll)
|
||||||
|
|
||||||
def acquire_stop(self) -> None:
|
def acquire_stop(self) -> None:
|
||||||
"""Stop acquiring hotkey state."""
|
"""Stop acquiring hotkey state."""
|
||||||
self.acquire_state = HotkeyMgr.ACQUIRE_INACTIVE
|
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||||
|
|
||||||
def _acquire_poll(self) -> None:
|
def _acquire_poll(self) -> None:
|
||||||
"""Perform a poll of current hotkey state."""
|
"""Perform a poll of current hotkey state."""
|
||||||
@ -184,10 +212,10 @@ if platform == 'darwin': # noqa: C901
|
|||||||
# No way of signalling to Tkinter from within the monkey-patched event handler that doesn't
|
# No way of signalling to Tkinter from within the monkey-patched event handler that doesn't
|
||||||
# cause Python to crash, so poll.
|
# cause Python to crash, so poll.
|
||||||
if self.acquire_state:
|
if self.acquire_state:
|
||||||
if self.acquire_state == HotkeyMgr.ACQUIRE_NEW:
|
if self.acquire_state == MacHotkeyMgr.ACQUIRE_NEW:
|
||||||
# Abuse tkEvent's keycode field to hold our acquired key & modifier
|
# Abuse tkEvent's keycode field to hold our acquired key & modifier
|
||||||
self.root.event_generate('<KeyPress>', keycode=self.acquire_key)
|
self.root.event_generate('<KeyPress>', keycode=self.acquire_key)
|
||||||
self.acquire_state = HotkeyMgr.ACQUIRE_ACTIVE
|
self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE
|
||||||
self.root.after(50, self._acquire_poll)
|
self.root.after(50, self._acquire_poll)
|
||||||
|
|
||||||
def fromevent(self, event) -> Optional[Union[bool, Tuple]]:
|
def fromevent(self, event) -> Optional[Union[bool, Tuple]]:
|
||||||
@ -201,18 +229,18 @@ if platform == 'darwin': # noqa: C901
|
|||||||
if (keycode
|
if (keycode
|
||||||
and not (modifiers & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask))):
|
and not (modifiers & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask))):
|
||||||
if keycode == 0x1b: # Esc = retain previous
|
if keycode == 0x1b: # Esc = retain previous
|
||||||
self.acquire_state = HotkeyMgr.ACQUIRE_INACTIVE
|
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# BkSp, Del, Clear = clear hotkey
|
# BkSp, Del, Clear = clear hotkey
|
||||||
elif keycode in [0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)]:
|
elif keycode in [0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)]:
|
||||||
self.acquire_state = HotkeyMgr.ACQUIRE_INACTIVE
|
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# don't allow keys needed for typing in System Map
|
# don't allow keys needed for typing in System Map
|
||||||
elif keycode in [0x13, 0x20, 0x2d] or 0x61 <= keycode <= 0x7a:
|
elif keycode in [0x13, 0x20, 0x2d] or 0x61 <= keycode <= 0x7a:
|
||||||
NSBeep()
|
NSBeep()
|
||||||
self.acquire_state = HotkeyMgr.ACQUIRE_INACTIVE
|
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return (keycode, modifiers)
|
return (keycode, modifiers)
|
||||||
@ -247,8 +275,8 @@ if platform == 'darwin': # noqa: C901
|
|||||||
elif ord(NSF1FunctionKey) <= keycode <= ord(NSF35FunctionKey):
|
elif ord(NSF1FunctionKey) <= keycode <= ord(NSF35FunctionKey):
|
||||||
text += f'F{keycode + 1 - ord(NSF1FunctionKey)}'
|
text += f'F{keycode + 1 - ord(NSF1FunctionKey)}'
|
||||||
|
|
||||||
elif keycode in HotkeyMgr.DISPLAY: # specials
|
elif keycode in MacHotkeyMgr.DISPLAY: # specials
|
||||||
text += HotkeyMgr.DISPLAY[keycode]
|
text += MacHotkeyMgr.DISPLAY[keycode]
|
||||||
|
|
||||||
elif keycode < 0x20: # control keys
|
elif keycode < 0x20: # control keys
|
||||||
text += chr(keycode + 0x40)
|
text += chr(keycode + 0x40)
|
||||||
@ -270,7 +298,7 @@ if platform == 'darwin': # noqa: C901
|
|||||||
self.snd_bad.play()
|
self.snd_bad.play()
|
||||||
|
|
||||||
|
|
||||||
elif platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
import ctypes
|
import ctypes
|
||||||
@ -397,7 +425,8 @@ elif platform == 'win32':
|
|||||||
INPUT_KEYBOARD = 1
|
INPUT_KEYBOARD = 1
|
||||||
INPUT_HARDWARE = 2
|
INPUT_HARDWARE = 2
|
||||||
|
|
||||||
class HotkeyMgr:
|
|
||||||
|
class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||||
"""Hot key management."""
|
"""Hot key management."""
|
||||||
|
|
||||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx
|
||||||
@ -595,8 +624,8 @@ elif platform == 'win32':
|
|||||||
elif VK_F1 <= keycode <= VK_F24:
|
elif VK_F1 <= keycode <= VK_F24:
|
||||||
text += f'F{keycode + 1 - VK_F1}'
|
text += f'F{keycode + 1 - VK_F1}'
|
||||||
|
|
||||||
elif keycode in HotkeyMgr.DISPLAY: # specials
|
elif keycode in WindowsHotkeyMgr.DISPLAY: # specials
|
||||||
text += HotkeyMgr.DISPLAY[keycode]
|
text += WindowsHotkeyMgr.DISPLAY[keycode]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
c = MapVirtualKey(keycode, 2) # printable ?
|
c = MapVirtualKey(keycode, 2) # printable ?
|
||||||
@ -621,26 +650,34 @@ elif platform == 'win32':
|
|||||||
if self.thread:
|
if self.thread:
|
||||||
PostThreadMessage(self.thread.ident, WM_SND_BAD, 0, 0)
|
PostThreadMessage(self.thread.ident, WM_SND_BAD, 0, 0)
|
||||||
|
|
||||||
else: # Linux
|
|
||||||
|
|
||||||
class HotkeyMgr:
|
class LinuxHotKeyMgr(AbstractHotkeyMgr):
|
||||||
"""Hot key management."""
|
"""Hot key management."""
|
||||||
|
|
||||||
def register(self, root, keycode, modifiers) -> None:
|
|
||||||
"""Register the hotkey handler."""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def unregister(self) -> None:
|
|
||||||
"""Unregister the hotkey handling."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def play_good(self) -> None:
|
def get_hotkeymgr(*args, **kwargs) -> AbstractHotkeyMgr:
|
||||||
"""Play the 'good' sound."""
|
"""
|
||||||
pass
|
Determine platform-specific HotkeyMgr.
|
||||||
|
|
||||||
|
:param args:
|
||||||
|
:param kwargs:
|
||||||
|
:return: Appropriate class instance.
|
||||||
|
:raises ValueError: If unsupported platform.
|
||||||
|
"""
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
return MacHotkeyMgr(*args, **kwargs)
|
||||||
|
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
return WindowsHotkeyMgr(*args, **kwargs)
|
||||||
|
|
||||||
|
elif sys.platform == 'linux':
|
||||||
|
return LinuxHotKeyMgr(*args, **kwargs)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Unknown platform: {sys.platform}')
|
||||||
|
|
||||||
def play_bad(self) -> None:
|
|
||||||
"""Play the 'bad' sound."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
# singleton
|
# singleton
|
||||||
hotkeymgr = HotkeyMgr()
|
hotkeymgr = get_hotkeymgr()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user