Skip to content

Commit

Permalink
[1805] Implement win32con in Hotkey
Browse files Browse the repository at this point in the history
  • Loading branch information
Rixxan committed Jun 11, 2024
1 parent b1ba45a commit f8d354a
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 78 deletions.
42 changes: 17 additions & 25 deletions EDMarketConnector.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,23 +253,17 @@ def handle_edmc_callback_or_foregrounding() -> None: # noqa: CCR001
logger.trace_if('frontier-auth.windows', 'Begin...')

if sys.platform == 'win32':

# If *this* instance hasn't locked, then another already has and we
# now need to do the edmc:// checks for auth callback
if locked != JournalLockResult.LOCKED:
from ctypes import windll, c_int, create_unicode_buffer, WINFUNCTYPE
from ctypes.wintypes import BOOL, HWND, INT, LPARAM, LPCWSTR, LPWSTR

EnumWindows = windll.user32.EnumWindows # noqa: N806
GetClassName = windll.user32.GetClassNameW # noqa: N806
GetClassName.argtypes = [HWND, LPWSTR, c_int]
GetWindowText = windll.user32.GetWindowTextW # noqa: N806
GetWindowText.argtypes = [HWND, LPWSTR, c_int]
GetWindowTextLength = windll.user32.GetWindowTextLengthW # noqa: N806
from ctypes import windll, create_unicode_buffer, WINFUNCTYPE
from ctypes.wintypes import BOOL, HWND, LPARAM
import win32gui
import win32api

GetProcessHandleFromHwnd = windll.oleacc.GetProcessHandleFromHwnd # noqa: N806

SW_RESTORE = 9 # noqa: N806
SetForegroundWindow = windll.user32.SetForegroundWindow # noqa: N806
ShowWindow = windll.user32.ShowWindow # noqa: N806
ShowWindowAsync = windll.user32.ShowWindowAsync # noqa: N806

Expand All @@ -278,14 +272,11 @@ def handle_edmc_callback_or_foregrounding() -> None: # noqa: CCR001
COINIT_DISABLE_OLE1DDE = 0x4 # noqa: N806
CoInitializeEx = windll.ole32.CoInitializeEx # noqa: N806

ShellExecute = windll.shell32.ShellExecuteW # noqa: N806
ShellExecute.argtypes = [HWND, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, INT]

def window_title(h: int) -> str | None:
if h:
text_length = GetWindowTextLength(h) + 1
text_length = win32gui.GetWindowTextLength(h) + 1
buf = create_unicode_buffer(text_length)
if GetWindowText(h, buf, text_length):
if win32gui.GetWindowText(h):
return buf.value

return None
Expand All @@ -309,7 +300,7 @@ def enumwindowsproc(window_handle, l_param): # noqa: CCR001
# class name limited to 256 - https://msdn.microsoft.com/en-us/library/windows/desktop/ms633576
cls = create_unicode_buffer(257)
# This conditional is exploded to make debugging slightly easier
if GetClassName(window_handle, cls, 257):
if win32gui.GetClassName(window_handle, cls, 257):
if cls.value == 'TkTopLevel':
if window_title(window_handle) == applongname:
if GetProcessHandleFromHwnd(window_handle):
Expand All @@ -318,11 +309,11 @@ def enumwindowsproc(window_handle, l_param): # noqa: CCR001
CoInitializeEx(0, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
# Wait for it to be responsive to avoid ShellExecute recursing
ShowWindow(window_handle, SW_RESTORE)
ShellExecute(0, None, sys.argv[1], None, None, SW_RESTORE)
win32api.ShellExecute(0, None, sys.argv[1], None, None, SW_RESTORE)

else:
ShowWindowAsync(window_handle, SW_RESTORE)
SetForegroundWindow(window_handle)
win32gui.SetForegroundWindow(window_handle)

return False # Indicate window found, so stop iterating

Expand All @@ -334,7 +325,7 @@ def enumwindowsproc(window_handle, l_param): # noqa: CCR001
# enumwindwsproc() on each. When an invocation returns False it
# stops iterating.
# Ref: <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows>
EnumWindows(enumwindowsproc, 0)
win32gui.EnumWindows(enumwindowsproc, 0)

def already_running_popup():
"""Create the "already running" popup."""
Expand Down Expand Up @@ -701,13 +692,14 @@ def open_window(systray: 'SysTrayIcon') -> None:
if match:
if sys.platform == 'win32':
# Check that the titlebar will be at least partly on screen
import ctypes
from ctypes.wintypes import POINT
import win32api
import win32con

x = int(match.group(1)) + 16
y = int(match.group(2)) + 16
point = (x, y)
# https://msdn.microsoft.com/en-us/library/dd145064
MONITOR_DEFAULTTONULL = 0 # noqa: N806
if ctypes.windll.user32.MonitorFromPoint(POINT(int(match.group(1)) + 16, int(match.group(2)) + 16),
MONITOR_DEFAULTTONULL):
if win32api.MonitorFromPoint(point, win32con.MONITOR_DEFAULTTONULL):
self.w.geometry(config.get_str('geometry'))
else:
self.w.geometry(config.get_str('geometry'))
Expand Down
76 changes: 24 additions & 52 deletions hotkey/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import pywintypes
import win32api
import win32gui
import win32con
from config import config
from EDMCLogging import get_main_logger
from hotkey import AbstractHotkeyMgr
Expand All @@ -24,40 +25,10 @@
UnregisterHotKey.argtypes = [HWND, ctypes.c_int]
UnregisterHotKey.restype = BOOL

MOD_ALT = 0x0001
MOD_CONTROL = 0x0002
MOD_SHIFT = 0x0004
MOD_WIN = 0x0008
MOD_NOREPEAT = 0x4000

WM_QUIT = 0x0012
WM_HOTKEY = 0x0312
WM_APP = 0x8000
WM_SND_GOOD = WM_APP + 1
WM_SND_BAD = WM_APP + 2

VK_BACK = 0x08
VK_CLEAR = 0x0c
VK_RETURN = 0x0d
VK_SHIFT = 0x10
VK_CONTROL = 0x11
VK_MENU = 0x12
VK_CAPITAL = 0x14
VK_MODECHANGE = 0x1f
VK_ESCAPE = 0x1b
VK_SPACE = 0x20
VK_DELETE = 0x2e
VK_LWIN = 0x5b
VK_RWIN = 0x5c
VK_NUMPAD0 = 0x60
VK_DIVIDE = 0x6f
VK_F1 = 0x70
VK_F24 = 0x87
WM_SND_GOOD = win32con.WM_APP + 1
WM_SND_BAD = win32con.WM_APP + 2
VK_OEM_MINUS = 0xbd
VK_NUMLOCK = 0x90
VK_SCROLL = 0x91
VK_PROCESSKEY = 0xe5
VK_OEM_CLEAR = 0xfe

# VirtualKey mapping values
# <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mapvirtualkeyexa>
Expand Down Expand Up @@ -200,7 +171,7 @@ def unregister(self) -> None:
logger.debug('Thread is/was running')
self.thread = None # type: ignore
logger.debug('Telling thread WM_QUIT')
win32gui.PostThreadMessage(thread.ident, WM_QUIT, 0, 0)
win32gui.PostThreadMessage(thread.ident, win32con.WM_QUIT, 0, 0)
logger.debug('Joining thread')
thread.join() # Wait for it to unregister hotkey and quit

Expand All @@ -226,7 +197,7 @@ def worker(self, keycode, modifiers) -> None: # noqa: CCR001
logger.debug('Entering GetMessage() loop...')
while win32gui.GetMessage(ctypes.byref(msg), None, 0, 0) != 0:
logger.debug('Got message')
if msg.message == WM_HOTKEY:
if msg.message == win32con.WM_HOTKEY:
logger.debug('WM_HOTKEY')

if (
Expand Down Expand Up @@ -284,31 +255,32 @@ def fromevent(self, event) -> bool | tuple | None: # noqa: CCR001
:param event: tk event ?
:return: False to retain previous, None to not use, else (keycode, modifiers)
"""
modifiers = ((win32api.GetKeyState(VK_MENU) & 0x8000) and MOD_ALT) \
| ((win32api.GetKeyState(VK_CONTROL) & 0x8000) and MOD_CONTROL) \
| ((win32api.GetKeyState(VK_SHIFT) & 0x8000) and MOD_SHIFT) \
| ((win32api.GetKeyState(VK_LWIN) & 0x8000) and MOD_WIN) \
| ((win32api.GetKeyState(VK_RWIN) & 0x8000) and MOD_WIN)
modifiers = ((win32api.GetKeyState(win32con.VK_MENU) & 0x8000) and win32con.MOD_ALT) \
| ((win32api.GetKeyState(win32con.VK_CONTROL) & 0x8000) and win32con.MOD_CONTROL) \
| ((win32api.GetKeyState(win32con.VK_SHIFT) & 0x8000) and win32con.MOD_SHIFT) \
| ((win32api.GetKeyState(win32con.VK_LWIN) & 0x8000) and win32con.MOD_WIN) \
| ((win32api.GetKeyState(win32con.VK_RWIN) & 0x8000) and win32con.MOD_WIN)
keycode = event.keycode

if keycode in (VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, VK_RWIN):
if keycode in (win32con.VK_SHIFT, win32con.VK_CONTROL, win32con.VK_MENU, win32con.VK_LWIN, win32con.VK_RWIN):
return 0, modifiers

if not modifiers:
if keycode == VK_ESCAPE: # Esc = retain previous
if keycode == win32con.VK_ESCAPE: # Esc = retain previous
return False

if keycode in (VK_BACK, VK_DELETE, VK_CLEAR, VK_OEM_CLEAR): # BkSp, Del, Clear = clear hotkey
if keycode in (win32con.VK_BACK, win32con.VK_DELETE,
win32con.VK_CLEAR, win32con.VK_OEM_CLEAR): # BkSp, Del, Clear = clear hotkey
return None

if (
keycode in (VK_RETURN, VK_SPACE, VK_OEM_MINUS) or ord('A') <= keycode <= ord('Z')
keycode in (win32con.VK_RETURN, win32con.VK_SPACE, VK_OEM_MINUS) or ord('A') <= keycode <= ord('Z')
): # don't allow keys needed for typing in System Map
winsound.MessageBeep()
return None

if (keycode in (VK_NUMLOCK, VK_SCROLL, VK_PROCESSKEY)
or VK_CAPITAL <= keycode <= VK_MODECHANGE): # ignore unmodified mode switch keys
if (keycode in (win32con.VK_NUMLOCK, win32con.VK_SCROLL, win32con.VK_PROCESSKEY)
or win32con.VK_CAPITAL <= keycode <= win32con.VK_MODECHANGE): # ignore unmodified mode switch keys
return 0, modifiers

# See if the keycode is usable and available
Expand All @@ -329,26 +301,26 @@ def display(self, keycode, modifiers) -> str:
:return: string form
"""
text = ''
if modifiers & MOD_WIN:
if modifiers & win32con.MOD_WIN:
text += '❖+'

if modifiers & MOD_CONTROL:
if modifiers & win32con.MOD_CONTROL:
text += 'Ctrl+'

if modifiers & MOD_ALT:
if modifiers & win32con.MOD_ALT:
text += 'Alt+'

if modifiers & MOD_SHIFT:
if modifiers & win32con.MOD_SHIFT:
text += '⇧+'

if VK_NUMPAD0 <= keycode <= VK_DIVIDE:
if win32con.VK_NUMPAD0 <= keycode <= win32con.VK_DIVIDE:
text += '№'

if not keycode:
pass

elif VK_F1 <= keycode <= VK_F24:
text += f'F{keycode + 1 - VK_F1}'
elif win32con.VK_F1 <= keycode <= win32con.VK_F24:
text += f'F{keycode + 1 - win32con.VK_F1}'

elif keycode in WindowsHotkeyMgr.DISPLAY: # specials
text += WindowsHotkeyMgr.DISPLAY[keycode]
Expand Down
2 changes: 1 addition & 1 deletion monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2127,7 +2127,7 @@ def WindowTitle(h): # noqa: N802
if h:
length = win32gui.GetWindowTextLength(h) + 1
buf = ctypes.create_unicode_buffer(length)
if win32gui.GetWindowText(h, buf, length):
if win32gui.GetWindowText(h):
return buf.value
return None

Expand Down

0 comments on commit f8d354a

Please sign in to comment.