mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-13 15:57:14 +03:00
Merge pull request #2263 from HullSeals/enhancement/1805/additional-prototyping
[1805] Prototype More Functions
This commit is contained in:
commit
5c5682d789
@ -255,24 +255,16 @@ if __name__ == '__main__': # noqa: C901
|
||||
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
|
||||
from ctypes import windll, create_unicode_buffer, WINFUNCTYPE
|
||||
from ctypes.wintypes import BOOL, HWND, LPARAM
|
||||
import win32gui
|
||||
import win32api
|
||||
import win32con
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
COINIT_MULTITHREADED = 0 # noqa: N806,F841
|
||||
@ -280,16 +272,9 @@ if __name__ == '__main__': # noqa: C901
|
||||
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
|
||||
buf = create_unicode_buffer(text_length)
|
||||
if GetWindowText(h, buf, text_length):
|
||||
return buf.value
|
||||
|
||||
return win32gui.GetWindowText(h)
|
||||
return None
|
||||
|
||||
@WINFUNCTYPE(BOOL, HWND, LPARAM)
|
||||
@ -311,7 +296,7 @@ if __name__ == '__main__': # noqa: C901
|
||||
# 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):
|
||||
@ -319,12 +304,12 @@ if __name__ == '__main__': # noqa: C901
|
||||
if len(sys.argv) > 1 and sys.argv[1].startswith(protocolhandler_redirect):
|
||||
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)
|
||||
win32gui.ShowWindow(window_handle, win32con.SW_RESTORE)
|
||||
win32api.ShellExecute(0, None, sys.argv[1], None, None, win32con.SW_RESTORE)
|
||||
|
||||
else:
|
||||
ShowWindowAsync(window_handle, SW_RESTORE)
|
||||
SetForegroundWindow(window_handle)
|
||||
ShowWindowAsync(window_handle, win32con.SW_RESTORE)
|
||||
win32gui.SetForegroundWindow(window_handle)
|
||||
|
||||
return False # Indicate window found, so stop iterating
|
||||
|
||||
@ -336,7 +321,7 @@ if __name__ == '__main__': # noqa: C901
|
||||
# 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."""
|
||||
@ -703,13 +688,14 @@ class AppWindow:
|
||||
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'))
|
||||
|
@ -7,41 +7,23 @@ See LICENSE file.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
import functools
|
||||
import pathlib
|
||||
import sys
|
||||
import uuid
|
||||
import winreg
|
||||
from ctypes.wintypes import DWORD, HANDLE
|
||||
from typing import Literal
|
||||
from config import AbstractConfig, applongname, appname, logger
|
||||
from win32comext.shell import shell
|
||||
|
||||
assert sys.platform == 'win32'
|
||||
|
||||
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:
|
||||
"""Look up a Windows GUID to actual folder path name."""
|
||||
buf = ctypes.c_wchar_p()
|
||||
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
|
||||
return shell.SHGetKnownFolderPath(guid, 0, 0)
|
||||
|
||||
|
||||
class WinConfig(AbstractConfig):
|
||||
@ -49,6 +31,7 @@ class WinConfig(AbstractConfig):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
REGISTRY_SUBKEY = r'Software\Marginal\EDMarketConnector' # noqa: N806
|
||||
create_key_defaults = functools.partial(
|
||||
winreg.CreateKeyEx,
|
||||
@ -63,7 +46,8 @@ class WinConfig(AbstractConfig):
|
||||
logger.exception('Could not create required registry keys')
|
||||
raise
|
||||
|
||||
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.default_plugin_dir_path = self.app_dir_path / 'plugins'
|
||||
@ -83,12 +67,12 @@ class WinConfig(AbstractConfig):
|
||||
self.home_path = pathlib.Path.home()
|
||||
|
||||
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.identifier = applongname
|
||||
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)
|
||||
|
||||
def __get_regentry(self, key: str) -> None | list | str | int:
|
||||
|
@ -8,7 +8,11 @@ import sys
|
||||
import threading
|
||||
import tkinter as tk
|
||||
import winsound
|
||||
from ctypes.wintypes import DWORD, HWND, LONG, LPWSTR, MSG, ULONG, WORD
|
||||
from ctypes.wintypes import DWORD, LONG, MSG, ULONG, WORD, HWND, BOOL, UINT
|
||||
import pywintypes
|
||||
import win32api
|
||||
import win32gui
|
||||
import win32con
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
from hotkey import AbstractHotkeyMgr
|
||||
@ -17,53 +21,22 @@ assert sys.platform == 'win32'
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
RegisterHotKey = ctypes.windll.user32.RegisterHotKey
|
||||
UnregisterHotKey = ctypes.windll.user32.UnregisterHotKey
|
||||
MOD_ALT = 0x0001
|
||||
MOD_CONTROL = 0x0002
|
||||
MOD_SHIFT = 0x0004
|
||||
MOD_WIN = 0x0008
|
||||
UnregisterHotKey = ctypes.windll.user32.UnregisterHotKey # TODO: Coming Soon
|
||||
UnregisterHotKey.argtypes = [HWND, ctypes.c_int]
|
||||
UnregisterHotKey.restype = BOOL
|
||||
|
||||
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_HOTKEY = 0x0312
|
||||
WM_APP = 0x8000
|
||||
WM_SND_GOOD = WM_APP + 1
|
||||
WM_SND_BAD = WM_APP + 2
|
||||
|
||||
GetKeyState = ctypes.windll.user32.GetKeyState
|
||||
MapVirtualKey = ctypes.windll.user32.MapVirtualKeyW
|
||||
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
|
||||
|
||||
GetForegroundWindow = ctypes.windll.user32.GetForegroundWindow
|
||||
GetWindowText = ctypes.windll.user32.GetWindowTextW
|
||||
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
|
||||
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
|
||||
# VirtualKey mapping values
|
||||
# <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mapvirtualkeyexa>
|
||||
MAPVK_VK_TO_VSC = 0
|
||||
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:
|
||||
@ -74,11 +47,7 @@ def window_title(h) -> str:
|
||||
:return: Window title.
|
||||
"""
|
||||
if h:
|
||||
title_length = GetWindowTextLength(h) + 1
|
||||
buf = ctypes.create_unicode_buffer(title_length)
|
||||
if GetWindowText(h, buf, title_length):
|
||||
return buf.value
|
||||
|
||||
return win32gui.GetWindowText(h)
|
||||
return ''
|
||||
|
||||
|
||||
@ -138,6 +107,7 @@ class INPUT(ctypes.Structure):
|
||||
|
||||
SendInput = ctypes.windll.user32.SendInput
|
||||
SendInput.argtypes = [ctypes.c_uint, ctypes.POINTER(INPUT), ctypes.c_int]
|
||||
SendInput.restype = UINT
|
||||
|
||||
INPUT_MOUSE = 0
|
||||
INPUT_KEYBOARD = 1
|
||||
@ -197,7 +167,7 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
logger.debug('Thread is/was running')
|
||||
self.thread = None # type: ignore
|
||||
logger.debug('Telling thread WM_QUIT')
|
||||
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
|
||||
|
||||
@ -210,8 +180,10 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
"""Handle hotkeys."""
|
||||
logger.debug('Begin...')
|
||||
# Hotkey must be registered by the thread that handles it
|
||||
if not RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode):
|
||||
logger.debug("We're not the right thread?")
|
||||
try:
|
||||
win32gui.RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode)
|
||||
except pywintypes.error:
|
||||
logger.exception("We're not the right thread?")
|
||||
self.thread = None # type: ignore
|
||||
return
|
||||
|
||||
@ -219,14 +191,14 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
|
||||
msg = MSG()
|
||||
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')
|
||||
if msg.message == WM_HOTKEY:
|
||||
if msg.message == win32con.WM_HOTKEY:
|
||||
logger.debug('WM_HOTKEY')
|
||||
|
||||
if (
|
||||
config.get_int('hotkey_always')
|
||||
or window_title(GetForegroundWindow()).startswith('Elite - Dangerous')
|
||||
config.get_int('hotkey_always')
|
||||
or window_title(win32gui.GetForegroundWindow()).startswith('Elite - Dangerous')
|
||||
):
|
||||
if not config.shutting_down:
|
||||
logger.debug('Sending event <<Invoke>>')
|
||||
@ -236,8 +208,10 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
logger.debug('Passing key on')
|
||||
UnregisterHotKey(None, 1)
|
||||
SendInput(1, fake, ctypes.sizeof(INPUT))
|
||||
if not RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode):
|
||||
logger.debug("We aren't registered for this ?")
|
||||
try:
|
||||
win32gui.RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode)
|
||||
except pywintypes.error:
|
||||
logger.exception("We aren't registered for this ?")
|
||||
break
|
||||
|
||||
elif msg.message == WM_SND_GOOD:
|
||||
@ -250,8 +224,8 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
|
||||
else:
|
||||
logger.debug('Something else')
|
||||
TranslateMessage(ctypes.byref(msg))
|
||||
DispatchMessage(ctypes.byref(msg))
|
||||
win32gui.TranslateMessage(ctypes.byref(msg))
|
||||
win32gui.DispatchMessage(ctypes.byref(msg))
|
||||
|
||||
logger.debug('Exited GetMessage() loop.')
|
||||
UnregisterHotKey(None, 1)
|
||||
@ -277,40 +251,42 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
:param event: tk event ?
|
||||
:return: False to retain previous, None to not use, else (keycode, modifiers)
|
||||
"""
|
||||
modifiers = ((GetKeyState(VK_MENU) & 0x8000) and MOD_ALT) \
|
||||
| ((GetKeyState(VK_CONTROL) & 0x8000) and MOD_CONTROL) \
|
||||
| ((GetKeyState(VK_SHIFT) & 0x8000) and MOD_SHIFT) \
|
||||
| ((GetKeyState(VK_LWIN) & 0x8000) and MOD_WIN) \
|
||||
| ((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
|
||||
if RegisterHotKey(None, 2, modifiers | MOD_NOREPEAT, keycode):
|
||||
try:
|
||||
win32gui.RegisterHotKey(None, 2, modifiers | MOD_NOREPEAT, keycode)
|
||||
UnregisterHotKey(None, 2)
|
||||
return keycode, modifiers
|
||||
|
||||
winsound.MessageBeep()
|
||||
return None
|
||||
except pywintypes.error:
|
||||
winsound.MessageBeep()
|
||||
return None
|
||||
|
||||
def display(self, keycode, modifiers) -> str:
|
||||
"""
|
||||
@ -321,32 +297,32 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
: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]
|
||||
|
||||
else:
|
||||
c = MapVirtualKey(keycode, 2) # printable ?
|
||||
c = win32api.MapVirtualKey(keycode, MAPVK_VK_TO_CHAR)
|
||||
if not c: # oops not printable
|
||||
text += '⁈'
|
||||
|
||||
@ -361,9 +337,9 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
def play_good(self) -> None:
|
||||
"""Play the 'good' sound."""
|
||||
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:
|
||||
"""Play the 'bad' sound."""
|
||||
if self.thread:
|
||||
PostThreadMessage(self.thread.ident, WM_SND_BAD, 0, 0)
|
||||
win32gui.PostThreadMessage(self.thread.ident, WM_SND_BAD, 0, 0)
|
||||
|
1
l10n.py
1
l10n.py
@ -49,7 +49,6 @@ if sys.platform == 'win32':
|
||||
GetUserPreferredUILanguages.argtypes = [
|
||||
DWORD, ctypes.POINTER(ctypes.c_ulong), LPCVOID, ctypes.POINTER(ctypes.c_ulong)
|
||||
]
|
||||
|
||||
GetUserPreferredUILanguages.restype = BOOL
|
||||
|
||||
LOCALE_NAME_USER_DEFAULT = None
|
||||
|
74
monitor.py
74
monitor.py
@ -18,6 +18,7 @@ from collections import defaultdict
|
||||
from os import SEEK_END, SEEK_SET, listdir
|
||||
from time import gmtime, localtime, mktime, sleep, strftime, strptime, time
|
||||
from typing import TYPE_CHECKING, Any, BinaryIO, MutableMapping
|
||||
import psutil
|
||||
import semantic_version
|
||||
import util_ships
|
||||
from config import config, appname, appversion
|
||||
@ -35,26 +36,10 @@ MAX_NAVROUTE_DISCREPANCY = 5 # Timestamp difference in seconds
|
||||
MAX_FCMATERIALS_DISCREPANCY = 5 # Timestamp difference in seconds
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR
|
||||
|
||||
from watchdog.events import FileSystemEventHandler, FileSystemEvent
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.observers.api import BaseObserver
|
||||
|
||||
EnumWindows = ctypes.windll.user32.EnumWindows
|
||||
EnumWindowsProc = ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
|
||||
|
||||
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
|
||||
|
||||
else:
|
||||
# Linux's inotify doesn't work over CIFS or NFS, so poll
|
||||
FileSystemEventHandler = object # dummy
|
||||
@ -70,7 +55,8 @@ class EDLogs(FileSystemEventHandler):
|
||||
"""Monitoring of Journal files."""
|
||||
|
||||
# Magic with FileSystemEventHandler can confuse type checkers when they do not have access to every import
|
||||
_POLL = 1 # Polling is cheap, so do it often
|
||||
_POLL = 1 # Polling while running is cheap, so do it often
|
||||
_INACTIVE_POLL = 10 # Polling while not running isn't as cheap, so do it less often
|
||||
_RE_CANONICALISE = re.compile(r'\$(.+)_name;')
|
||||
_RE_CATEGORY = re.compile(r'\$MICRORESOURCE_CATEGORY_(.+);')
|
||||
_RE_LOGFILE = re.compile(r'^Journal(Alpha|Beta)?\.[0-9]{2,4}(-)?[0-9]{2}(-)?[0-9]{2}(T)?[0-9]{2}[0-9]{2}[0-9]{2}'
|
||||
@ -100,6 +86,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
self.catching_up = False
|
||||
|
||||
self.game_was_running = False # For generation of the "ShutDown" event
|
||||
self.running_process = None
|
||||
|
||||
# Context for journal handling
|
||||
self.version: str | None = None
|
||||
@ -474,7 +461,10 @@ class EDLogs(FileSystemEventHandler):
|
||||
loghandle = open(logfile, 'rb', 0) # unbuffered
|
||||
log_pos = 0
|
||||
|
||||
sleep(self._POLL)
|
||||
if self.game_was_running:
|
||||
sleep(self._POLL)
|
||||
else:
|
||||
sleep(self._INACTIVE_POLL)
|
||||
|
||||
# Check whether we're still supposed to be running
|
||||
if threading.current_thread() != self.thread:
|
||||
@ -2150,36 +2140,34 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
return entry
|
||||
|
||||
def game_running(self) -> bool: # noqa: CCR001
|
||||
def game_running(self) -> bool:
|
||||
"""
|
||||
Determine if the game is currently running.
|
||||
|
||||
TODO: Implement on Linux
|
||||
|
||||
:return: bool - True if the game is running.
|
||||
"""
|
||||
if sys.platform == 'win32':
|
||||
def WindowTitle(h): # noqa: N802
|
||||
if h:
|
||||
length = GetWindowTextLength(h) + 1
|
||||
buf = ctypes.create_unicode_buffer(length)
|
||||
if GetWindowText(h, buf, length):
|
||||
return buf.value
|
||||
return None
|
||||
|
||||
def callback(hWnd, lParam): # noqa: N803
|
||||
name = WindowTitle(hWnd)
|
||||
if name and name.startswith('Elite - Dangerous'):
|
||||
handle = GetProcessHandleFromHwnd(hWnd)
|
||||
if handle: # If GetProcessHandleFromHwnd succeeds then the app is already running as this user
|
||||
CloseHandle(handle)
|
||||
return False # stop enumeration
|
||||
|
||||
return True
|
||||
|
||||
return not EnumWindows(EnumWindowsProc(callback), 0)
|
||||
|
||||
return False
|
||||
if self.running_process:
|
||||
p = self.running_process
|
||||
try:
|
||||
with p.oneshot():
|
||||
if p.status() not in [psutil.STATUS_RUNNING, psutil.STATUS_SLEEPING]:
|
||||
raise psutil.NoSuchProcess
|
||||
except psutil.NoSuchProcess:
|
||||
# Process likely expired
|
||||
self.running_process = None
|
||||
if not self.running_process:
|
||||
edmc_process = psutil.Process()
|
||||
edmc_user = edmc_process.username()
|
||||
try:
|
||||
for pid in psutil.pids():
|
||||
proc = psutil.Process(pid)
|
||||
if 'EliteDangerous' in proc.name() and proc.username() == edmc_user:
|
||||
self.running_process = proc
|
||||
return True
|
||||
except psutil.NoSuchProcess:
|
||||
pass
|
||||
return False
|
||||
return bool(self.running_process)
|
||||
|
||||
def ship(self, timestamped=True) -> MutableMapping[str, Any] | None:
|
||||
"""
|
||||
|
19
prefs.py
19
prefs.py
@ -186,7 +186,9 @@ class AutoInc(contextlib.AbstractContextManager):
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
import winreg
|
||||
from ctypes.wintypes import HINSTANCE, HWND, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT, BOOL
|
||||
import win32gui
|
||||
import win32api
|
||||
is_wine = False
|
||||
try:
|
||||
WINE_REGISTRY_KEY = r'HKEY_LOCAL_MACHINE\Software\Wine'
|
||||
@ -201,6 +203,8 @@ if sys.platform == 'win32':
|
||||
if not is_wine:
|
||||
try:
|
||||
CalculatePopupWindowPosition = ctypes.windll.user32.CalculatePopupWindowPosition
|
||||
CalculatePopupWindowPosition.argtypes = [POINT, SIZE, UINT, RECT, RECT]
|
||||
CalculatePopupWindowPosition.restype = BOOL
|
||||
|
||||
except AttributeError as e:
|
||||
logger.error(
|
||||
@ -217,17 +221,9 @@ if sys.platform == 'win32':
|
||||
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.argtypes = [LPCWSTR, LPWSTR, UINT, ctypes.POINTER(ctypes.c_int)]
|
||||
|
||||
LoadString = ctypes.windll.user32.LoadStringW
|
||||
LoadString.argtypes = [HINSTANCE, UINT, LPWSTR, ctypes.c_int]
|
||||
|
||||
|
||||
class PreferencesDialog(tk.Toplevel):
|
||||
"""The EDMC preferences dialog."""
|
||||
@ -313,7 +309,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
# Ensure fully on-screen
|
||||
if sys.platform == 'win32' and CalculatePopupWindowPosition:
|
||||
position = RECT()
|
||||
GetWindowRect(GetParent(self.winfo_id()), position)
|
||||
win32gui.GetWindowRect(win32gui.GetParent(self.winfo_id()))
|
||||
if CalculatePopupWindowPosition(
|
||||
POINT(parent.winfo_rootx(), parent.winfo_rooty()),
|
||||
SIZE(position.right - position.left, position.bottom - position.top), # type: ignore
|
||||
@ -1110,7 +1106,8 @@ class PreferencesDialog(tk.Toplevel):
|
||||
for i in range(start, len(components)):
|
||||
try:
|
||||
if (not SHGetLocalizedName('\\'.join(components[:i+1]), buf, MAX_PATH, ctypes.byref(pidsRes)) and
|
||||
LoadString(ctypes.WinDLL(expandvars(buf.value))._handle, pidsRes.value, buf, MAX_PATH)):
|
||||
win32api.LoadString(ctypes.WinDLL(expandvars(buf.value))._handle,
|
||||
pidsRes.value, buf, MAX_PATH)):
|
||||
display.append(buf.value)
|
||||
|
||||
else:
|
||||
|
79
protocol.py
79
protocol.py
@ -69,13 +69,16 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
# This could be false if you use auth_force_edmc_protocol, but then you get to keep the pieces
|
||||
assert sys.platform == 'win32'
|
||||
# spell-checker: words HBRUSH HICON WPARAM wstring WNDCLASS HMENU HGLOBAL
|
||||
from ctypes import ( # type: ignore
|
||||
windll, POINTER, WINFUNCTYPE, Structure, byref, c_long, c_void_p, create_unicode_buffer, wstring_at
|
||||
from ctypes import (
|
||||
windll, WINFUNCTYPE, Structure, byref, c_long, c_void_p, create_unicode_buffer, wstring_at
|
||||
)
|
||||
from ctypes.wintypes import (
|
||||
ATOM, BOOL, DWORD, HBRUSH, HGLOBAL, HICON, HINSTANCE, HMENU, HWND, INT, LPARAM, LPCWSTR, LPMSG, LPVOID, LPWSTR,
|
||||
ATOM, HBRUSH, HICON, HINSTANCE, HWND, INT, LPARAM, LPCWSTR, LPWSTR,
|
||||
MSG, UINT, WPARAM
|
||||
)
|
||||
import win32gui
|
||||
import win32con
|
||||
import win32api
|
||||
|
||||
class WNDCLASS(Structure):
|
||||
"""
|
||||
@ -98,41 +101,8 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
('lpszClassName', LPCWSTR)
|
||||
]
|
||||
|
||||
CW_USEDEFAULT = 0x80000000
|
||||
|
||||
CreateWindowExW = windll.user32.CreateWindowExW
|
||||
CreateWindowExW.argtypes = [DWORD, LPCWSTR, LPCWSTR, DWORD, INT, INT, INT, INT, HWND, HMENU, HINSTANCE, LPVOID]
|
||||
CreateWindowExW.restype = HWND
|
||||
RegisterClassW = windll.user32.RegisterClassW
|
||||
RegisterClassW.argtypes = [POINTER(WNDCLASS)]
|
||||
# DefWindowProcW
|
||||
# Ref: <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-defwindowprocw>
|
||||
# LRESULT DefWindowProcW([in] HWND hWnd,[in] UINT Msg,[in] WPARAM wParam,[in] LPARAM lParam);
|
||||
# As per example at <https://docs.python.org/3/library/ctypes.html#ctypes.WINFUNCTYPE>
|
||||
|
||||
prototype = WINFUNCTYPE(c_long, HWND, UINT, WPARAM, LPARAM)
|
||||
paramflags = (1, "hWnd"), (1, "Msg"), (1, "wParam"), (1, "lParam")
|
||||
DefWindowProcW = prototype(("DefWindowProcW", windll.user32), paramflags)
|
||||
|
||||
GetParent = windll.user32.GetParent
|
||||
SetForegroundWindow = windll.user32.SetForegroundWindow
|
||||
|
||||
# <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessagew>
|
||||
# NB: Despite 'BOOL' return type, it *can* be >0, 0 or -1, so is actually
|
||||
# c_long
|
||||
prototype = WINFUNCTYPE(c_long, LPMSG, HWND, UINT, UINT)
|
||||
paramflags = (1, "lpMsg"), (1, "hWnd"), (1, "wMsgFilterMin"), (1, "wMsgFilterMax")
|
||||
GetMessageW = prototype(("GetMessageW", windll.user32), paramflags)
|
||||
|
||||
TranslateMessage = windll.user32.TranslateMessage
|
||||
DispatchMessageW = windll.user32.DispatchMessageW
|
||||
PostThreadMessageW = windll.user32.PostThreadMessageW
|
||||
SendMessageW = windll.user32.SendMessageW
|
||||
SendMessageW.argtypes = [HWND, UINT, WPARAM, LPARAM]
|
||||
PostMessageW = windll.user32.PostMessageW
|
||||
PostMessageW.argtypes = [HWND, UINT, WPARAM, LPARAM]
|
||||
|
||||
WM_QUIT = 0x0012
|
||||
# https://docs.microsoft.com/en-us/windows/win32/dataxchg/wm-dde-initiate
|
||||
WM_DDE_INITIATE = 0x03E0
|
||||
WM_DDE_TERMINATE = 0x03E1
|
||||
@ -148,12 +118,6 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
GlobalGetAtomNameW = windll.kernel32.GlobalGetAtomNameW
|
||||
GlobalGetAtomNameW.argtypes = [ATOM, LPWSTR, INT]
|
||||
GlobalGetAtomNameW.restype = UINT
|
||||
GlobalLock = windll.kernel32.GlobalLock
|
||||
GlobalLock.argtypes = [HGLOBAL]
|
||||
GlobalLock.restype = LPVOID
|
||||
GlobalUnlock = windll.kernel32.GlobalUnlock
|
||||
GlobalUnlock.argtypes = [HGLOBAL]
|
||||
GlobalUnlock.restype = BOOL
|
||||
|
||||
# Windows Message handler stuff (IPC)
|
||||
# https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633573(v=vs.85)
|
||||
@ -171,7 +135,7 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
if message != WM_DDE_INITIATE:
|
||||
# Not a DDE init message, bail and tell windows to do the default
|
||||
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-defwindowproca?redirectedfrom=MSDN
|
||||
return DefWindowProcW(hwnd, message, wParam, lParam)
|
||||
return win32gui.DefWindowProc(hwnd, message, wParam, lParam)
|
||||
|
||||
service = create_unicode_buffer(256)
|
||||
topic = create_unicode_buffer(256)
|
||||
@ -196,7 +160,7 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
|
||||
if target_is_valid and topic_is_valid:
|
||||
# if everything is happy, send an acknowledgement of the DDE request
|
||||
SendMessageW(
|
||||
win32gui.SendMessage(
|
||||
wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, GlobalAddAtomW(appname), GlobalAddAtomW('System'))
|
||||
)
|
||||
|
||||
@ -229,7 +193,7 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
thread = self.thread
|
||||
if thread:
|
||||
self.thread = None
|
||||
PostThreadMessageW(thread.ident, WM_QUIT, 0, 0)
|
||||
win32api.PostThreadMessage(thread.ident, win32con.WM_QUIT, 0, 0)
|
||||
thread.join() # Wait for it to quit
|
||||
|
||||
def worker(self) -> None:
|
||||
@ -239,24 +203,25 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
wndclass.lpfnWndProc = WndProc
|
||||
wndclass.cbClsExtra = 0
|
||||
wndclass.cbWndExtra = 0
|
||||
wndclass.hInstance = windll.kernel32.GetModuleHandleW(0)
|
||||
wndclass.hInstance = win32gui.GetModuleHandle(0)
|
||||
wndclass.hIcon = None
|
||||
wndclass.hCursor = None
|
||||
wndclass.hbrBackground = None
|
||||
wndclass.lpszMenuName = None
|
||||
wndclass.lpszClassName = 'DDEServer'
|
||||
|
||||
if not RegisterClassW(byref(wndclass)):
|
||||
if not win32gui.RegisterClass(byref(wndclass)):
|
||||
print('Failed to register Dynamic Data Exchange for cAPI')
|
||||
return
|
||||
|
||||
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
|
||||
hwnd = CreateWindowExW(
|
||||
hwnd = win32gui.CreateWindowEx(
|
||||
0, # dwExStyle
|
||||
wndclass.lpszClassName, # lpClassName
|
||||
"DDE Server", # lpWindowName
|
||||
0, # dwStyle
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, # X, Y, nWidth, nHeight
|
||||
# X, Y, nWidth, nHeight
|
||||
win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
|
||||
self.master.winfo_id(), # hWndParent # Don't use HWND_MESSAGE since the window won't get DDE broadcasts
|
||||
None, # hMenu
|
||||
wndclass.hInstance, # hInstance
|
||||
@ -276,13 +241,13 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
#
|
||||
# But it does actually work. Either getting a non-0 value and
|
||||
# entering the loop, or getting 0 and exiting it.
|
||||
while GetMessageW(byref(msg), None, 0, 0) != 0:
|
||||
while win32gui.GetMessage(byref(msg), None, 0, 0) != 0:
|
||||
logger.trace_if('frontier-auth.windows', f'DDE message of type: {msg.message}')
|
||||
if msg.message == WM_DDE_EXECUTE:
|
||||
# GlobalLock does some sort of "please dont move this?"
|
||||
# https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globallock
|
||||
args = wstring_at(GlobalLock(msg.lParam)).strip()
|
||||
GlobalUnlock(msg.lParam) # Unlocks the GlobalLock-ed object
|
||||
args = wstring_at(win32gui.GlobalLock(msg.lParam)).strip()
|
||||
win32gui.GlobalUnlock(msg.lParam) # Unlocks the GlobalLock-ed object
|
||||
|
||||
if args.lower().startswith('open("') and args.endswith('")'):
|
||||
logger.trace_if('frontier-auth.windows', f'args are: {args}')
|
||||
@ -291,20 +256,20 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
logger.debug(f'Message starts with {self.redirect}')
|
||||
self.event(url)
|
||||
|
||||
SetForegroundWindow(GetParent(self.master.winfo_id())) # raise app window
|
||||
win32gui.SetForegroundWindow(win32gui.GetParent(self.master.winfo_id())) # raise app window
|
||||
# Send back a WM_DDE_ACK. this is _required_ with WM_DDE_EXECUTE
|
||||
PostMessageW(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0x80, msg.lParam))
|
||||
win32gui.PostMessage(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0x80, msg.lParam))
|
||||
|
||||
else:
|
||||
# Send back a WM_DDE_ACK. this is _required_ with WM_DDE_EXECUTE
|
||||
PostMessageW(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0, msg.lParam))
|
||||
win32gui.PostMessage(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0, msg.lParam))
|
||||
|
||||
elif msg.message == WM_DDE_TERMINATE:
|
||||
PostMessageW(msg.wParam, WM_DDE_TERMINATE, hwnd, 0)
|
||||
win32gui.PostMessage(msg.wParam, WM_DDE_TERMINATE, hwnd, 0)
|
||||
|
||||
else:
|
||||
TranslateMessage(byref(msg)) # "Translates virtual key messages into character messages" ???
|
||||
DispatchMessageW(byref(msg))
|
||||
win32gui.DispatchMessage(byref(msg))
|
||||
|
||||
|
||||
else: # Linux / Run from source
|
||||
|
@ -42,8 +42,6 @@ pytest==8.2.2
|
||||
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-conditional-plugin==0.9.0
|
||||
# For manipulating folder permissions and the like.
|
||||
pywin32==306; sys_platform == 'win32'
|
||||
|
||||
|
||||
# All of the normal requirements
|
||||
|
@ -3,3 +3,6 @@ pillow==10.3.0
|
||||
watchdog==4.0.1
|
||||
simplesystray==0.1.0; sys_platform == 'win32'
|
||||
semantic-version==2.10.0
|
||||
# For manipulating folder permissions and the like.
|
||||
pywin32==306; sys_platform == 'win32'
|
||||
psutil==5.9.8
|
||||
|
19
stats.py
19
stats.py
@ -25,17 +25,15 @@ logger = EDMCLogging.get_main_logger()
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import HWND, POINT, RECT, SIZE, UINT
|
||||
from ctypes.wintypes import POINT, RECT, SIZE, UINT, BOOL
|
||||
import win32gui
|
||||
|
||||
try:
|
||||
CalculatePopupWindowPosition = ctypes.windll.user32.CalculatePopupWindowPosition
|
||||
CalculatePopupWindowPosition.argtypes = [
|
||||
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)]
|
||||
CalculatePopupWindowPosition.restype = BOOL
|
||||
|
||||
except Exception: # Not supported under Wine 4.0
|
||||
CalculatePopupWindowPosition = None # type: ignore
|
||||
@ -243,7 +241,7 @@ def ships(companion_data: dict[str, Any]) -> list[ShipRet]:
|
||||
"""
|
||||
Return a list of 5 tuples of ship information.
|
||||
|
||||
:param data: [description]
|
||||
:param companion_data: [description]
|
||||
:return: A 5 tuple of strings containing: Ship ID, Ship Type Name (internal), Ship Name, System, Station, and Value
|
||||
"""
|
||||
ships: list[dict[str, Any]] = companion.listify(cast(list, companion_data.get('ships')))
|
||||
@ -253,16 +251,15 @@ def ships(companion_data: dict[str, Any]) -> list[ShipRet]:
|
||||
ships.insert(0, ships.pop(current)) # Put current ship first
|
||||
|
||||
if not companion_data['commander'].get('docked'):
|
||||
out: list[ShipRet] = []
|
||||
# Set current system, not last docked
|
||||
out.append(ShipRet(
|
||||
out: list[ShipRet] = [ShipRet(
|
||||
id=str(ships[0]['id']),
|
||||
type=ship_name_map.get(ships[0]['name'].lower(), ships[0]['name']),
|
||||
name=str(ships[0].get('shipName', '')),
|
||||
system=companion_data['lastSystem']['name'],
|
||||
station='',
|
||||
value=str(ships[0]['value']['total'])
|
||||
))
|
||||
)]
|
||||
out.extend(
|
||||
ShipRet(
|
||||
id=str(ship['id']),
|
||||
@ -302,7 +299,7 @@ def export_ships(companion_data: dict[str, Any], filename: AnyStr) -> None:
|
||||
h.writerow(list(thing))
|
||||
|
||||
|
||||
class StatsDialog():
|
||||
class StatsDialog:
|
||||
"""Status dialog containing all of the current cmdr's stats."""
|
||||
|
||||
def __init__(self, parent: tk.Tk, status: tk.Label) -> None:
|
||||
@ -423,7 +420,7 @@ class StatsResults(tk.Toplevel):
|
||||
# Ensure fully on-screen
|
||||
if sys.platform == 'win32' and CalculatePopupWindowPosition:
|
||||
position = RECT()
|
||||
GetWindowRect(GetParent(self.winfo_id()), position)
|
||||
win32gui.GetWindowRect(win32gui.GetParent(self.winfo_id()))
|
||||
if CalculatePopupWindowPosition(
|
||||
POINT(parent.winfo_rootx(), parent.winfo_rooty()),
|
||||
# - is evidently supported on the C side
|
||||
|
21
theme.py
21
theme.py
@ -33,6 +33,7 @@ if sys.platform == "linux":
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import DWORD, LPCVOID, LPCWSTR
|
||||
import win32gui
|
||||
AddFontResourceEx = ctypes.windll.gdi32.AddFontResourceExW
|
||||
AddFontResourceEx.restypes = [LPCWSTR, DWORD, LPCVOID] # type: ignore
|
||||
FR_PRIVATE = 0x10
|
||||
@ -421,14 +422,7 @@ class _Theme:
|
||||
self.active = theme
|
||||
|
||||
if sys.platform == 'win32':
|
||||
GWL_STYLE = -16 # noqa: N806 # ctypes
|
||||
WS_MAXIMIZEBOX = 0x00010000 # noqa: N806 # ctypes
|
||||
# tk8.5.9/win/tkWinWm.c:342
|
||||
GWL_EXSTYLE = -20 # noqa: N806 # ctypes
|
||||
WS_EX_APPWINDOW = 0x00040000 # 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
|
||||
import win32con
|
||||
|
||||
# FIXME: Lose the "treat this like a boolean" bullshit
|
||||
if theme == self.THEME_DEFAULT:
|
||||
@ -445,14 +439,17 @@ class _Theme:
|
||||
|
||||
root.withdraw()
|
||||
root.update_idletasks() # Size and windows styles get recalculated here
|
||||
hwnd = ctypes.windll.user32.GetParent(root.winfo_id())
|
||||
SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX) # disable maximize
|
||||
hwnd = win32gui.GetParent(root.winfo_id())
|
||||
win32gui.SetWindowLong(hwnd, win32con.GWL_STYLE,
|
||||
win32gui.GetWindowLong(hwnd, win32con.GWL_STYLE)
|
||||
& ~win32con.WS_MAXIMIZEBOX) # disable maximize
|
||||
|
||||
if theme == self.THEME_TRANSPARENT:
|
||||
SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_LAYERED) # Add to taskbar
|
||||
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE,
|
||||
win32con.WS_EX_APPWINDOW | win32con.WS_EX_LAYERED) # Add to taskbar
|
||||
|
||||
else:
|
||||
SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW) # Add to taskbar
|
||||
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, win32con.WS_EX_APPWINDOW) # Add to taskbar
|
||||
|
||||
root.deiconify()
|
||||
root.wait_visibility() # need main window to be displayed before returning
|
||||
|
Loading…
x
Reference in New Issue
Block a user