feat(window): add KWin scripting backend with 5 read+control tools#26
Open
isac322 wants to merge 6 commits into
Open
feat(window): add KWin scripting backend with 5 read+control tools#26isac322 wants to merge 6 commits into
isac322 wants to merge 6 commits into
Conversation
d93cd8e to
eeddaf6
Compare
a5a6cac to
e303f50
Compare
eeddaf6 to
5ffd09e
Compare
e303f50 to
698b982
Compare
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
698b982 to
6376973
Compare
📝 Docs & SEO ReviewSource files changed in this PR: Consistency check results:
|
240a2e0 to
a346962
Compare
- Add dbus_args.py: full dbus-send recursive-descent parser (12 basic
types + array/dict/variant containers) returning dbus-python types.
- Add typed-JSON args dispatcher: args list now accepts both legacy
'type:value' strings and {type, value} dicts, mixed in one call.
Schema widened (additive): list[str] -> list[str | dict].
- core.py:dbus_call now uses dbus.bus.BusConnection + Interface in-process
(no subprocess). Errors surface as 'D-Bus error: <name>: <msg>'.
- _format_dbus_result: void -> empty, primitive -> bare, container -> JSON.
- 80 unit tests for the parser; 6 integration tests against virtual KWin.
- docs/design/dbus-call-call-sites.md documents the public-tool contract.
Pre-commit: ruff/ty clean, ci_guards pass, 87 tests green.
…l worker Replaces per-call `subprocess.run([sys.executable, '-m', 'kwin_mcp.accessibility', ...])` with a long-lived spawn-context multiprocessing.Pool(processes=1) worker. Key changes: - New module `kwin_mcp.accessibility_worker` containing picklable top-level callables `_init_atspi_worker` and `do_atspi_op`. Module is NEVER imported at module-top by core.py (CI guard 3 enforces this); deferred imports inside `_ensure_atspi_pool` and `_run_atspi` only. - Worker init validates `Atspi.get_desktop(0).get_child_count() >= 0` against the bus address and raises RuntimeError with the bus address on failure. - Pool teardown protocol survives hung workers within 7s: close → join 5s → terminate → join 2s → SIGKILL → join 1s. - IPC error handling catches `(EOFError, BrokenPipeError, ConnectionResetError)` and `OSError errno in (32, 104)` for transparent recovery; one retry, then re-raise. - Defensive `__del__` teardown swallows exceptions for partial-init safety. - AGENT_EXEC_APPROVAL=1 prefix used for all uv invocations during this change per session permission. Performance: - Cold start ≤ 1.5s (verified) - Warm calls < 200ms (verified, plan-mandated threshold) - vs ~700ms per subprocess on the previous design Tests: - tests/integration/test_atspi_pool.py — 6 tests, all passing under `AGENT_EXEC_APPROVAL=1 uv run pytest -m kwin`: cold start <1.5s; warm call <200ms; recovers from external SIGKILL; init failure surfaces RuntimeError with bus address; teardown within 7s even with SIGSTOPped worker; no zombie processes after session_stop. Plan tasks: 11, 12, 13, 14, 15.
…ckend
PR 4 lands the screenshot-backend overhaul as a single atomic unit.
What changed:
- New `_probe_screenshot_capability(dbus_address, wayland_socket)` runs at
session_start. Tries a 1x1 `CaptureArea` via in-process ScreenShot2 D-Bus,
falls back to a real `spectacle` capture, returns one of:
"screenshot2_dbus" | "spectacle_cli" | "unavailable".
10s wall-clock cap; bus address GUID redacted in logs.
- `SessionInfo.screenshot_backend: str = "unavailable"` carries the probe
result; `Session.start` and `LiveSession.__init__` populate it via a
helper that swallows probe exceptions and warns on failure.
- `capture_screenshot_to_file` and `capture_frame_burst` now accept
`screenshot_backend` as an explicit keyword argument (no global state).
Dispatch is a `match` on the three values; "unavailable" raises
`RuntimeError("No screenshot backend available; install spectacle or
fix KWin EglBackend")`.
- Two `core.py` call sites pass `screenshot_backend=info.screenshot_backend`.
Tests:
- New `tests/integration/test_screenshot_backends.py` (4 tests, marked
`@pytest.mark.kwin`):
1. probe returns one of the three documented strings
2. screenshot writes a real PNG (signature byte-checked)
3. burst capture preserves backend invariant + valid PNGs per frame
4. forcing backend="unavailable" raises with the exact message
All 4 pass against a real virtual KWin session.
Plan tasks: 16, 17, 18, 19.
PR 5 - Wave 5 of kwin-mcp backend overhaul. New module src/kwin_mcp/window.py: KWinScriptingBackend with 5 JS templates (JS_LIST_WINDOWS, JS_ACTIVE_WINDOW, JS_GEOMETRY_BY_ID, JS_ACTIVATE_BY_ID, JS_CLOSE_BY_ID) loaded via tempfile + KWin.Scripting.loadScript (because KWin 6.6.4 lacks loadScriptFromText). callDBus result-passing via per-call unique bus name + dbus.service.Object listener with scoped GLib MainContext iteration (no perpetual mainloop). JS templates rewritten from KDE Plasma 6 scripting docs (no kdotool GPL-3.0 copy). 5 new MCP tools in server.py + corresponding AutomationEngine methods: window_list, active_window, window_geometry, window_activate, window_close. Tool count: 30 -> 35. Live-mode safety gate: window_activate/window_close return error string when session is LiveSession instance (read-only ops unchanged in both modes). Tests: tests/integration/test_window_backend.py with 7 tests (kcalc launch, list, active, geometry, activate, close, live-mode mock, invalid id) -- all pass against virtual KWin session. Docs sync: README/SKILL.md/check_docs_seo.py/positioning.yml all updated to 35 tools; opencode mirror auto-generated via sync_plugin_version.py.
a346962 to
ec0b4c5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
AT-SPI is good for widget semantics but weak for window-level enumeration / geometry / activation. KWin scripting (KDE-specific) is the deterministic path.
What
src/kwin_mcp/window.py(NEW, 301L):KWinScriptingBackend(dbus_address)JS_LIST_WINDOWS,JS_ACTIVE_WINDOW,JS_GEOMETRY_BY_ID,JS_ACTIVATE_BY_ID,JS_CLOSE_BY_IDloadScriptFromTextis unsupported (KWin 6.6.4 — see PR 1b spike), so the backend writes JS to a tempfile and usesloadScript(path, name)+ UUID per calldbus.service.Object.Result(s)listener +callDBus(...)from JS. UsesDBusGMainLoop(set_as_default=True)plus scopedGLib.MainContext.default().iteration()polling — no global mainloop.src/kwin_mcp/server.py: 5 new@mcp.tool()entries (window_list,active_window,window_geometry,window_activate,window_close). Tool count: 30 → 35.src/kwin_mcp/core.py: engine methods +_check_window_mutation_allowed(blocks mutation in live sessions)tests/integration/test_window_backend.py: 7 tests (kcalc lifecycle + live mode gate + invalid id)Docs sync
README.md: "30 MCP tools" → "35 MCP tools" (×2), "(30 tools)" → "(35 tools)" (arch diagram), new tool reference table, updated arch descriptionintegrations/claude-code/skills/kwin-desktop-automation/SKILL.md(source) + opencode mirror (auto-synced viascripts/sync_plugin_version.py)CHANGELOG.md: entry under[Unreleased]scripts/check_docs_seo.py:TOOL_COUNT_CANONICAL = 35.claude/positioning.yml:tool_count: 35,tool_count_canonical: 35Safety
In live sessions,
window_activateandwindow_closereturn"Error: window mutation not supported in live session (v1 safety). Use virtual session.".window_list/active_window/window_geometrywork in both modes.Verify
Series: Stacked on top of PR 4. Base =
overhaul/pr4-screenshot. Final PR in the series.