1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-13 15:57:14 +03:00

Merge branch 'main' into stable

This commit is contained in:
David Sangrey 2024-10-02 19:50:42 -04:00
commit fc00839dfa
No known key found for this signature in database
GPG Key ID: 3AEADBB0186884BC
7 changed files with 109 additions and 62 deletions

View File

@ -6,6 +6,29 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are
in the source (not distributed with the Windows installer) for the in the source (not distributed with the Windows installer) for the
currently used version. currently used version.
--- ---
Release 5.12.1
===
This release fixes a handful of bugs reported with 5.12.0, notably a widely-reported bug with EDMC CAPI Authentication.
**Changes and Enhancements**
* Fixed a typo in the prior release notes
**Bug Fixes**
* Fixed a bug where the EDMC System Profiler wouldn't load details properly
* Reverted a number of usages of Pathlib back to os.path for further validation testing
* Fixed a bug where EDMC would error out with a max() ValueError
* Fixed an issue where the EDMC protocol wouldn't be processed properly via prototyping
**Plugin Developers**
* nb.Entry is deprecated, and is slated for removal in 6.0 or later. Please migrate to nb.EntryMenu
* nb.ColoredButton is deprecated, and is slated for removal in 6.0 or later. Please migrate to tk.Button
* Calling internal translations with `_()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to importing `translations` and calling `translations.translate` or `translations.tl` directly
* `Translations` as the translate system singleton is deprecated, and is slated for removal in 6.0 or later. Please migrate to the `translations` singleton
* `help_open_log_folder()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to open_folder()
* `update_feed` is deprecated, and is slated for removal in 6.0 or later. Please migrate to `get_update_feed()`.
Release 5.12.0 Release 5.12.0
=== ===

11
EDMC.py
View File

@ -14,7 +14,6 @@ import locale
import os import os
import queue import queue
import sys import sys
from pathlib import Path
from time import sleep, time from time import sleep, time
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
@ -213,24 +212,22 @@ def main(): # noqa: C901, CCR001
# system, chances are its the current locale, and not utf-8. Otherwise if it was copied, its probably # system, chances are its the current locale, and not utf-8. Otherwise if it was copied, its probably
# utf8. Either way, try the system FIRST because reading something like cp1251 in UTF-8 results in garbage # utf8. Either way, try the system FIRST because reading something like cp1251 in UTF-8 results in garbage
# but the reverse results in an exception. # but the reverse results in an exception.
json_file = Path(args.j).resolve() json_file = os.path.abspath(args.j)
try: try:
with open(json_file) as file_handle: with open(json_file) as file_handle:
data = json.load(file_handle) data = json.load(file_handle)
except UnicodeDecodeError: except UnicodeDecodeError:
with open(json_file, encoding='utf-8') as file_handle: with open(json_file, encoding='utf-8') as file_handle:
data = json.load(file_handle) data = json.load(file_handle)
file_path = Path(args.j) config.set('querytime', int(os.path.getmtime(args.j)))
modification_time = file_path.stat().st_mtime
config.set('querytime', int(modification_time))
else: else:
# Get state from latest Journal file # Get state from latest Journal file
logger.debug('Getting state from latest journal file') logger.debug('Getting state from latest journal file')
try: try:
monitor.currentdir = Path(config.get_str('journaldir', default=config.default_journal_dir)) monitor.currentdir = config.get_str('journaldir', default=config.default_journal_dir)
if not monitor.currentdir: if not monitor.currentdir:
monitor.currentdir = config.default_journal_dir_path monitor.currentdir = config.default_journal_dir
logger.debug(f'logdir = "{monitor.currentdir}"') logger.debug(f'logdir = "{monitor.currentdir}"')
logfile = monitor.journal_newest_filename(monitor.currentdir) logfile = monitor.journal_newest_filename(monitor.currentdir)

View File

@ -11,7 +11,7 @@ import locale
import webbrowser import webbrowser
import platform import platform
import sys import sys
from os import chdir, environ from os import chdir, environ, path
import pathlib import pathlib
import logging import logging
from journal_lock import JournalLock from journal_lock import JournalLock
@ -19,10 +19,10 @@ from journal_lock import JournalLock
if getattr(sys, "frozen", False): if getattr(sys, "frozen", False):
# Under py2exe sys.path[0] is the executable name # Under py2exe sys.path[0] is the executable name
if sys.platform == "win32": if sys.platform == "win32":
chdir(pathlib.Path(sys.path[0]).parent) chdir(path.dirname(sys.path[0]))
# Allow executable to be invoked from any cwd # Allow executable to be invoked from any cwd
environ['TCL_LIBRARY'] = str(pathlib.Path(sys.path[0]).parent / 'lib' / 'tcl') environ["TCL_LIBRARY"] = path.join(path.dirname(sys.path[0]), "lib", "tcl")
environ['TK_LIBRARY'] = str(pathlib.Path(sys.path[0]).parent / 'lib' / 'tk') environ["TK_LIBRARY"] = path.join(path.dirname(sys.path[0]), "lib", "tk")
else: else:
# We still want to *try* to have CWD be where the main script is, even if # We still want to *try* to have CWD be where the main script is, even if
@ -44,12 +44,11 @@ def get_sys_report(config: config.AbstractConfig) -> str:
plt = platform.uname() plt = platform.uname()
locale.setlocale(locale.LC_ALL, "") locale.setlocale(locale.LC_ALL, "")
lcl = locale.getlocale() lcl = locale.getlocale()
monitor.currentdir = pathlib.Path(config.get_str( monitor.currentdir = config.get_str(
"journaldir", default=config.default_journal_dir "journaldir", default=config.default_journal_dir
)
) )
if not monitor.currentdir: if not monitor.currentdir:
monitor.currentdir = config.default_journal_dir_path monitor.currentdir = config.default_journal_dir
try: try:
logfile = monitor.journal_newest_filename(monitor.currentdir) logfile = monitor.journal_newest_filename(monitor.currentdir)
if logfile is None: if logfile is None:
@ -116,12 +115,12 @@ def main() -> None:
root.withdraw() # Hide the window initially to calculate the dimensions root.withdraw() # Hide the window initially to calculate the dimensions
try: try:
icon_image = tk.PhotoImage( icon_image = tk.PhotoImage(
file=cur_config.respath_path / "io.edcd.EDMarketConnector.png" file=path.join(cur_config.respath_path, "io.edcd.EDMarketConnector.png")
) )
root.iconphoto(True, icon_image) root.iconphoto(True, icon_image)
except tk.TclError: except tk.TclError:
root.iconbitmap(cur_config.respath_path / "EDMarketConnector.ico") root.iconbitmap(path.join(cur_config.respath_path, "EDMarketConnector.ico"))
sys_report = get_sys_report(cur_config) sys_report = get_sys_report(cur_config)

View File

@ -52,7 +52,7 @@ appcmdname = 'EDMC'
# <https://semver.org/#semantic-versioning-specification-semver> # <https://semver.org/#semantic-versioning-specification-semver>
# Major.Minor.Patch(-prerelease)(+buildmetadata) # Major.Minor.Patch(-prerelease)(+buildmetadata)
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild() # NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
_static_appversion = '5.12.0' _static_appversion = '5.12.1'
_cached_version: semantic_version.Version | None = None _cached_version: semantic_version.Version | None = None
copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'

View File

@ -8,7 +8,7 @@ See LICENSE file.
from __future__ import annotations from __future__ import annotations
import json import json
from pathlib import Path import pathlib
import queue import queue
import re import re
import sys import sys
@ -16,6 +16,7 @@ import threading
from calendar import timegm from calendar import timegm
from collections import defaultdict from collections import defaultdict
from os import SEEK_END, SEEK_SET, listdir from os import SEEK_END, SEEK_SET, listdir
from os.path import basename, expanduser, getctime, isdir, join
from time import gmtime, localtime, mktime, sleep, strftime, strptime, time from time import gmtime, localtime, mktime, sleep, strftime, strptime, time
from typing import TYPE_CHECKING, Any, BinaryIO, MutableMapping from typing import TYPE_CHECKING, Any, BinaryIO, MutableMapping
import psutil import psutil
@ -67,7 +68,7 @@ class EDLogs(FileSystemEventHandler):
# TODO(A_D): A bunch of these should be switched to default values (eg '' for strings) and no longer be Optional # TODO(A_D): A bunch of these should be switched to default values (eg '' for strings) and no longer be Optional
FileSystemEventHandler.__init__(self) # futureproofing - not need for current version of watchdog FileSystemEventHandler.__init__(self) # futureproofing - not need for current version of watchdog
self.root: 'tkinter.Tk' = None # type: ignore # Don't use Optional[] - mypy thinks no methods self.root: 'tkinter.Tk' = None # type: ignore # Don't use Optional[] - mypy thinks no methods
self.currentdir: Path | None = None # The actual logdir that we're monitoring self.currentdir: str | None = None # The actual logdir that we're monitoring
self.logfile: str | None = None self.logfile: str | None = None
self.observer: BaseObserver | None = None self.observer: BaseObserver | None = None
self.observed = None # a watchdog ObservedWatch, or None if polling self.observed = None # a watchdog ObservedWatch, or None if polling
@ -190,9 +191,9 @@ class EDLogs(FileSystemEventHandler):
if journal_dir == '' or journal_dir is None: if journal_dir == '' or journal_dir is None:
journal_dir = config.default_journal_dir journal_dir = config.default_journal_dir
logdir = Path(journal_dir).expanduser() logdir = expanduser(journal_dir)
if not logdir or not Path.is_dir(logdir): if not logdir or not isdir(logdir):
logger.error(f'Journal Directory is invalid: "{logdir}"') logger.error(f'Journal Directory is invalid: "{logdir}"')
self.stop() self.stop()
return False return False
@ -265,10 +266,9 @@ class EDLogs(FileSystemEventHandler):
# Odyssey Update 11 has, e.g. Journal.2022-03-15T152503.01.log # Odyssey Update 11 has, e.g. Journal.2022-03-15T152503.01.log
# Horizons Update 11 equivalent: Journal.220315152335.01.log # Horizons Update 11 equivalent: Journal.220315152335.01.log
# So we can no longer use a naive sort. # So we can no longer use a naive sort.
journals_dir_path = Path(journals_dir) journals_dir_path = pathlib.Path(journals_dir)
journal_files = (journals_dir_path / Path(x) for x in journal_files) journal_files = (journals_dir_path / pathlib.Path(x) for x in journal_files)
latest_file = max(journal_files, key=lambda f: Path(f).stat().st_ctime) return str(max(journal_files, key=getctime))
return str(latest_file)
return None return None
@ -337,7 +337,7 @@ class EDLogs(FileSystemEventHandler):
def on_created(self, event: 'FileSystemEvent') -> None: def on_created(self, event: 'FileSystemEvent') -> None:
"""Watchdog callback when, e.g. client (re)started.""" """Watchdog callback when, e.g. client (re)started."""
if not event.is_directory and self._RE_LOGFILE.search(Path(event.src_path).name): if not event.is_directory and self._RE_LOGFILE.search(basename(event.src_path)):
self.logfile = event.src_path self.logfile = event.src_path
@ -1076,7 +1076,7 @@ class EDLogs(FileSystemEventHandler):
self.state['Cargo'] = defaultdict(int) self.state['Cargo'] = defaultdict(int)
# From 3.3 full Cargo event (after the first one) is written to a separate file # From 3.3 full Cargo event (after the first one) is written to a separate file
if 'Inventory' not in entry: if 'Inventory' not in entry:
with open(self.currentdir / 'Cargo.json', 'rb') as h: # type: ignore with open(join(self.currentdir, 'Cargo.json'), 'rb') as h: # type: ignore
entry = json.load(h) entry = json.load(h)
self.state['CargoJSON'] = entry self.state['CargoJSON'] = entry
@ -1103,7 +1103,7 @@ class EDLogs(FileSystemEventHandler):
# Always attempt loading of this, but if it fails we'll hope this was # Always attempt loading of this, but if it fails we'll hope this was
# a startup/boarding version and thus `entry` contains # a startup/boarding version and thus `entry` contains
# the data anyway. # the data anyway.
currentdir_path = Path(str(self.currentdir)) currentdir_path = pathlib.Path(str(self.currentdir))
shiplocker_filename = currentdir_path / 'ShipLocker.json' shiplocker_filename = currentdir_path / 'ShipLocker.json'
shiplocker_max_attempts = 5 shiplocker_max_attempts = 5
shiplocker_fail_sleep = 0.01 shiplocker_fail_sleep = 0.01
@ -1172,7 +1172,7 @@ class EDLogs(FileSystemEventHandler):
# TODO: v31 doc says this is`backpack.json` ... but Howard Chalkley # TODO: v31 doc says this is`backpack.json` ... but Howard Chalkley
# said it's `Backpack.json` # said it's `Backpack.json`
backpack_file = Path(str(self.currentdir)) / 'Backpack.json' backpack_file = pathlib.Path(str(self.currentdir)) / 'Backpack.json'
backpack_data = None backpack_data = None
if not backpack_file.exists(): if not backpack_file.exists():
@ -1548,7 +1548,7 @@ class EDLogs(FileSystemEventHandler):
entry = fcmaterials entry = fcmaterials
elif event_type == 'moduleinfo': elif event_type == 'moduleinfo':
with open(self.currentdir / 'ModulesInfo.json', 'rb') as mf: # type: ignore with open(join(self.currentdir, 'ModulesInfo.json'), 'rb') as mf: # type: ignore
try: try:
entry = json.load(mf) entry = json.load(mf)
@ -2259,14 +2259,14 @@ class EDLogs(FileSystemEventHandler):
oldfiles = sorted((x for x in listdir(config.get_str('outdir')) if regexp.match(x))) oldfiles = sorted((x for x in listdir(config.get_str('outdir')) if regexp.match(x)))
if oldfiles: if oldfiles:
try: try:
with open(config.get_str('outdir') / Path(oldfiles[-1]), encoding='utf-8') as h: with open(join(config.get_str('outdir'), oldfiles[-1]), encoding='utf-8') as h:
if h.read() == string: if h.read() == string:
return # same as last time - don't write return # same as last time - don't write
except UnicodeError: except UnicodeError:
logger.exception("UnicodeError reading old ship loadout with utf-8 encoding, trying without...") logger.exception("UnicodeError reading old ship loadout with utf-8 encoding, trying without...")
try: try:
with open(config.get_str('outdir') / Path(oldfiles[-1])) as h: with open(join(config.get_str('outdir'), oldfiles[-1])) as h:
if h.read() == string: if h.read() == string:
return # same as last time - don't write return # same as last time - don't write
@ -2285,7 +2285,7 @@ class EDLogs(FileSystemEventHandler):
# Write # Write
ts = strftime('%Y-%m-%dT%H.%M.%S', localtime(time())) ts = strftime('%Y-%m-%dT%H.%M.%S', localtime(time()))
filename = config.get_str('outdir') / Path(f'{ship}.{ts}.txt') filename = join(config.get_str('outdir'), f'{ship}.{ts}.txt')
try: try:
with open(filename, 'wt', encoding='utf-8') as h: with open(filename, 'wt', encoding='utf-8') as h:
@ -2372,7 +2372,7 @@ class EDLogs(FileSystemEventHandler):
try: try:
with open(self.currentdir / 'NavRoute.json') as f: with open(join(self.currentdir, 'NavRoute.json')) as f:
raw = f.read() raw = f.read()
except Exception as e: except Exception as e:
@ -2398,7 +2398,7 @@ class EDLogs(FileSystemEventHandler):
try: try:
with open(self.currentdir / 'FCMaterials.json') as f: with open(join(self.currentdir, 'FCMaterials.json')) as f:
raw = f.read() raw = f.read()
except Exception as e: except Exception as e:

View File

@ -4,7 +4,7 @@ from __future__ import annotations
import contextlib import contextlib
import logging import logging
from os.path import expandvars from os.path import expandvars, join, normpath
from pathlib import Path from pathlib import Path
import subprocess import subprocess
import sys import sys
@ -1100,7 +1100,7 @@ class PreferencesDialog(tk.Toplevel):
if sys.platform == 'win32': if sys.platform == 'win32':
start = len(config.home.split('\\')) if pathvar.get().lower().startswith(config.home.lower()) else 0 start = len(config.home.split('\\')) if pathvar.get().lower().startswith(config.home.lower()) else 0
display = [] display = []
components = Path(pathvar.get()).resolve().parts components = normpath(pathvar.get()).split('\\')
buf = ctypes.create_unicode_buffer(MAX_PATH) buf = ctypes.create_unicode_buffer(MAX_PATH)
pidsRes = ctypes.c_int() # noqa: N806 # Windows convention pidsRes = ctypes.c_int() # noqa: N806 # Windows convention
for i in range(start, len(components)): for i in range(start, len(components)):
@ -1253,7 +1253,7 @@ class PreferencesDialog(tk.Toplevel):
config.set( config.set(
'outdir', 'outdir',
str(config.home_path / self.outdir.get()[2:]) if self.outdir.get().startswith('~') else self.outdir.get() join(config.home_path, self.outdir.get()[2:]) if self.outdir.get().startswith('~') else self.outdir.get()
) )
logdir = self.logdir.get() logdir = self.logdir.get()
@ -1296,8 +1296,8 @@ class PreferencesDialog(tk.Toplevel):
if self.plugdir.get() != config.get('plugin_dir'): if self.plugdir.get() != config.get('plugin_dir'):
config.set( config.set(
'plugin_dir', 'plugin_dir',
str(Path(config.home_path, self.plugdir.get()[2:])) if self.plugdir.get().startswith('~') else join(config.home_path, self.plugdir.get()[2:]) if self.plugdir.get().startswith(
str(Path(self.plugdir.get())) '~') else self.plugdir.get()
) )
self.req_restart = True self.req_restart = True

View File

@ -69,16 +69,14 @@ 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 # This could be false if you use auth_force_edmc_protocol, but then you get to keep the pieces
assert sys.platform == 'win32' assert sys.platform == 'win32'
# spell-checker: words HBRUSH HICON WPARAM wstring WNDCLASS HMENU HGLOBAL # spell-checker: words HBRUSH HICON WPARAM wstring WNDCLASS HMENU HGLOBAL
from ctypes import ( from ctypes import ( # type: ignore
windll, WINFUNCTYPE, Structure, byref, c_long, c_void_p, create_unicode_buffer, wstring_at windll, POINTER, WINFUNCTYPE, Structure, byref, c_long, c_void_p, create_unicode_buffer, wstring_at
) )
from ctypes.wintypes import ( from ctypes.wintypes import (
ATOM, HBRUSH, HICON, HINSTANCE, HWND, INT, LPARAM, LPCWSTR, LPWSTR, ATOM, BOOL, DWORD, HBRUSH, HGLOBAL, HICON, HINSTANCE, HMENU, HWND, INT, LPARAM, LPCWSTR, LPMSG, LPVOID, LPWSTR,
MSG, UINT, WPARAM MSG, UINT, WPARAM
) )
import win32gui import win32gui
import win32con
import win32api
class WNDCLASS(Structure): class WNDCLASS(Structure):
""" """
@ -101,8 +99,33 @@ if (config.auth_force_edmc_protocol # noqa: C901
('lpszClassName', LPCWSTR) ('lpszClassName', LPCWSTR)
] ]
TranslateMessage = windll.user32.TranslateMessage 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)]
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 # https://docs.microsoft.com/en-us/windows/win32/dataxchg/wm-dde-initiate
WM_DDE_INITIATE = 0x03E0 WM_DDE_INITIATE = 0x03E0
WM_DDE_TERMINATE = 0x03E1 WM_DDE_TERMINATE = 0x03E1
@ -118,6 +141,12 @@ if (config.auth_force_edmc_protocol # noqa: C901
GlobalGetAtomNameW = windll.kernel32.GlobalGetAtomNameW GlobalGetAtomNameW = windll.kernel32.GlobalGetAtomNameW
GlobalGetAtomNameW.argtypes = [ATOM, LPWSTR, INT] GlobalGetAtomNameW.argtypes = [ATOM, LPWSTR, INT]
GlobalGetAtomNameW.restype = UINT 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) # Windows Message handler stuff (IPC)
# https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633573(v=vs.85) # https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633573(v=vs.85)
@ -160,7 +189,7 @@ if (config.auth_force_edmc_protocol # noqa: C901
if target_is_valid and topic_is_valid: if target_is_valid and topic_is_valid:
# if everything is happy, send an acknowledgement of the DDE request # if everything is happy, send an acknowledgement of the DDE request
win32gui.SendMessage( SendMessageW(
wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, GlobalAddAtomW(appname), GlobalAddAtomW('System')) wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, GlobalAddAtomW(appname), GlobalAddAtomW('System'))
) )
@ -193,7 +222,7 @@ if (config.auth_force_edmc_protocol # noqa: C901
thread = self.thread thread = self.thread
if thread: if thread:
self.thread = None self.thread = None
win32api.PostThreadMessage(thread.ident, win32con.WM_QUIT, 0, 0) PostThreadMessageW(thread.ident, WM_QUIT, 0, 0)
thread.join() # Wait for it to quit thread.join() # Wait for it to quit
def worker(self) -> None: def worker(self) -> None:
@ -203,25 +232,24 @@ if (config.auth_force_edmc_protocol # noqa: C901
wndclass.lpfnWndProc = WndProc wndclass.lpfnWndProc = WndProc
wndclass.cbClsExtra = 0 wndclass.cbClsExtra = 0
wndclass.cbWndExtra = 0 wndclass.cbWndExtra = 0
wndclass.hInstance = win32gui.GetModuleHandle(0) wndclass.hInstance = windll.kernel32.GetModuleHandleW(0)
wndclass.hIcon = None wndclass.hIcon = None
wndclass.hCursor = None wndclass.hCursor = None
wndclass.hbrBackground = None wndclass.hbrBackground = None
wndclass.lpszMenuName = None wndclass.lpszMenuName = None
wndclass.lpszClassName = 'DDEServer' wndclass.lpszClassName = 'DDEServer'
if not win32gui.RegisterClass(byref(wndclass)): if not RegisterClassW(byref(wndclass)):
print('Failed to register Dynamic Data Exchange for cAPI') print('Failed to register Dynamic Data Exchange for cAPI')
return return
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
hwnd = win32gui.CreateWindowEx( hwnd = CreateWindowExW(
0, # dwExStyle 0, # dwExStyle
wndclass.lpszClassName, # lpClassName wndclass.lpszClassName, # lpClassName
"DDE Server", # lpWindowName "DDE Server", # lpWindowName
0, # dwStyle 0, # dwStyle
# X, Y, nWidth, nHeight CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, # 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 self.master.winfo_id(), # hWndParent # Don't use HWND_MESSAGE since the window won't get DDE broadcasts
None, # hMenu None, # hMenu
wndclass.hInstance, # hInstance wndclass.hInstance, # hInstance
@ -241,13 +269,13 @@ if (config.auth_force_edmc_protocol # noqa: C901
# #
# But it does actually work. Either getting a non-0 value and # But it does actually work. Either getting a non-0 value and
# entering the loop, or getting 0 and exiting it. # entering the loop, or getting 0 and exiting it.
while win32gui.GetMessage(byref(msg), None, 0, 0) != 0: while GetMessageW(byref(msg), None, 0, 0) != 0:
logger.trace_if('frontier-auth.windows', f'DDE message of type: {msg.message}') logger.trace_if('frontier-auth.windows', f'DDE message of type: {msg.message}')
if msg.message == WM_DDE_EXECUTE: if msg.message == WM_DDE_EXECUTE:
# GlobalLock does some sort of "please dont move this?" # GlobalLock does some sort of "please dont move this?"
# https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globallock # https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globallock
args = wstring_at(win32gui.GlobalLock(msg.lParam)).strip() args = wstring_at(GlobalLock(msg.lParam)).strip()
win32gui.GlobalUnlock(msg.lParam) # Unlocks the GlobalLock-ed object GlobalUnlock(msg.lParam) # Unlocks the GlobalLock-ed object
if args.lower().startswith('open("') and args.endswith('")'): if args.lower().startswith('open("') and args.endswith('")'):
logger.trace_if('frontier-auth.windows', f'args are: {args}') logger.trace_if('frontier-auth.windows', f'args are: {args}')
@ -256,20 +284,20 @@ if (config.auth_force_edmc_protocol # noqa: C901
logger.debug(f'Message starts with {self.redirect}') logger.debug(f'Message starts with {self.redirect}')
self.event(url) self.event(url)
win32gui.SetForegroundWindow(win32gui.GetParent(self.master.winfo_id())) # raise app window SetForegroundWindow(GetParent(self.master.winfo_id())) # raise app window
# Send back a WM_DDE_ACK. this is _required_ with WM_DDE_EXECUTE # Send back a WM_DDE_ACK. this is _required_ with WM_DDE_EXECUTE
win32gui.PostMessage(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0x80, msg.lParam)) PostMessageW(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0x80, msg.lParam))
else: else:
# Send back a WM_DDE_ACK. this is _required_ with WM_DDE_EXECUTE # Send back a WM_DDE_ACK. this is _required_ with WM_DDE_EXECUTE
win32gui.PostMessage(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0, msg.lParam)) PostMessageW(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0, msg.lParam))
elif msg.message == WM_DDE_TERMINATE: elif msg.message == WM_DDE_TERMINATE:
win32gui.PostMessage(msg.wParam, WM_DDE_TERMINATE, hwnd, 0) PostMessageW(msg.wParam, WM_DDE_TERMINATE, hwnd, 0)
else: else:
TranslateMessage(byref(msg)) # "Translates virtual key messages into character messages" ??? TranslateMessage(byref(msg)) # "Translates virtual key messages into character messages" ???
win32gui.DispatchMessage(byref(msg)) DispatchMessageW(byref(msg))
else: # Linux / Run from source else: # Linux / Run from source
@ -348,7 +376,7 @@ else: # Linux / Run from source
if self.parse(): if self.parse():
self.send_header('Content-Type', 'text/html') self.send_header('Content-Type', 'text/html')
self.end_headers() self.end_headers()
self.wfile.write(self._generate_auth_response().encode('utf-8')) self.wfile.write(self._generate_auth_response().encode())
else: else:
self.send_response(404) self.send_response(404)
self.end_headers() self.end_headers()