1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-12 23:37:14 +03:00

[#1805] pywin32 Handoff

This commit is contained in:
David Sangrey 2024-06-10 23:00:44 -04:00
parent b10548da57
commit 17a7af959a
No known key found for this signature in database
GPG Key ID: 3AEADBB0186884BC
8 changed files with 68 additions and 96 deletions

View File

@ -7,41 +7,23 @@ See LICENSE file.
""" """
from __future__ import annotations from __future__ import annotations
import ctypes
import functools import functools
import pathlib import pathlib
import sys import sys
import uuid import uuid
import winreg import winreg
from ctypes.wintypes import DWORD, HANDLE
from typing import Literal from typing import Literal
from config import AbstractConfig, applongname, appname, logger from config import AbstractConfig, applongname, appname, logger
from win32comext.shell import shell
assert sys.platform == 'win32' assert sys.platform == 'win32'
REG_RESERVED_ALWAYS_ZERO = 0 REG_RESERVED_ALWAYS_ZERO = 0
# This is the only way to do this from python without external deps (which do this anyway).
FOLDERID_Documents = uuid.UUID('{FDD39AD0-238F-46AF-ADB4-6C85480369C7}')
FOLDERID_LocalAppData = uuid.UUID('{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}')
FOLDERID_Profile = uuid.UUID('{5E6C858F-0E22-4760-9AFE-EA3317B67173}')
FOLDERID_SavedGames = uuid.UUID('{4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4}')
SHGetKnownFolderPath = ctypes.windll.shell32.SHGetKnownFolderPath
SHGetKnownFolderPath.argtypes = [ctypes.c_char_p, DWORD, HANDLE, ctypes.POINTER(ctypes.c_wchar_p)]
CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree
CoTaskMemFree.argtypes = [ctypes.c_void_p]
def known_folder_path(guid: uuid.UUID) -> str | None: def known_folder_path(guid: uuid.UUID) -> str | None:
"""Look up a Windows GUID to actual folder path name.""" """Look up a Windows GUID to actual folder path name."""
buf = ctypes.c_wchar_p() return shell.SHGetKnownFolderPath(guid, 0, 0)
if SHGetKnownFolderPath(ctypes.create_string_buffer(guid.bytes_le), 0, 0, ctypes.byref(buf)):
return None
retval = buf.value # copy data
CoTaskMemFree(buf) # and free original
return retval
class WinConfig(AbstractConfig): class WinConfig(AbstractConfig):
@ -49,7 +31,8 @@ class WinConfig(AbstractConfig):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self.app_dir_path = pathlib.Path(known_folder_path(FOLDERID_LocalAppData)) / appname # type: ignore if local_appdata := known_folder_path(shell.FOLDERID_LocalAppData):
self.app_dir_path = pathlib.Path(local_appdata) / appname
self.app_dir_path.mkdir(exist_ok=True) self.app_dir_path.mkdir(exist_ok=True)
self.plugin_dir_path = self.app_dir_path / 'plugins' self.plugin_dir_path = self.app_dir_path / 'plugins'
@ -65,7 +48,7 @@ class WinConfig(AbstractConfig):
self.home_path = pathlib.Path.home() self.home_path = pathlib.Path.home()
journal_dir_path = pathlib.Path( journal_dir_path = pathlib.Path(
known_folder_path(FOLDERID_SavedGames)) / 'Frontier Developments' / 'Elite Dangerous' # type: ignore known_folder_path(shell.FOLDERID_SavedGames)) / 'Frontier Developments' / 'Elite Dangerous' # type: ignore
self.default_journal_dir_path = journal_dir_path if journal_dir_path.is_dir() else None # type: ignore self.default_journal_dir_path = journal_dir_path if journal_dir_path.is_dir() else None # type: ignore
REGISTRY_SUBKEY = r'Software\Marginal\EDMarketConnector' # noqa: N806 REGISTRY_SUBKEY = r'Software\Marginal\EDMarketConnector' # noqa: N806
@ -84,7 +67,7 @@ class WinConfig(AbstractConfig):
self.identifier = applongname self.identifier = applongname
if (outdir_str := self.get_str('outdir')) is None or not pathlib.Path(outdir_str).is_dir(): if (outdir_str := self.get_str('outdir')) is None or not pathlib.Path(outdir_str).is_dir():
docs = known_folder_path(FOLDERID_Documents) docs = known_folder_path(shell.FOLDERID_Documents)
self.set("outdir", docs if docs is not None else self.home) self.set("outdir", docs if docs is not None else self.home)
def __get_regentry(self, key: str) -> None | list | str | int: def __get_regentry(self, key: str) -> None | list | str | int:

View File

@ -8,7 +8,10 @@ import sys
import threading import threading
import tkinter as tk import tkinter as tk
import winsound import winsound
from ctypes.wintypes import DWORD, HWND, LONG, LPWSTR, MSG, ULONG, WORD from ctypes.wintypes import DWORD, LONG, MSG, ULONG, WORD
import pywintypes
import win32api
import win32gui
from config import config from config import config
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
from hotkey import AbstractHotkeyMgr from hotkey import AbstractHotkeyMgr
@ -17,26 +20,20 @@ assert sys.platform == 'win32'
logger = get_main_logger() logger = get_main_logger()
RegisterHotKey = ctypes.windll.user32.RegisterHotKey UnregisterHotKey = ctypes.windll.user32.UnregisterHotKey # TODO: Coming Soon
UnregisterHotKey = ctypes.windll.user32.UnregisterHotKey
MOD_ALT = 0x0001 MOD_ALT = 0x0001
MOD_CONTROL = 0x0002 MOD_CONTROL = 0x0002
MOD_SHIFT = 0x0004 MOD_SHIFT = 0x0004
MOD_WIN = 0x0008 MOD_WIN = 0x0008
MOD_NOREPEAT = 0x4000 MOD_NOREPEAT = 0x4000
GetMessage = ctypes.windll.user32.GetMessageW
TranslateMessage = ctypes.windll.user32.TranslateMessage
DispatchMessage = ctypes.windll.user32.DispatchMessageW
PostThreadMessage = ctypes.windll.user32.PostThreadMessageW
WM_QUIT = 0x0012 WM_QUIT = 0x0012
WM_HOTKEY = 0x0312 WM_HOTKEY = 0x0312
WM_APP = 0x8000 WM_APP = 0x8000
WM_SND_GOOD = WM_APP + 1 WM_SND_GOOD = WM_APP + 1
WM_SND_BAD = WM_APP + 2 WM_SND_BAD = WM_APP + 2
GetKeyState = ctypes.windll.user32.GetKeyState
MapVirtualKey = ctypes.windll.user32.MapVirtualKeyW
VK_BACK = 0x08 VK_BACK = 0x08
VK_CLEAR = 0x0c VK_CLEAR = 0x0c
VK_RETURN = 0x0d VK_RETURN = 0x0d
@ -60,10 +57,13 @@ VK_SCROLL = 0x91
VK_PROCESSKEY = 0xe5 VK_PROCESSKEY = 0xe5
VK_OEM_CLEAR = 0xfe VK_OEM_CLEAR = 0xfe
GetForegroundWindow = ctypes.windll.user32.GetForegroundWindow # VirtualKey mapping values
GetWindowText = ctypes.windll.user32.GetWindowTextW # <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mapvirtualkeyexa>
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int] MAPVK_VK_TO_VSC = 0
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW MAPVK_VSC_TO_VK = 1
MAPVK_VK_TO_CHAR = 2
MAPVK_VSC_TO_VK_EX = 3
MAPVK_VK_TO_VSC_EX = 4
def window_title(h) -> str: def window_title(h) -> str:
@ -74,9 +74,9 @@ def window_title(h) -> str:
:return: Window title. :return: Window title.
""" """
if h: if h:
title_length = GetWindowTextLength(h) + 1 title_length = win32gui.GetWindowTextLength(h) + 1
buf = ctypes.create_unicode_buffer(title_length) buf = ctypes.create_unicode_buffer(title_length)
if GetWindowText(h, buf, title_length): if win32gui.GetWindowText(h, buf, title_length):
return buf.value return buf.value
return '' return ''
@ -197,7 +197,7 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
logger.debug('Thread is/was running') logger.debug('Thread is/was running')
self.thread = None # type: ignore self.thread = None # type: ignore
logger.debug('Telling thread WM_QUIT') logger.debug('Telling thread WM_QUIT')
PostThreadMessage(thread.ident, WM_QUIT, 0, 0) win32gui.PostThreadMessage(thread.ident, WM_QUIT, 0, 0)
logger.debug('Joining thread') logger.debug('Joining thread')
thread.join() # Wait for it to unregister hotkey and quit thread.join() # Wait for it to unregister hotkey and quit
@ -210,8 +210,10 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
"""Handle hotkeys.""" """Handle hotkeys."""
logger.debug('Begin...') logger.debug('Begin...')
# Hotkey must be registered by the thread that handles it # Hotkey must be registered by the thread that handles it
if not RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode): try:
logger.debug("We're not the right thread?") win32gui.RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode)
except pywintypes.error:
logger.exception("We're not the right thread?")
self.thread = None # type: ignore self.thread = None # type: ignore
return return
@ -219,14 +221,14 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
msg = MSG() msg = MSG()
logger.debug('Entering GetMessage() loop...') logger.debug('Entering GetMessage() loop...')
while GetMessage(ctypes.byref(msg), None, 0, 0) != 0: while win32gui.GetMessage(ctypes.byref(msg), None, 0, 0) != 0:
logger.debug('Got message') logger.debug('Got message')
if msg.message == WM_HOTKEY: if msg.message == WM_HOTKEY:
logger.debug('WM_HOTKEY') logger.debug('WM_HOTKEY')
if ( if (
config.get_int('hotkey_always') config.get_int('hotkey_always')
or window_title(GetForegroundWindow()).startswith('Elite - Dangerous') or window_title(win32gui.GetForegroundWindow()).startswith('Elite - Dangerous')
): ):
if not config.shutting_down: if not config.shutting_down:
logger.debug('Sending event <<Invoke>>') logger.debug('Sending event <<Invoke>>')
@ -236,8 +238,10 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
logger.debug('Passing key on') logger.debug('Passing key on')
UnregisterHotKey(None, 1) UnregisterHotKey(None, 1)
SendInput(1, fake, ctypes.sizeof(INPUT)) SendInput(1, fake, ctypes.sizeof(INPUT))
if not RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode): try:
logger.debug("We aren't registered for this ?") win32gui.RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode)
except pywintypes.error:
logger.exception("We aren't registered for this ?")
break break
elif msg.message == WM_SND_GOOD: elif msg.message == WM_SND_GOOD:
@ -250,8 +254,8 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
else: else:
logger.debug('Something else') logger.debug('Something else')
TranslateMessage(ctypes.byref(msg)) win32gui.TranslateMessage(ctypes.byref(msg))
DispatchMessage(ctypes.byref(msg)) win32gui.DispatchMessage(ctypes.byref(msg))
logger.debug('Exited GetMessage() loop.') logger.debug('Exited GetMessage() loop.')
UnregisterHotKey(None, 1) UnregisterHotKey(None, 1)
@ -266,7 +270,7 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
"""Stop acquiring hotkey state.""" """Stop acquiring hotkey state."""
pass pass
def fromevent(self, event) -> bool | tuple | None: # noqa: CCR001 def fromevent(self, event) -> bool | tuple | None:
""" """
Return configuration (keycode, modifiers) or None=clear or False=retain previous. Return configuration (keycode, modifiers) or None=clear or False=retain previous.
@ -277,11 +281,11 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
:param event: tk event ? :param event: tk event ?
:return: False to retain previous, None to not use, else (keycode, modifiers) :return: False to retain previous, None to not use, else (keycode, modifiers)
""" """
modifiers = ((GetKeyState(VK_MENU) & 0x8000) and MOD_ALT) \ modifiers = ((win32api.GetKeyState(VK_MENU) & 0x8000) and MOD_ALT) \
| ((GetKeyState(VK_CONTROL) & 0x8000) and MOD_CONTROL) \ | ((win32api.GetKeyState(VK_CONTROL) & 0x8000) and MOD_CONTROL) \
| ((GetKeyState(VK_SHIFT) & 0x8000) and MOD_SHIFT) \ | ((win32api.GetKeyState(VK_SHIFT) & 0x8000) and MOD_SHIFT) \
| ((GetKeyState(VK_LWIN) & 0x8000) and MOD_WIN) \ | ((win32api.GetKeyState(VK_LWIN) & 0x8000) and MOD_WIN) \
| ((GetKeyState(VK_RWIN) & 0x8000) and MOD_WIN) | ((win32api.GetKeyState(VK_RWIN) & 0x8000) and MOD_WIN)
keycode = event.keycode keycode = event.keycode
if keycode in (VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, VK_RWIN): if keycode in (VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, VK_RWIN):
@ -295,7 +299,7 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
return None return None
if ( if (
keycode in (VK_RETURN, VK_SPACE, VK_OEM_MINUS) or ord('A') <= keycode <= ord('Z') keycode in (VK_RETURN, VK_SPACE, VK_OEM_MINUS) or ord('A') <= keycode <= ord('Z')
): # don't allow keys needed for typing in System Map ): # don't allow keys needed for typing in System Map
winsound.MessageBeep() winsound.MessageBeep()
return None return None
@ -305,12 +309,13 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
return 0, modifiers return 0, modifiers
# See if the keycode is usable and available # See if the keycode is usable and available
if RegisterHotKey(None, 2, modifiers | MOD_NOREPEAT, keycode): try:
win32gui.RegisterHotKey(None, 2, modifiers | MOD_NOREPEAT, keycode)
UnregisterHotKey(None, 2) UnregisterHotKey(None, 2)
return keycode, modifiers return keycode, modifiers
except pywintypes.error:
winsound.MessageBeep() winsound.MessageBeep()
return None return None
def display(self, keycode, modifiers) -> str: def display(self, keycode, modifiers) -> str:
""" """
@ -346,7 +351,7 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
text += WindowsHotkeyMgr.DISPLAY[keycode] text += WindowsHotkeyMgr.DISPLAY[keycode]
else: else:
c = MapVirtualKey(keycode, 2) # printable ? c = win32api.MapVirtualKey(keycode, MAPVK_VK_TO_CHAR)
if not c: # oops not printable if not c: # oops not printable
text += '' text += ''
@ -361,9 +366,9 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
def play_good(self) -> None: def play_good(self) -> None:
"""Play the 'good' sound.""" """Play the 'good' sound."""
if self.thread: if self.thread:
PostThreadMessage(self.thread.ident, WM_SND_GOOD, 0, 0) win32gui.PostThreadMessage(self.thread.ident, WM_SND_GOOD, 0, 0)
def play_bad(self) -> None: def play_bad(self) -> None:
"""Play the 'bad' sound.""" """Play the 'bad' sound."""
if self.thread: if self.thread:
PostThreadMessage(self.thread.ident, WM_SND_BAD, 0, 0) win32gui.PostThreadMessage(self.thread.ident, WM_SND_BAD, 0, 0)

View File

@ -36,23 +36,15 @@ MAX_FCMATERIALS_DISCREPANCY = 5 # Timestamp difference in seconds
if sys.platform == 'win32': if sys.platform == 'win32':
import ctypes import ctypes
from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR from ctypes.wintypes import BOOL, HWND, LPARAM
import win32gui
from watchdog.events import FileSystemEventHandler, FileSystemEvent from watchdog.events import FileSystemEventHandler, FileSystemEvent
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.observers.api import BaseObserver from watchdog.observers.api import BaseObserver
EnumWindows = ctypes.windll.user32.EnumWindows
EnumWindowsProc = ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM) EnumWindowsProc = ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
CloseHandle = ctypes.windll.kernel32.CloseHandle CloseHandle = ctypes.windll.kernel32.CloseHandle
GetWindowText = ctypes.windll.user32.GetWindowTextW
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
GetWindowTextLength.argtypes = [ctypes.wintypes.HWND]
GetWindowTextLength.restype = ctypes.c_int
GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd
else: else:
@ -2131,9 +2123,9 @@ class EDLogs(FileSystemEventHandler):
if sys.platform == 'win32': if sys.platform == 'win32':
def WindowTitle(h): # noqa: N802 def WindowTitle(h): # noqa: N802
if h: if h:
length = GetWindowTextLength(h) + 1 length = win32gui.GetWindowTextLength(h) + 1
buf = ctypes.create_unicode_buffer(length) buf = ctypes.create_unicode_buffer(length)
if GetWindowText(h, buf, length): if win32gui.GetWindowText(h, buf, length):
return buf.value return buf.value
return None return None
@ -2147,7 +2139,7 @@ class EDLogs(FileSystemEventHandler):
return True return True
return not EnumWindows(EnumWindowsProc(callback), 0) return not win32gui.EnumWindows(EnumWindowsProc(callback), 0)
return False return False

View File

@ -188,7 +188,8 @@ class AutoInc(contextlib.AbstractContextManager):
if sys.platform == 'win32': if sys.platform == 'win32':
import ctypes import ctypes
import winreg import winreg
from ctypes.wintypes import HINSTANCE, HWND, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT from ctypes.wintypes import HINSTANCE, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT
import win32gui
is_wine = False is_wine = False
try: try:
WINE_REGISTRY_KEY = r'HKEY_LOCAL_MACHINE\Software\Wine' WINE_REGISTRY_KEY = r'HKEY_LOCAL_MACHINE\Software\Wine'
@ -219,11 +220,6 @@ if sys.platform == 'win32':
ctypes.POINTER(RECT) ctypes.POINTER(RECT)
] ]
GetParent = ctypes.windll.user32.GetParent
GetParent.argtypes = [HWND]
GetWindowRect = ctypes.windll.user32.GetWindowRect
GetWindowRect.argtypes = [HWND, ctypes.POINTER(RECT)]
SHGetLocalizedName = ctypes.windll.shell32.SHGetLocalizedName SHGetLocalizedName = ctypes.windll.shell32.SHGetLocalizedName
SHGetLocalizedName.argtypes = [LPCWSTR, LPWSTR, UINT, ctypes.POINTER(ctypes.c_int)] SHGetLocalizedName.argtypes = [LPCWSTR, LPWSTR, UINT, ctypes.POINTER(ctypes.c_int)]
@ -314,7 +310,7 @@ class PreferencesDialog(tk.Toplevel):
# Ensure fully on-screen # Ensure fully on-screen
if sys.platform == 'win32' and CalculatePopupWindowPosition: if sys.platform == 'win32' and CalculatePopupWindowPosition:
position = RECT() position = RECT()
GetWindowRect(GetParent(self.winfo_id()), position) win32gui.GetWindowRect(win32gui.GetParent(self.winfo_id()), position)
if CalculatePopupWindowPosition( if CalculatePopupWindowPosition(
POINT(parent.winfo_rootx(), parent.winfo_rooty()), POINT(parent.winfo_rootx(), parent.winfo_rooty()),
SIZE(position.right - position.left, position.bottom - position.top), # type: ignore SIZE(position.right - position.left, position.bottom - position.top), # type: ignore

View File

@ -42,8 +42,6 @@ pytest==8.2.0
pytest-cov==5.0.0 # Pytest code coverage support pytest-cov==5.0.0 # Pytest code coverage support
coverage[toml]==7.5.0 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs coverage[toml]==7.5.0 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
coverage-conditional-plugin==0.9.0 coverage-conditional-plugin==0.9.0
# For manipulating folder permissions and the like.
pywin32==306; sys_platform == 'win32'
# All of the normal requirements # All of the normal requirements

View File

@ -3,3 +3,5 @@ pillow==10.3.0
watchdog==4.0.0 watchdog==4.0.0
simplesystray==0.1.0; sys_platform == 'win32' simplesystray==0.1.0; sys_platform == 'win32'
semantic-version==2.10.0 semantic-version==2.10.0
# For manipulating folder permissions and the like.
pywin32==306; sys_platform == 'win32'

View File

@ -25,17 +25,14 @@ logger = EDMCLogging.get_main_logger()
if sys.platform == 'win32': if sys.platform == 'win32':
import ctypes import ctypes
from ctypes.wintypes import HWND, POINT, RECT, SIZE, UINT from ctypes.wintypes import POINT, RECT, SIZE, UINT
import win32gui
try: try:
CalculatePopupWindowPosition = ctypes.windll.user32.CalculatePopupWindowPosition CalculatePopupWindowPosition = ctypes.windll.user32.CalculatePopupWindowPosition
CalculatePopupWindowPosition.argtypes = [ CalculatePopupWindowPosition.argtypes = [
ctypes.POINTER(POINT), ctypes.POINTER(SIZE), UINT, ctypes.POINTER(RECT), ctypes.POINTER(RECT) ctypes.POINTER(POINT), ctypes.POINTER(SIZE), UINT, ctypes.POINTER(RECT), ctypes.POINTER(RECT)
] ]
GetParent = ctypes.windll.user32.GetParent
GetParent.argtypes = [HWND]
GetWindowRect = ctypes.windll.user32.GetWindowRect
GetWindowRect.argtypes = [HWND, ctypes.POINTER(RECT)]
except Exception: # Not supported under Wine 4.0 except Exception: # Not supported under Wine 4.0
CalculatePopupWindowPosition = None # type: ignore CalculatePopupWindowPosition = None # type: ignore
@ -423,7 +420,7 @@ class StatsResults(tk.Toplevel):
# Ensure fully on-screen # Ensure fully on-screen
if sys.platform == 'win32' and CalculatePopupWindowPosition: if sys.platform == 'win32' and CalculatePopupWindowPosition:
position = RECT() position = RECT()
GetWindowRect(GetParent(self.winfo_id()), position) win32gui.GetWindowRect(win32gui.GetParent(self.winfo_id()), position)
if CalculatePopupWindowPosition( if CalculatePopupWindowPosition(
POINT(parent.winfo_rootx(), parent.winfo_rooty()), POINT(parent.winfo_rootx(), parent.winfo_rooty()),
# - is evidently supported on the C side # - is evidently supported on the C side

View File

@ -34,6 +34,7 @@ if sys.platform == "linux":
if sys.platform == 'win32': if sys.platform == 'win32':
import ctypes import ctypes
from ctypes.wintypes import DWORD, LPCVOID, LPCWSTR from ctypes.wintypes import DWORD, LPCVOID, LPCWSTR
import win32gui
AddFontResourceEx = ctypes.windll.gdi32.AddFontResourceExW AddFontResourceEx = ctypes.windll.gdi32.AddFontResourceExW
AddFontResourceEx.restypes = [LPCWSTR, DWORD, LPCVOID] # type: ignore AddFontResourceEx.restypes = [LPCWSTR, DWORD, LPCVOID] # type: ignore
FR_PRIVATE = 0x10 FR_PRIVATE = 0x10
@ -427,8 +428,6 @@ class _Theme:
GWL_EXSTYLE = -20 # noqa: N806 # ctypes GWL_EXSTYLE = -20 # noqa: N806 # ctypes
WS_EX_APPWINDOW = 0x00040000 # noqa: N806 # ctypes WS_EX_APPWINDOW = 0x00040000 # noqa: N806 # ctypes
WS_EX_LAYERED = 0x00080000 # noqa: N806 # ctypes WS_EX_LAYERED = 0x00080000 # noqa: N806 # ctypes
GetWindowLongW = ctypes.windll.user32.GetWindowLongW # noqa: N806 # ctypes
SetWindowLongW = ctypes.windll.user32.SetWindowLongW # noqa: N806 # ctypes
# FIXME: Lose the "treat this like a boolean" bullshit # FIXME: Lose the "treat this like a boolean" bullshit
if theme == self.THEME_DEFAULT: if theme == self.THEME_DEFAULT:
@ -445,14 +444,14 @@ class _Theme:
root.withdraw() root.withdraw()
root.update_idletasks() # Size and windows styles get recalculated here root.update_idletasks() # Size and windows styles get recalculated here
hwnd = ctypes.windll.user32.GetParent(root.winfo_id()) hwnd = win32gui.GetParent(root.winfo_id())
SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX) # disable maximize win32gui.SetWindowLong(hwnd, GWL_STYLE, win32gui.GetWindowLong(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX) # disable maximize
if theme == self.THEME_TRANSPARENT: if theme == self.THEME_TRANSPARENT:
SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_LAYERED) # Add to taskbar win32gui.SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_LAYERED) # Add to taskbar
else: else:
SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW) # Add to taskbar win32gui.SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW) # Add to taskbar
root.deiconify() root.deiconify()
root.wait_visibility() # need main window to be displayed before returning root.wait_visibility() # need main window to be displayed before returning