"""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)