fix(scripts): skip the portal probe on niri in inject-probe
The injector never offers the RemoteDesktop portal on niri, but inject-probe ran the portal probe unguarded and reported portal_available: true there. The probe now reuses the injector's _is_niri_session guard: on niri it skips the portal and reports portal_skip_reason "niri" with portal_available false. Off niri the probe runs and reports its answer as before. Fixes #46
This commit is contained in:
@@ -63,10 +63,12 @@ with the installed build's interpreter.
|
||||
version-probe result and timing, then a session-establishment attempt
|
||||
(fast failure versus hang matters more than the version answer), plus
|
||||
the resolved first rung and a no-emission `wtype ""` spawn cost as a
|
||||
lower bound. End-to-end key-to-screen latency is deliberately not
|
||||
taken here: emitting keystrokes into a live desktop from a background
|
||||
run would disturb the session. It belongs to the hardware checklist
|
||||
with a dedicated target window.
|
||||
lower bound. On niri the portal probe is skipped, as the injector skips
|
||||
it, and the report says so with `portal_skip_reason: "niri"` and
|
||||
`portal_available: false`. End-to-end key-to-screen latency is
|
||||
deliberately not taken here: emitting keystrokes into a live desktop
|
||||
from a background run would disturb the session. It belongs to the
|
||||
hardware checklist with a dedicated target window.
|
||||
- **Thresholds for the model ladder:** derive free-memory cutoffs from
|
||||
the maximum observed per-rung footprint (transcribe peak, not just
|
||||
load) plus explicit headroom for the compositor and other GPU
|
||||
|
||||
@@ -433,8 +433,11 @@ def cmd_inject_probe(args: argparse.Namespace) -> int:
|
||||
|
||||
Never emits keystrokes: end-to-end key-to-screen latency would type
|
||||
into the live desktop, so it stays a hardware-checklist item (#32).
|
||||
|
||||
On niri the portal is skipped with the injector's own niri guard: the
|
||||
injector never offers the portal there, so the report must not claim it.
|
||||
"""
|
||||
from vocalinux.text_injection.text_injector import TextInjector
|
||||
from vocalinux.text_injection.text_injector import TextInjector, _is_niri_session
|
||||
|
||||
probe_text = "the quick brown fox jumps over the lazy dog 0123456789"
|
||||
started = time.monotonic()
|
||||
@@ -462,16 +465,20 @@ def cmd_inject_probe(args: argparse.Namespace) -> int:
|
||||
env=host_env(),
|
||||
)
|
||||
wtype_roundtrips.append(time.monotonic() - begin)
|
||||
try:
|
||||
from vocalinux.text_injection.remote_desktop_portal import RemoteDesktopPortal
|
||||
portal_available = False
|
||||
portal_seconds = None
|
||||
portal_skip_reason = "niri" if _is_niri_session() else None
|
||||
if portal_skip_reason is None:
|
||||
try:
|
||||
from vocalinux.text_injection.remote_desktop_portal import RemoteDesktopPortal
|
||||
|
||||
portal_started = time.monotonic()
|
||||
portal_available = RemoteDesktopPortal().probe()
|
||||
portal_seconds = time.monotonic() - portal_started
|
||||
except Exception as exc: # noqa: BLE001 - probe must never fail the pass
|
||||
portal_available = False
|
||||
portal_seconds = None
|
||||
print(f"portal probe raised: {exc}", file=sys.stderr)
|
||||
portal_started = time.monotonic()
|
||||
portal_available = RemoteDesktopPortal().probe()
|
||||
portal_seconds = time.monotonic() - portal_started
|
||||
except Exception as exc: # noqa: BLE001 - probe must never fail the pass
|
||||
portal_available = False
|
||||
portal_seconds = None
|
||||
print(f"portal probe raised: {exc}", file=sys.stderr)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
@@ -481,6 +488,7 @@ def cmd_inject_probe(args: argparse.Namespace) -> int:
|
||||
"wtype_empty_roundtrip_stats": describe(wtype_roundtrips),
|
||||
"portal_available": portal_available,
|
||||
"portal_probe_seconds": portal_seconds,
|
||||
"portal_skip_reason": portal_skip_reason,
|
||||
},
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
|
||||
@@ -7,7 +7,10 @@ helpers tested here. Tests run offline with fixture text only and never
|
||||
touch audio hardware, the GPU, or the network.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -134,3 +137,90 @@ DBUS_TOGGLE_LOG = """\
|
||||
def test_parse_wake_latencies_pairs_dbus_toggle_starts_only() -> None:
|
||||
"""Only IDLE toggles open capture; a stop toggle never borrows the next run."""
|
||||
assert PROTOCOL.parse_wake_latencies(DBUS_TOGGLE_LOG) == pytest.approx([0.146, 0.649])
|
||||
|
||||
|
||||
#: Session variables the injector's niri guard reads, plus Wayland; cleared so
|
||||
#: the host's own session (this machine runs niri) cannot leak into a test.
|
||||
_SESSION_ENV = (
|
||||
"NIRI_SOCKET",
|
||||
"XDG_CURRENT_DESKTOP",
|
||||
"XDG_SESSION_DESKTOP",
|
||||
"DESKTOP_SESSION",
|
||||
"WAYLAND_DISPLAY",
|
||||
)
|
||||
|
||||
|
||||
class _FakeInjector:
|
||||
"""Stands in for TextInjector: a Wayland session that selected wtype."""
|
||||
|
||||
wayland_mode = True
|
||||
wayland_tool = "wtype"
|
||||
|
||||
|
||||
def _run_inject_probe(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
session_env: dict[str, str],
|
||||
portal_answers: bool,
|
||||
) -> tuple[dict, list[bool]]:
|
||||
"""Run inject-probe under a fake session; return its report and portal probe calls."""
|
||||
from vocalinux.text_injection import remote_desktop_portal, text_injector
|
||||
|
||||
probes: list[bool] = []
|
||||
|
||||
class _SpyPortal:
|
||||
def probe(self) -> bool:
|
||||
probes.append(True)
|
||||
return portal_answers
|
||||
|
||||
for name in _SESSION_ENV:
|
||||
monkeypatch.delenv(name, raising=False)
|
||||
for name, value in session_env.items():
|
||||
monkeypatch.setenv(name, value)
|
||||
monkeypatch.setattr(text_injector, "TextInjector", _FakeInjector)
|
||||
monkeypatch.setattr(remote_desktop_portal, "RemoteDesktopPortal", _SpyPortal)
|
||||
monkeypatch.setattr(shutil, "which", lambda _name: None)
|
||||
assert PROTOCOL.cmd_inject_probe(argparse.Namespace()) == 0
|
||||
return json.loads(capsys.readouterr().out), probes
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"session_env",
|
||||
[
|
||||
{"XDG_CURRENT_DESKTOP": "niri"},
|
||||
{"NIRI_SOCKET": "/run/user/1000/niri.wayland-1.1.sock"},
|
||||
],
|
||||
)
|
||||
def test_inject_probe_never_probes_portal_on_niri(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
session_env: dict[str, str],
|
||||
) -> None:
|
||||
"""The injector never offers the portal on niri, so the probe must not claim it.
|
||||
|
||||
Both niri signals are covered: the probe reuses the injector's guard, so a
|
||||
session the injector treats as niri is reported the same way here.
|
||||
"""
|
||||
report, probes = _run_inject_probe(
|
||||
monkeypatch, capsys, session_env=session_env, portal_answers=True
|
||||
)
|
||||
assert probes == []
|
||||
assert report["portal_available"] is False
|
||||
assert report["portal_probe_seconds"] is None
|
||||
assert report["portal_skip_reason"] == "niri"
|
||||
|
||||
|
||||
def test_inject_probe_reports_portal_answer_off_niri(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
"""Off niri the portal is probed and its answer is reported unchanged."""
|
||||
report, probes = _run_inject_probe(
|
||||
monkeypatch,
|
||||
capsys,
|
||||
session_env={"XDG_CURRENT_DESKTOP": "GNOME"},
|
||||
portal_answers=True,
|
||||
)
|
||||
assert probes == [True]
|
||||
assert report["portal_available"] is True
|
||||
assert report["portal_skip_reason"] is None
|
||||
|
||||
Reference in New Issue
Block a user