88 lines
3.2 KiB
Python
88 lines
3.2 KiB
Python
"""Process boundary for the patched DSView GUI (host protocol 1, Windows).
|
|
|
|
The host owns only its child process/window. Qt 5, Python and libusb remain
|
|
inside DSView; they must never be loaded into the hosting application's Qt.
|
|
"""
|
|
from contextlib import contextmanager
|
|
import ctypes
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
|
|
def resolve_runtime(bundled, executable=None, frozen=None):
|
|
"""A dsview folder beside the GUI EXE overrides its bundled runtime."""
|
|
frozen = getattr(sys, 'frozen', False) if frozen is None else frozen
|
|
if frozen:
|
|
external = Path(executable or sys.executable).resolve().parent / 'dsview'
|
|
if external.exists():
|
|
# An incomplete external install should be reported, not silently
|
|
# replaced with an older bundled component.
|
|
return external
|
|
return Path(bundled)
|
|
|
|
|
|
def child_environment(runtime, stylesheet, capture_directory, environment=None):
|
|
"""Remove Python/Qt/PyInstaller overrides before starting the private runtime."""
|
|
env = dict(os.environ if environment is None else environment)
|
|
for key in list(env):
|
|
if key.upper().startswith(('PYTHON', 'QT_', 'QML', 'PYSIDE', '_PYI', '_MEIPASS')):
|
|
env.pop(key)
|
|
windows = Path(env.get('SystemRoot', r'C:\Windows'))
|
|
env['PATH'] = os.pathsep.join(map(str, (Path(runtime), windows / 'System32', windows)))
|
|
env['DSVIEW_HOST_STYLE'] = str(stylesheet)
|
|
env['DSVIEW_CAPTURE_DIR'] = str(capture_directory)
|
|
env['PYTHONDONTWRITEBYTECODE'] = '1'
|
|
return env
|
|
|
|
|
|
def ready_window(line):
|
|
"""Only accept the versioned child's explicit ready message."""
|
|
fields = line.strip().split()
|
|
if len(fields) == 2 and fields[0] == 'DSVIEW_READY':
|
|
try:
|
|
value = int(fields[1])
|
|
return value if value > 0 else None
|
|
except ValueError:
|
|
pass
|
|
return None
|
|
|
|
|
|
def window_api():
|
|
from ctypes import wintypes
|
|
api = ctypes.WinDLL('user32', use_last_error=True)
|
|
api.GetWindowThreadProcessId.argtypes = [wintypes.HWND, ctypes.POINTER(wintypes.DWORD)]
|
|
api.GetWindowThreadProcessId.restype = wintypes.DWORD
|
|
api.GetParent.argtypes = [wintypes.HWND]
|
|
api.GetParent.restype = wintypes.HWND
|
|
return api
|
|
|
|
|
|
def owns_window(window, pid, parent=None):
|
|
if sys.platform != 'win32' or not window or not pid:
|
|
return False
|
|
from ctypes import wintypes
|
|
api = window_api()
|
|
owner = wintypes.DWORD()
|
|
api.GetWindowThreadProcessId(window, ctypes.byref(owner))
|
|
return owner.value == pid and (parent is None or api.GetParent(window) == parent)
|
|
|
|
|
|
@contextmanager
|
|
def independent_dll_directory():
|
|
"""Do not inherit PyInstaller's Qt DLL search directory into DSView."""
|
|
if sys.platform != 'win32' or not getattr(sys, 'frozen', False):
|
|
yield
|
|
return
|
|
api = ctypes.WinDLL('kernel32', use_last_error=True)
|
|
api.GetDllDirectoryW.argtypes = [ctypes.c_uint32, ctypes.c_wchar_p]
|
|
api.SetDllDirectoryW.argtypes = [ctypes.c_wchar_p]
|
|
buffer = ctypes.create_unicode_buffer(32768)
|
|
api.GetDllDirectoryW(len(buffer), buffer)
|
|
if not api.SetDllDirectoryW(None):
|
|
raise ctypes.WinError(ctypes.get_last_error())
|
|
try:
|
|
yield
|
|
finally:
|
|
api.SetDllDirectoryW(buffer.value or None)
|