Better handling of some qt and gtk applications. Used Audacious for testing.
This commit is contained in:
1 parent
300f4a7504
commit
bbe931335f
15 files changed
+507
-16
No files matched your search
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2026 Storm Dragon
|
||||
# Authors: Storm Dragon, Angus Kola
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Exercise real Audacious GUIs without the desktop player's session or files."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import wave
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
from native_test_session import run_private_session
|
||||
from native_x11 import wait_for
|
||||
|
||||
|
||||
def run_inside():
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
temporary = Path(os.environ['XDG_CONFIG_HOME'])
|
||||
configPath = temporary / 'i3.conf'
|
||||
configPath.write_text('font pango:monospace 10\nfocus_follows_mouse no\nipc-socket ' +
|
||||
str(temporary / 'i3.sock') + '\n', encoding='utf-8')
|
||||
manager = subprocess.Popen(['i3', '-c', str(configPath)], stdout=subprocess.DEVNULL)
|
||||
process = None
|
||||
try:
|
||||
wait_for(lambda: subprocess.run(['i3-msg', '-t', 'get_version'],
|
||||
capture_output=True).returncode == 0, 'private i3 missing')
|
||||
tracks = []
|
||||
trackDirectory = temporary / 'Example Artist' / 'Example Album'
|
||||
trackDirectory.mkdir(parents=True)
|
||||
for name in ('Alpha', 'Beta', 'Gamma'):
|
||||
path = trackDirectory / (name + '.wav')
|
||||
with wave.open(str(path), 'wb') as track:
|
||||
track.setparams((1, 2, 8000, 0, 'NONE', 'not compressed'))
|
||||
track.writeframes(b'\0\0' * 8000)
|
||||
tracks.append(str(path))
|
||||
process = subprocess.Popen(['audacious', '-2', os.environ['CTHULHU_NATIVE_AUDACIOUS'], '-e', *tracks])
|
||||
|
||||
def find_window():
|
||||
result = subprocess.run(['xdotool', 'search', '--onlyvisible', '--name', '^Audacious \\(2\\)$'],
|
||||
capture_output=True, text=True)
|
||||
return result.stdout.strip().splitlines()[0] if result.stdout.strip() else None
|
||||
|
||||
wait_for(find_window, 'Audacious window missing')
|
||||
import pyatspi
|
||||
|
||||
def find_playlist(node):
|
||||
if node.getRoleName() in ('tree', 'table'):
|
||||
table = node.queryTable()
|
||||
if table.nRows == 3:
|
||||
return table
|
||||
for child in node:
|
||||
result = find_playlist(child)
|
||||
if result is not None:
|
||||
return result
|
||||
return None
|
||||
|
||||
playlist = wait_for(lambda: find_playlist(pyatspi.Registry.getDesktop(0)),
|
||||
'Audacious playlist missing')
|
||||
assert playlist.addRowSelection(0), 'could not select first fixture row'
|
||||
if os.environ['CTHULHU_NATIVE_AUDACIOUS'] == '-G':
|
||||
assert playlist.getAccessibleAt(0, 0).queryComponent().grabFocus(), 'could not focus first fixture cell'
|
||||
# Selecting through AT-SPI does not initialize Audacious's GTK
|
||||
# keyboard cursor. The first Down establishes its first row.
|
||||
subprocess.run(['xdotool', 'key', '--clearmodifiers', 'Down'], check=True)
|
||||
wait_for(lambda: playlist.getSelectedRows() == [0], 'GTK cursor did not select first row')
|
||||
subprocess.run(['go', 'test', './internal/app', '-run', '^TestNativeAudacious$',
|
||||
'-count=1', '-v', '-timeout=90s'], cwd=root, check=True, timeout=120)
|
||||
finally:
|
||||
if process is not None:
|
||||
process.terminate()
|
||||
process.wait(timeout=5)
|
||||
manager.terminate()
|
||||
manager.wait(timeout=5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if '--diagnose' in sys.argv:
|
||||
import pyatspi
|
||||
def describe(node, depth=0):
|
||||
if depth > 8:
|
||||
return
|
||||
if node.getRoleName() in ('tree', 'table', 'frame', 'table cell'):
|
||||
print(node.getRoleName(), repr(node.name), node.getState().getStates(), flush=True)
|
||||
if node.getRoleName() in ('tree', 'table'):
|
||||
table = node.queryTable()
|
||||
print('ROWS', table.getSelectedRows(), flush=True)
|
||||
for row in range(table.nRows):
|
||||
cell = table.getAccessibleAt(row, 0)
|
||||
print('CELL', repr(cell.name), cell.getState().getStates(), flush=True)
|
||||
for child in node:
|
||||
describe(child, depth+1)
|
||||
for app in pyatspi.Registry.getDesktop(0):
|
||||
if app.name.lower() == 'audacious':
|
||||
describe(app)
|
||||
elif '--inside' in sys.argv:
|
||||
run_inside()
|
||||
else:
|
||||
moduleCache = subprocess.check_output(['go', 'env', 'GOMODCACHE'], text=True).strip()
|
||||
for mode in ('-Q', '-G'):
|
||||
with tempfile.TemporaryDirectory(prefix='cthulhu-audacious-') as temporary:
|
||||
environment = dict(os.environ, XDG_CONFIG_HOME=temporary,
|
||||
XDG_CACHE_HOME=temporary, XDG_DATA_HOME=temporary, XDG_STATE_HOME=temporary,
|
||||
GOMODCACHE=moduleCache, GOCACHE='/tmp/cthulhu-go-cache',
|
||||
GOPROXY='off', QT_QPA_PLATFORM='xcb',
|
||||
QT_LINUX_ACCESSIBILITY_ALWAYS_ON='1', NO_AT_BRIDGE='0',
|
||||
CTHULHU_NATIVE_AUDACIOUS=mode)
|
||||
environment['I3SOCK'] = str(Path(temporary) / 'i3.sock')
|
||||
run_private_session(['xvfb-run', '-a', 'dbus-run-session', '--',
|
||||
sys.executable, __file__, '--inside'], env=environment, check=True)
|
||||
Reference in new issue
Block a user