Repository navigation
Linux: pin the island to its display on layer-shell (Hyprland multi-monitor), display picker, click guard - #227
Merged
Conversation
A layer surface ignores set_position, so the island landed on whichever output had focus when it was mapped. Pin it with gtk_layer_set_monitor to the display target_monitor picked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BNUKEFycKsFCP5fKkhY1Uf
"Main display" means little on Wayland: GDK has no primary monitor, so it is whichever output GDK lists first. List every display in "Island lives on" and store the choice as at:<x>,<y> (logical origin). Names cannot be the key: two monitors of the same model report the same one. A display that is gone falls back to the primary one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BNUKEFycKsFCP5fKkhY1Uf
The card opens under a cursor that was busy with the window underneath, so a click meant for that window could land on Allow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BNUKEFycKsFCP5fKkhY1Uf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BNUKEFycKsFCP5fKkhY1Uf
JhoanG956
pushed a commit
to JhoanG956/coucou
that referenced
this pull request
Oct 7, 2026
…M#299 - Linux pointer watch (Louis-CFM#160) now also runs for a regular window (GNOME, X11), not only on layer-shell, so auto-close works there too (Louis-CFM#136). - GNOME: force GDK_BACKEND=x11 (Louis-CFM#149) only when XWayland is there (DISPLAY set). - GNOME large text (Louis-CFM#122): undo text-scaling-factor for the island through GDK_DPI_SCALE, unless the user set it. - Display choice (Louis-CFM#227): remember a display by place, name and size, so it is found again after the layout is rearranged or the resolution changes; keys saved before still match. - PKGBUILD (Louis-CFM#299): license matches LICENSE-ASSETS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JhoanG956
pushed a commit
to JhoanG956/coucou
that referenced
this pull request
Oct 7, 2026
…desktop Includes port/mochi-wardrobe. Conflicts: settings fields, commands, tray entries (Open, Weekly recap, Wardrobe…, Settings…, Pause, Quit), bridge calls, views and styles kept from every branch; save_settings still keeps Mochi's desktop spot whatever a page sends, next to the shortcuts check; the duplicate gtk_layer_set_monitor binding (Louis-CFM#227 and this branch) is declared once. Wired up after the merge: - alertActive() says what it now covers: the one card for every agent's permission and for Claude Code's questions (Louis-CFM#216), folded or not; a question card flies Mochi home like a permission does (tested). - The wardrobe shortcut's open-wardrobe event opens the wardrobe from any state, or closes it, through the same path as the desktop Mochi's right-click. - Only the main tool's pill dresses Mochi (it was hard-wired to VS Code's). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
On a two-monitor Hyprland desktop the Linux island opens on whichever monitor has keyboard focus when it starts, and the "Island lives on" setting does nothing. This PR pins the layer-shell surface to a display and lets the user pick which one. Two smaller fixes come with it.
One commit per topic, so you can review them separately, or ask me to split them into separate PRs (CONTRIBUTING asks for one topic per PR):
Environment
DP-2at0,0andDP-3at2592,0linux-v0.1.1AppImage, then built from source (npm run tauri build -- --no-bundle) onmainWhat I observed
1. The island follows keyboard focus instead of the setting
screenis"primary"(the default). Each run below starts the app with keyboard focus on one monitor, then moves focus to the other monitor, then readshyprctl layersto see where thecoucousurface is:Cause.
apply_geometrycallsset_position, but a layer surface ignores it.make_non_activatingnever callsgtk_layer_set_monitor, so the compositor picks the output itself, and Hyprland uses the focused one.There is a second problem on Wayland. "Main display" maps to
app.primary_monitor(), and GDK on Wayland has no primary monitor (is_primary()is false for both outputs here). So it falls back to whatever GDK lists first. On this machine that isDP-3, the right-hand screen, which the user never chose. "Display under the cursor" can't work on Wayland either:cursor_physical()returnsNonethere, so it falls back the same way.2. Monitor names can't tell displays apart
tao takes the monitor name from
gdk_monitor_get_model(), and both monitors here reportSA322QU A. GDK3 doesn't expose the connector name (DP-2/DP-3). So the setting stores the display's logical origin instead (at:<x>,<y>), the same coordinates GDK uses for monitor geometry. If that display is unplugged or moved, the island falls back to the primary one.3. The permission card opens under a busy cursor
The card expands right under where the pointer already is. I was clicking around in a terminal under the top of the screen while testing, and the card appeared with the cursor sitting on Allow (screenshot below). The click that time was deliberate, but a click meant for the window underneath would approve the request. The card now ignores clicks for its first 600 ms, the same idea as a confirmation-delay guard.
I measured it by sending a
PermissionRequestwith the cursor parked on Deny and clicking every 150 ms: the first 5 clicks reached the card (mousedownlogged) and were ignored, and the 6th registereddecide deny.4. The AppImage on this system (for #95)
This matches #95. The AppImage printed
gtk-layer-shell v0.7.0 may not work on GTK v3.24.33. Hook events reached Rust (hook PreToolUseand so on incoucou.log), but the page never reacted. EveryPermissionRequestended withisland never acknowledged — terminal takes over. In the first run the main process went away after about 6 minutes, and the orphanedWebKitWebProcessdied withSIGTRAP(WebProcess didn't exit as expected after the UI process connection was closed). Built from source against the system libraries, none of that happened in my testing: no warning, the page boots, and the card shows and answers.Changes
platform/linux.rs: bindgtk_layer_set_monitor. Newpin_to_monitor(win, x, y)finds theGdkMonitorwhose geometry starts at that logical origin and pins the surface to it. It does nothing for a regular window (GNOME,COUCOU_LAYER_SHELL=0).platform/windows.rs:pin_to_monitordoes nothing, becauseset_positionalready places the window there.island.rs:apply_geometrypins after placing.target_monitoracceptsat:<x>,<y>. Newmonitor_choices()for Settings.lib.rs: newlist_monitorscommand.settings/main.ts: "Island lives on" now lists each display as<model> — <w>×<h> at <x>,<y>after "Main display" and "Display under the cursor". The existing options and styling are unchanged.core/state.ts,core/bridge.ts:screenwidens tostring. Existing"primary"/"cursor"values keep working.views/views.ts:CLICK_GUARD_MS = 600on the approval card's two buttons, reset perrequestId.How I tested
On the machine above:
cargo test --releasepasses (12/12) andtsc --noEmitis clean. Each commit builds.coucou-hooksyntheticSessionStart/UserPromptSubmit/PreToolUse/PostToolUse/PermissionRequestpayloads on stdin, and also ran a real Claude Code session with the hooks installed. I checkedcoucou.logand the relay's stdout:{"behavior":"allow"}and{"behavior":"deny","message":"Denied from Coucou"}both came back.at:0,0andat:2592,0, moving focus between outputs (hl.dsp.focus({monitor=...})) and starting the app with focus on each. The surface stayed on the chosen output every time (table above).hl.dsp.cursor.moveandydotool: hover from hidden goes to compact, a click expands it, and the gear and "Settings…" open the Settings window.grim.Screenshots
The permission card on Hyprland, opened under the pointer:
The display picker in Settings:
Related
gtk_layer_set_monitor(place_on_monitor, by GDK index), as part of a larger KDE PR, and finds the primary display withkscreen-doctor. This PR is the smaller, compositor-agnostic piece: it pins by logical origin and lets the user choose, which also covers Hyprland and Sway where there is nokscreen-doctor. If Linux fixes for KDE/Wayland, plus a few features (language, Claude Code chat fallback, code view) #158 lands first I'll rebase onto itsplace_on_monitorand keep only the Settings choice.Noticed but not changed
KEYBOARD_NONE.🤖 Generated with Claude Code
https://claude.ai/code/session_01BNUKEFycKsFCP5fKkhY1Uf