"""Private-display physical-key driver for the Go Wine review acceptance test.""" import ctypes import json import os import re from pathlib import Path import subprocess import sys import tempfile import time sys.dont_write_bytecode = True from native_x11 import set_active_window, wait_for def chord(symbols): x11 = ctypes.CDLL('libX11.so.6') xtst = ctypes.CDLL('libXtst.so.6') x11.XOpenDisplay.argtypes = [ctypes.c_char_p] x11.XOpenDisplay.restype = ctypes.c_void_p x11.XKeysymToKeycode.argtypes = [ctypes.c_void_p, ctypes.c_ulong] x11.XKeysymToKeycode.restype = ctypes.c_ubyte x11.XSync.argtypes = [ctypes.c_void_p, ctypes.c_int] x11.XCloseDisplay.argtypes = [ctypes.c_void_p] xtst.XTestFakeKeyEvent.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_int, ctypes.c_ulong] display = x11.XOpenDisplay(None) assert display codes = [x11.XKeysymToKeycode(display, value) for value in symbols] assert all(codes) try: for code in codes: assert xtst.XTestFakeKeyEvent(display, code, 1, 0) for code in reversed(codes): assert xtst.XTestFakeKeyEvent(display, code, 0, 0) x11.XSync(display, 0) finally: x11.XCloseDisplay(display) def main(): build = Path(os.environ['CTHULHU_WINE_REVIEW_BUILD']) fixture = build / ('review-fixture32.exe' if os.environ.get('CTHULHU_WINE_REVIEW_32') == '1' else 'review-fixture64.exe') processes = [] window_manager = None with tempfile.TemporaryDirectory(prefix='wine-review-keys-') as directory: if os.environ.get('CTHULHU_WINE_REVIEW_WM') == 'i3': config=Path(directory)/'i3.conf' config.write_text('font pango:monospace 10\ndefault_border pixel 2\nfocus_follows_mouse no\n') window_manager=subprocess.Popen(['i3','-a','-c',str(config)],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) wait_for(lambda: '0x' in subprocess.run(['xprop','-root','_NET_SUPPORTING_WM_CHECK'],capture_output=True,text=True).stdout,'private i3 did not start',5) env = dict(os.environ, WINEPREFIX=str(Path(directory)/'prefix'), WINEDEBUG='-all', WINEDLLOVERRIDES='mscoree,mshtml=', CTHULHU_WINE_ACCESS_MANAGED='1') secondary_env, secondary_window = None, None def configure(mode): subprocess.run(['wine', str(fixture), '--configure', str(mode)],env=env,check=True,timeout=5,stdout=subprocess.DEVNULL) def activate(): found = subprocess.run(['xdotool','search','--onlyvisible','--name','^Wine review fixture$'],capture_output=True,text=True) if not found.stdout.strip():return False candidates = [int(value) for value in found.stdout.splitlines() if int(value) != secondary_window] if not candidates:return False window = candidates[0] if window_manager is not None: subprocess.run(['xdotool','windowactivate','--sync',str(window)],check=True) subprocess.run(['xdotool','windowfocus',str(window)],check=True) set_active_window(window) return window linux = None def activate_linux(): nonlocal linux if linux is None: source = ('import gi; gi.require_version("Gtk", "3.0"); ' 'from gi.repository import Gtk, GLib; ' 'window=Gtk.Window(title="Wine review Linux fixture"); ' 'window.set_default_size(400,250); view=Gtk.TextView(); ' 'view.get_buffer().set_text("Linux review ready\\nnext Linux"); ' 'view.get_buffer().place_cursor(view.get_buffer().get_start_iter()); ' 'window.add(view); window.show_all(); view.grab_focus(); Gtk.main()') linux = subprocess.Popen([sys.executable, '-c', source],stdout=subprocess.DEVNULL) processes.append(linux) def focus(): result = subprocess.run(['xdotool','search','--onlyvisible','--name','^Wine review Linux fixture$'],capture_output=True,text=True) if not result.stdout.strip():return False window = int(result.stdout.splitlines()[0]) subprocess.run(['xdotool','windowfocus',str(window)],check=True) set_active_window(window) return True wait_for(focus,'Linux fixture missing',5) try: subprocess.run(['wineboot','-u'],env=env,check=True,timeout=45,stdout=subprocess.DEVNULL) processes.append(subprocess.Popen([str(build/'wine-access.exe'),'--no-controller'],env=env,stdout=subprocess.DEVNULL)) processes.append(subprocess.Popen(['wine',str(fixture)],env=env,stdout=subprocess.DEVNULL)) wait_for(activate,'native Wine window missing',10) window = activate() diagnostics = subprocess.run(['xprop', '-id', str(window), 'WM_CLASS', '_NET_WM_PID'],capture_output=True,text=True) print(diagnostics.stdout,file=sys.stderr,flush=True) match = re.search(r'_NET_WM_PID\(CARDINAL\) = (\d+)', diagnostics.stdout) if match:print('Wine executable: '+os.readlink('/proc/'+match.group(1)+'/exe'),file=sys.stderr,flush=True) configure(12) print(json.dumps({'ready':True}),flush=True) for line in sys.stdin: command = json.loads(line) action = command['action'] if action == 'keys':chord(command['symbols']) elif action == 'configure':configure(command['mode']);activate() elif action == 'check': subprocess.run(['wine',str(fixture),command.get('check','--check-strict')],env=env,check=True,timeout=5,stdout=subprocess.DEVNULL) elif action == 'restart-helper': processes[0].terminate();processes[0].wait(timeout=3) processes[0]=subprocess.Popen([str(build/'wine-access.exe'),'--no-controller'],env=env,stdout=subprocess.DEVNULL) elif action == 'linux':activate_linux() elif action == 'wine':activate() elif action == 'second-prefix': known = set(subprocess.run(['xdotool','search','--onlyvisible','--name','^Wine review fixture$'],capture_output=True,text=True).stdout.splitlines()) secondary_env = dict(env, WINEPREFIX=str(Path(directory)/'second-prefix')) subprocess.run(['wineboot','-u'],env=secondary_env,check=True,timeout=45,stdout=subprocess.DEVNULL) processes.append(subprocess.Popen([str(build/'wine-access.exe'),'--no-controller'],env=secondary_env,stdout=subprocess.DEVNULL)) processes.append(subprocess.Popen(['wine',str(fixture)],env=secondary_env,stdout=subprocess.DEVNULL)) def focus_second(): found = subprocess.run(['xdotool','search','--onlyvisible','--name','^Wine review fixture$'],capture_output=True,text=True) fresh = [value for value in found.stdout.splitlines() if value not in known] return int(fresh[0]) if fresh else None secondary_window = wait_for(focus_second,'second-prefix window missing',10) subprocess.run(['xdotool','windowfocus',str(secondary_window)],check=True) set_active_window(secondary_window) elif action == 'virtual-desktop': # Configure the same default desktop used by winecfg. A # helper on another named Windows desktop cannot inspect # this desktop's foreground, even within the same prefix. subprocess.run(['wine','reg','add',r'HKCU\Software\Wine\Explorer','/v','Desktop','/d','Default','/f'],env=env,check=True,stdout=subprocess.DEVNULL,timeout=5) subprocess.run(['wine','reg','add',r'HKCU\Software\Wine\Explorer\Desktops','/v','Default','/d','900x700','/f'],env=env,check=True,stdout=subprocess.DEVNULL,timeout=5) subprocess.run(['wineserver','-k'],env=env,check=True,timeout=5) processes[0].wait(timeout=3);processes[1].wait(timeout=3) processes[0]=subprocess.Popen([str(build/'wine-access.exe'),'--no-controller'],env=env,stdout=subprocess.DEVNULL) processes[1]=subprocess.Popen(['wine',str(fixture)],env=env,stdout=subprocess.DEVNULL) window = wait_for(activate,'virtual-desktop app missing',10) result = subprocess.run(['xdotool','search','--onlyvisible','--name','Wine Desktop'],capture_output=True,text=True,check=True) assert result.stdout.strip(), 'Wine desktop container missing' subprocess.run(['xdotool','windowfocus',str(window)],check=True) set_active_window(int(result.stdout.splitlines()[0])) diagnostics = subprocess.run(['xwininfo','-id',str(window),'-tree'],capture_output=True,text=True) print(diagnostics.stdout,file=sys.stderr,flush=True) elif action == 'quit':break else:raise ValueError(action) print(json.dumps({'ready':True}),flush=True) finally: for process in reversed(processes): if process.poll() is None: process.terminate() try:process.wait(timeout=3) except subprocess.TimeoutExpired:process.kill();process.wait(timeout=3) subprocess.run(['wineserver','-k'],env=env,timeout=5) if secondary_env is not None:subprocess.run(['wineserver','-k'],env=secondary_env,timeout=5) if window_manager is not None: window_manager.terminate();window_manager.wait(timeout=3) if __name__ == '__main__':main()