From 7dbf150846c2d4f539fde6e6ce3e2aef03341c21 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 7 Apr 2021 16:22:42 +0100 Subject: [PATCH] hotkey: flake8 lints for darwin --- hotkey.py | 196 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 140 insertions(+), 56 deletions(-) diff --git a/hotkey.py b/hotkey.py index cd451b43..bb26f5ab 100644 --- a/hotkey.py +++ b/hotkey.py @@ -1,12 +1,18 @@ """Handle keyboard input for manual update triggering.""" # -*- coding: utf-8 -*- -from os.path import join +import pathlib from sys import platform +from typing import TYPE_CHECKING, Optional, Tuple, Union from config import config from EDMCLogging import get_main_logger +# isort: off +if TYPE_CHECKING: + import tkinter as tk +# isort: on + logger = get_main_logger() if platform == 'darwin': # noqa: C901 @@ -19,17 +25,19 @@ if platform == 'darwin': # noqa: C901 ) class HotkeyMgr: + """Hot key management.""" - MODIFIERMASK = NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask|NSNumericPadKeyMask + MODIFIERMASK = NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask | NSNumericPadKeyMask POLL = 250 # https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSEvent_Class/#//apple_ref/doc/constant_group/Function_Key_Unicodes - DISPLAY = { 0x03: u'⌅', 0x09: u'⇥', 0xd: u'↩', 0x19: u'⇤', 0x1b: u'esc', 0x20: u'⏘', 0x7f: u'⌫', - 0xf700: u'↑', 0xf701: u'↓', 0xf702: u'←', 0xf703: u'→', - 0xf727: u'Ins', - 0xf728: u'⌦', 0xf729: u'↖', 0xf72a: u'Fn', 0xf72b: u'↘', - 0xf72c: u'⇞', 0xf72d: u'⇟', 0xf72e: u'PrtScr', 0xf72f: u'ScrollLock', - 0xf730: u'Pause', 0xf731: u'SysReq', 0xf732: u'Break', 0xf733: u'Reset', - 0xf739: u'⌧', + DISPLAY = { + 0x03: u'⌅', 0x09: u'⇥', 0xd: u'↩', 0x19: u'⇤', 0x1b: u'esc', 0x20: u'⏘', 0x7f: u'⌫', + 0xf700: u'↑', 0xf701: u'↓', 0xf702: u'←', 0xf703: u'→', + 0xf727: u'Ins', + 0xf728: u'⌦', 0xf729: u'↖', 0xf72a: u'Fn', 0xf72b: u'↘', + 0xf72c: u'⇞', 0xf72d: u'⇟', 0xf72e: u'PrtScr', 0xf72f: u'ScrollLock', + 0xf730: u'Pause', 0xf731: u'SysReq', 0xf732: u'Break', 0xf733: u'Reset', + 0xf739: u'⌧', } (ACQUIRE_INACTIVE, ACQUIRE_ACTIVE, ACQUIRE_NEW) = range(3) @@ -46,10 +54,21 @@ if platform == 'darwin': # noqa: C901 self.tkProcessKeyEvent_old = None - self.snd_good = NSSound.alloc().initWithContentsOfFile_byReference_(join(config.respath_path, 'snd_good.wav'), False) - self.snd_bad = NSSound.alloc().initWithContentsOfFile_byReference_(join(config.respath_path, 'snd_bad.wav'), False) + self.snd_good = NSSound.alloc().initWithContentsOfFile_byReference_( + pathlib.Path(config.respath_path) / 'snd_good.wav', False + ) + self.snd_bad = NSSound.alloc().initWithContentsOfFile_byReference_( + pathlib.Path(config.respath_path) / 'snd_bad.wav', False + ) - def register(self, root, keycode, modifiers): + def register(self, root: tk.Tk, keycode, modifiers) -> None: + """ + Register current hotkey for monitoring. + + :param root: parent window. + :param keycode: Key to monitor. + :param modifiers: Any modifiers to take into account. + """ self.root = root self.keycode = keycode self.modifiers = modifiers @@ -65,32 +84,56 @@ if platform == 'darwin': # noqa: C901 sel = b'tkProcessKeyEvent:' cls = NSApplication.sharedApplication().class__() self.tkProcessKeyEvent_old = NSApplication.sharedApplication().methodForSelector_(sel) - newmethod = objc.selector(self.tkProcessKeyEvent, selector = self.tkProcessKeyEvent_old.selector, signature = self.tkProcessKeyEvent_old.signature) + newmethod = objc.selector( + self.tkProcessKeyEvent, + selector=self.tkProcessKeyEvent_old.selector, + signature=self.tkProcessKeyEvent_old.signature + ) objc.classAddMethod(cls, sel, newmethod) - # Monkey-patch tk (tkMacOSXKeyEvent.c) to: - # - workaround crash on OSX 10.9 & 10.10 on seeing a composing character - # - notice when modifier key state changes - # - keep a copy of NSEvent.charactersIgnoringModifiers, which is what we need for the hotkey - # (Would like to use a decorator but need to ensure the application is created before this is installed) - def tkProcessKeyEvent(self, cls, theEvent): + def tkProcessKeyEvent(self, cls, the_event): # noqa: N802 + """ + Monkey-patch tk (tkMacOSXKeyEvent.c). + + - workaround crash on OSX 10.9 & 10.10 on seeing a composing character + - notice when modifier key state changes + - keep a copy of NSEvent.charactersIgnoringModifiers, which is what we need for the hotkey + + (Would like to use a decorator but need to ensure the application is created before this is installed) + :param cls: ??? + :param the_event: tk event + :return: ??? + """ if self.acquire_state: - if theEvent.type() == NSFlagsChanged: - self.acquire_key = theEvent.modifierFlags() & NSDeviceIndependentModifierFlagsMask + if the_event.type() == NSFlagsChanged: + self.acquire_key = the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask self.acquire_state = HotkeyMgr.ACQUIRE_NEW # suppress the event by not chaining the old function - return theEvent - elif theEvent.type() in (NSKeyDown, NSKeyUp): - c = theEvent.charactersIgnoringModifiers() - self.acquire_key = (c and ord(c[0]) or 0) | (theEvent.modifierFlags() & NSDeviceIndependentModifierFlagsMask) + return the_event + + elif the_event.type() in (NSKeyDown, NSKeyUp): + c = the_event.charactersIgnoringModifiers() + self.acquire_key = (c and ord(c[0]) or 0) | \ + (the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask) self.acquire_state = HotkeyMgr.ACQUIRE_NEW # suppress the event by not chaining the old function - return theEvent + return the_event # replace empty characters with charactersIgnoringModifiers to avoid crash - elif theEvent.type() in (NSKeyDown, NSKeyUp) and not theEvent.characters(): - theEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(theEvent.type(), theEvent.locationInWindow(), theEvent.modifierFlags(), theEvent.timestamp(), theEvent.windowNumber(), theEvent.context(), theEvent.charactersIgnoringModifiers(), theEvent.charactersIgnoringModifiers(), theEvent.isARepeat(), theEvent.keyCode()) - return self.tkProcessKeyEvent_old(cls, theEvent) + elif the_event.type() in (NSKeyDown, NSKeyUp) and not the_event.characters(): + the_event = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_( # noqa: E501 + the_event.type(), + the_event.locationInWindow(), + the_event.modifierFlags(), + the_event.timestamp(), + the_event.windowNumber(), + the_event.context(), + the_event.charactersIgnoringModifiers(), + the_event.charactersIgnoringModifiers(), + the_event.isARepeat(), + the_event.keyCode() + ) + return self.tkProcessKeyEvent_old(cls, the_event) def _observe(self): # Must be called after root.mainloop() so that the app's message loop has been created @@ -109,29 +152,35 @@ if platform == 'darwin': # noqa: C901 if self.keycode or self.modifiers: self.root.after(HotkeyMgr.POLL, self._poll) - def unregister(self): + def unregister(self) -> None: + """Remove hotkey registration.""" self.keycode = None self.modifiers = None @objc.callbackFor(NSEvent.addGlobalMonitorForEventsMatchingMask_handler_) - def _handler(self, event): + def _handler(self, event) -> None: # use event.charactersIgnoringModifiers to handle composing characters like Alt-e - if (event.modifierFlags() & HotkeyMgr.MODIFIERMASK) == self.modifiers and ord(event.charactersIgnoringModifiers()[0]) == self.keycode: + if ((event.modifierFlags() & HotkeyMgr.MODIFIERMASK) == self.modifiers + and ord(event.charactersIgnoringModifiers()[0]) == self.keycode): if config.get_int('hotkey_always'): self.activated = True - else: # Only trigger if game client is front process + + else: # Only trigger if game client is front process front = NSWorkspace.sharedWorkspace().frontmostApplication() if front and front.bundleIdentifier() == 'uk.co.frontier.EliteDangerous': self.activated = True - def acquire_start(self): + def acquire_start(self) -> None: + """Start acquiring hotkey state via polling.""" self.acquire_state = HotkeyMgr.ACQUIRE_ACTIVE self.root.after_idle(self._acquire_poll) - def acquire_stop(self): + def acquire_stop(self) -> None: + """Stop acquiring hotkey state.""" self.acquire_state = HotkeyMgr.ACQUIRE_INACTIVE - def _acquire_poll(self): + def _acquire_poll(self) -> None: + """Perform a poll of current hotkey state.""" if config.shutting_down: return @@ -140,52 +189,87 @@ if platform == 'darwin': # noqa: C901 if self.acquire_state: if self.acquire_state == HotkeyMgr.ACQUIRE_NEW: # Abuse tkEvent's keycode field to hold our acquired key & modifier - self.root.event_generate('', keycode = self.acquire_key) + self.root.event_generate('', keycode=self.acquire_key) self.acquire_state = HotkeyMgr.ACQUIRE_ACTIVE self.root.after(50, self._acquire_poll) - def fromevent(self, event): - # Return configuration (keycode, modifiers) or None=clear or False=retain previous - (keycode, modifiers) = (event.keycode & 0xffff, event.keycode & 0xffff0000) # Set by _acquire_poll() - if keycode and not (modifiers & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask)): + def fromevent(self, event) -> Optional[Union[bool, Tuple]]: + """ + Return configuration (keycode, modifiers) or None=clear or False=retain previous. + + :param event: tk event ? + :return: False to retain previous, None to not use, else (keycode, modifiers) + """ + (keycode, modifiers) = (event.keycode & 0xffff, event.keycode & 0xffff0000) # Set by _acquire_poll() + if (keycode + and not (modifiers & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask))): if keycode == 0x1b: # Esc = retain previous self.acquire_state = HotkeyMgr.ACQUIRE_INACTIVE return False - elif keycode in [0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)]: # BkSp, Del, Clear = clear hotkey + + # BkSp, Del, Clear = clear hotkey + elif keycode in [0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)]: self.acquire_state = HotkeyMgr.ACQUIRE_INACTIVE return None - elif keycode in [0x13, 0x20, 0x2d] or 0x61 <= keycode <= 0x7a: # 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: NSBeep() self.acquire_state = HotkeyMgr.ACQUIRE_INACTIVE return None + return (keycode, modifiers) - def display(self, keycode, modifiers): - # Return displayable form + def display(self, keycode, modifiers) -> str: + """ + Return displayable form of given hotkey + modifiers. + + :param keycode: + :param modifiers: + :return: string form + """ text = '' - if modifiers & NSControlKeyMask: text += u'⌃' - if modifiers & NSAlternateKeyMask: text += u'⌥' - if modifiers & NSShiftKeyMask: text += u'⇧' - if modifiers & NSCommandKeyMask: text += u'⌘' - if (modifiers & NSNumericPadKeyMask) and keycode <= 0x7f: text += u'№' + if modifiers & NSControlKeyMask: + text += u'⌃' + + if modifiers & NSAlternateKeyMask: + text += u'⌥' + + if modifiers & NSShiftKeyMask: + text += u'⇧' + + if modifiers & NSCommandKeyMask: + text += u'⌘' + + if (modifiers & NSNumericPadKeyMask) and keycode <= 0x7f: + text += u'№' + if not keycode: pass + elif ord(NSF1FunctionKey) <= keycode <= ord(NSF35FunctionKey): - text += 'F%d' % (keycode + 1 - ord(NSF1FunctionKey)) - elif keycode in HotkeyMgr.DISPLAY: # specials + text += f'F{keycode + 1 - ord(NSF1FunctionKey)}' + + elif keycode in HotkeyMgr.DISPLAY: # specials text += HotkeyMgr.DISPLAY[keycode] - elif keycode < 0x20: # control keys + + elif keycode < 0x20: # control keys text += chr(keycode+0x40) - elif keycode < 0xf700: # key char + + elif keycode < 0xf700: # key char text += chr(keycode).upper() + else: text += u'⁈' + return text def play_good(self): + """Play the 'good' sound.""" self.snd_good.play() def play_bad(self): + """Play the 'bad' sound.""" self.snd_bad.play() @@ -295,9 +379,9 @@ elif platform == 'win32': def __init__(self): self.root = None self.thread = None - with open(join(config.respath, 'snd_good.wav'), 'rb') as sg: + with open(pathlib.Path(config.respath) / 'snd_good.wav', 'rb') as sg: self.snd_good = sg.read() - with open(join(config.respath, 'snd_bad.wav'), 'rb') as sb: + with open(pathlib.Path(config.respath) / 'snd_bad.wav', 'rb') as sb: self.snd_bad = sb.read() atexit.register(self.unregister)