> For the complete documentation index, see [llms.txt](https://docs.kouch.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kouch.dev/docs/adr/0015-gamescope-refresh-island.md).

# ADR 0015: A Linux unsafe island so the UI follows the display refresh under gamescope

## Status

Accepted on 2026-09-26. It extends CLAUDE.md ground rule 4's list of documented `unsafe` islands.

## Context

* The owner requires Kouch to "match the deck's frame rate, which on this model is 90".
* On the owner's Deck OLED, Kouch reached 90 fps under KDE (Wayland). Under gamescope, which is Game Mode's compositor, it stayed at a fixed 60 fps whatever gamescope ran at.
* WebKitGTK's own log explains it: "Could not create a vblank monitor for display 1: no drm node with CRTC found … falling back to timer". That timer is a fixed 60 fps.
* WebKit finds the DRM connector to wait on by matching `gdk_monitor_get_width_mm/height_mm` against each connector's physical size.
* Game Mode runs games on gamescope's second Xwayland. Only the first Xwayland gets the panel's size (gamescope `wlserver_set_output_info` updates server 0 only). The game server reports 0 mm, so GDK derives 339×212 mm from 96 dpi, and nothing matches the panel's 100×160 mm.
* **Experiment:**
  * Reporting 100×160 mm through a preloaded shim made WebKit use the panel's DRM vblank. The perf tour then ran at the panel's rate: ambient 89.7 fps, p50 11 ms.
  * Setting the X screen's size with `xrandr --fbmm` has no effect on Xwayland.
  * A preload can't ship: it would leak into the emulators Kouch starts.

## Decision

* `app/src-tauri/src/gamescope_vblank.rs` interposes the two GDK functions from inside the Kouch executable:
  * It defines `gdk_monitor_get_width_mm` and `gdk_monitor_get_height_mm` as `extern "C"`.
  * `build.rs` exports them with `-Wl,--export-dynamic-symbol`, so WebKit's calls resolve to them before libgtk's.
  * Anything not overridden goes to GDK's own functions, found with `dlsym(RTLD_NEXT)`.
* **The override applies only under gamescope** (`GAMESCOPE_WAYLAND_DISPLAY` or `XDG_CURRENT_DESKTOP=gamescope`):
  * It reports the physical size of the single active connector, read from sysfs EDID. An external display wins when docked.
  * It stays off when no single connector is active, and when `KOUCH_KEEP_GDK_MM` is set.
* The `unsafe` is limited to the exported functions, the `dlsym` lookup and the pointer cast. What to report is decided in safe, tested code (EDID parsing, connector choice).

## Consequences

* Game Mode can run Kouch's UI at 90 Hz on the OLED (60 on the LCD Deck, the TV's rate when docked), subject to the UI's own per-frame cost (PLAN\_V2 Phase 1 gate).
* Other callers of those two GDK functions in Kouch's process also see the panel's real size, which is the true value.
* The workaround tracks a WebKitGTK internal. If a later WebKit finds the connector another way, the override becomes a no-op, and the perf tour on the Deck shows it either way.
* Emulators are unaffected: they're separate processes and don't carry the exported symbols.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.kouch.dev/docs/adr/0015-gamescope-refresh-island.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
