112 lines
5.3 KiB
Python
112 lines
5.3 KiB
Python
#!/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)
|