Files
templates/python/logic_analyzer/dsview_host.py

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)