Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,153 @@
|
||||
"""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/'cthulhu-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/'cthulhu-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/'cthulhu-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/'cthulhu-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()
|
||||
Reference in new issue
Block a user