122 lines
5.0 KiB
Python
122 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Real Calc through the production reader, in a private X11/AT-SPI session."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
|
|
|
|
def wait_for(check, message, timeout=15):
|
|
deadline = time.monotonic() + timeout
|
|
while time.monotonic() < deadline:
|
|
result = check()
|
|
if result:
|
|
return result
|
|
time.sleep(0.05)
|
|
raise RuntimeError(message)
|
|
|
|
|
|
def run_inside():
|
|
import gi
|
|
gi.require_version("Atspi", "2.0")
|
|
from gi.repository import Atspi
|
|
|
|
with tempfile.TemporaryDirectory(prefix="cthulhu-libreoffice-") as temporary:
|
|
root = Path(temporary)
|
|
profile = root / "profile" / "user"
|
|
profile.mkdir(parents=True)
|
|
(profile / "registrymodifications.xcu").write_text(
|
|
'<oor:items xmlns:oor="http://openoffice.org/2001/registry">'
|
|
'<item oor:path="/org.openoffice.Office.Common/Misc">'
|
|
'<prop oor:name="FirstRun" oor:op="fuse"><value>false</value></prop>'
|
|
'<prop oor:name="ShowTipOfTheDay" oor:op="fuse"><value>false</value></prop>'
|
|
'</item></oor:items>', encoding="utf-8",
|
|
)
|
|
document = root / "Example.fods"
|
|
shutil.copyfile(Path(__file__).with_name("libreoffice.fods"), document)
|
|
environment = dict(os.environ, SAL_USE_VCLPLUGIN="gtk3",
|
|
LC_ALL="C.UTF-8",
|
|
GTK_MODULES="gail:atk-bridge", NO_AT_BRIDGE="0",
|
|
XDG_CONFIG_HOME=str(root / "config"),
|
|
XDG_CACHE_HOME=str(root / "cache"),
|
|
XDG_DATA_HOME=str(root / "data"))
|
|
application = subprocess.Popen(
|
|
["libreoffice", "-env:UserInstallation=" + (root / "profile").as_uri(),
|
|
"--norestore", "--nodefault", "--nofirststartwizard", "--calc", str(document)],
|
|
env=environment, start_new_session=True,
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
|
)
|
|
try:
|
|
def find_window():
|
|
result = subprocess.run(
|
|
["xdotool", "search", "--onlyvisible", "--class", "libreoffice-calc"],
|
|
capture_output=True, text=True, check=False,
|
|
)
|
|
return result.stdout.splitlines()[-1] if result.stdout.strip() else None
|
|
|
|
window = wait_for(find_window, "Calc window missing")
|
|
# Wait for the actual accessible sheet, without enumerating its
|
|
# millions of cells. Dismiss disposable first-run UI if present.
|
|
def ready():
|
|
pending = [Atspi.get_desktop(0)]
|
|
remaining = 2500
|
|
found = False
|
|
while pending and remaining:
|
|
node = pending.pop()
|
|
remaining -= 1
|
|
role = node.get_role_name()
|
|
if role == "table":
|
|
parent = node.get_parent()
|
|
found |= parent is not None and parent.get_role_name() == "document spreadsheet"
|
|
continue
|
|
if role == "dialog" and node.get_name() == "Welcome to LibreOffice!":
|
|
subprocess.run(["xdotool", "key", "Escape"], check=True)
|
|
return False
|
|
if role == "table cell":
|
|
continue
|
|
for index in range(min(node.get_child_count(), 200)):
|
|
child = node.get_child_at_index(index)
|
|
if child is not None:
|
|
pending.append(child)
|
|
return found
|
|
|
|
wait_for(ready, "Calc accessible sheet missing")
|
|
environment.update(CTHULHU_NATIVE_LIBREOFFICE="1", CTHULHU_CALC_WINDOW=window)
|
|
environment.setdefault("GOCACHE", "/tmp/cthulhu-go-cache")
|
|
subprocess.run(
|
|
["go", "test", "./internal/app", "-run", "^TestNativeLibreOfficeCalc$",
|
|
"-count=1", "-timeout=50s", "-v"],
|
|
cwd=Path(__file__).resolve().parents[1], env=environment,
|
|
check=True, timeout=60,
|
|
)
|
|
finally:
|
|
if application.poll() is None:
|
|
os.killpg(application.pid, signal.SIGTERM)
|
|
application.wait(timeout=5)
|
|
|
|
|
|
def main():
|
|
if sys.argv[1:] == ["--inside"]:
|
|
run_inside()
|
|
return
|
|
with tempfile.TemporaryDirectory(prefix="cthulhu-libreoffice-session-") as temporary:
|
|
environment = dict(os.environ, XDG_SESSION_TYPE="x11",
|
|
XDG_CONFIG_HOME=temporary, XDG_CACHE_HOME=temporary,
|
|
XDG_DATA_HOME=temporary)
|
|
for name in ("DBUS_SESSION_BUS_ADDRESS", "AT_SPI_BUS_ADDRESS", "WAYLAND_DISPLAY"):
|
|
environment.pop(name, None)
|
|
subprocess.run(
|
|
["xvfb-run", "-a", "dbus-run-session", "--", sys.executable,
|
|
str(Path(__file__).resolve()), "--inside"],
|
|
env=environment, check=True, timeout=90,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|