Repository navigation
fix(clipboard): keep PRIMARY process-local on macOS/Windows and report its ownership - #349
Conversation
There was a problem hiding this comment.
🔵 Needs a closer look
It changes cross-platform PRIMARY selection ownership semantics (including Linux/X11, where gui-backend-selection-owner-p now returns tracked ownership instead of nil) and the author verified only macOS arm64 while CI runs the new test on X11, so human validation is warranted.
Pull request overview
This PR fixes a macOS/Windows regression where every region deactivation logged PRIMARY selection is not supported on this platform and turned into a Lisp error. GNU Emacs never rejects PRIMARY on non-X platforms (NS uses a private pasteboard; w32 a Lisp property), so the arboard backend now keeps PRIMARY in a process-local store on non-Linux targets, and neo-win.el tracks ownership so deactivate-mark correctly republishes the region instead of leaving a stale value.
Changes:
- Add a
PrivatePasteboard { Disowned, Owned(String) }process-local store for PRIMARY in the arboard backend on non-Linux targets, making store/load/disown succeed while CLIPBOARD still uses the system clipboard (Linux path unchanged). - Record PRIMARY ownership in
neo-win.el(neo--owns-primary-selection) and implementgui-backend-selection-owner-pfor theneowindow-system, following the w32 model. - Add Rust unit tests and a real-GUI integration test (with a new
primary-selection.elfixture) covering the store and thedeactivate-markownership contract; update theClipboardSelectiondoc comment.
File summaries
| File | Description |
|---|---|
crates/neomacs-display-runtime/src/clipboard.rs |
Adds PrivatePasteboard and routes non-Linux PRIMARY store/load through it instead of erroring; adds unit tests. |
lisp/term/neo-win.el |
Tracks PRIMARY ownership and answers gui-backend-selection-owner-p for the neo display. |
crates/neomacs-display-runtime/src/thread_comm.rs |
Clarifies the ClipboardSelection doc comment for the new process-local semantics. |
crates/neomacs-gui-tests/fixtures/primary-selection.el |
New fixture exercising the deactivate-mark/ownership Lisp contract. |
crates/neomacs-gui-tests/tests/real_gui_smoke.rs |
New integration test asserting PRIMARY ownership follows deactivate-mark. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
I studied the GNU Emacs NS, w32, X/PGTK, command-loop, and The macOS reproduction, private PRIMARY store, enum-based local state, and real-GUI regression are a strong contribution. I approved the direction and will implement these follow-ups directly on this branch before merge. StandardsThe current ownership record is split between Rust
SpecThe new Lisp predicate applies on Linux even though the PR says Linux is untouched. If another X11 or Wayland client takes PRIMARY, the bit remains true and Windows currently has compile coverage but no runnable real-GUI contract, and empty PRIMARY text is reported as owned but nonexistent. Follow-up design
I will preserve the original fix while making these follow-ups here. |
…tforms On macOS every region deactivation logged WARN neomacs_display_runtime::clipboard: clipboard set failed: PRIMARY selection is not supported on this platform selection=Primary and `neomacs-primary-selection-set` turned that error into a Lisp `error` signal. `select-active-regions` defaults to t (GNU keyboard.c:14338), so `deactivate-mark` calls `gui-set-selection 'PRIMARY` after every mouse drag or shift-selection, and the arboard backend's non-Linux branch rejected the request unconditionally. GNU Emacs never rejects PRIMARY on a platform without X selections. The NS port maps it to a private pasteboard named "Selection" (emacs-31.0.90 src/nsselect.m:56, :547) that `ns-own-selection-internal` (:397-446) writes and `ns-get-selection` (:514-541) reads back; the w32 port keeps it as a Lisp property (lisp/term/w32-win.el:364-367, :417-447). Follow that: the arboard backend now owns a typed `PrivatePasteboard` for PRIMARY on non-Linux targets, so store, load and disown succeed and stay process-local, while CLIPBOARD still goes to the system clipboard. Linux is untouched: it keeps the Wayland data-device backend or arboard's X11 PRIMARY kind. Declared divergence (in the type's doc): GNU NS compares pasteboard change counts to detect a foreign owner of the named pasteboard; neomacs never exposes one, so the in-process value is authoritative. Tests (red first): the backend round trip failed with the exact warning text before the change. Run on macOS arm64 only; the Linux branch is unchanged and was reasoned about, not built. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FzChXMKZaxRsqnoXaMzh8N
…sh it Adversarial review of the previous commit: once the private PRIMARY store held any text, `gui-backend-selection-exists-p` answered t while the neo window-system had no `gui-backend-selection-owner-p` at all, so the generic default (lisp/select.el:349-354) answered nil. That is the exact state GNU `deactivate-mark` (emacs-31.0.90 lisp/simple.el:7056-7066) reserves for a foreign owner (Bug#11772): both of its `gui-set-selection` calls became dead, and a region deactivated inside one command left the earlier PRIMARY value in place. A real GUI run on macOS reproduced it: after `(gui-set-selection 'PRIMARY "old")`, selecting and deactivating "new" in one command still read back "old". Every GNU port answers ownership from the record it wrote: NS compares the pasteboard change count it stored (nsselect.m:494-511), w32 reads the `x-selections` property it set (lisp/term/w32-win.el:364-367, :449-451), X asks the server (x-win.el:1359-1361). Follow w32: neo-win.el now records ownership of PRIMARY when it sets or disowns it through the display backend and answers `gui-backend-selection-owner-p` from that record, with nil meaning PRIMARY as in GNU. CLIPBOARD reports nil, like w32, because the system clipboard changes hands without notice and `gui--selection-value-internal` only trusts the predicate for CLIPBOARD on x and haiku (lisp/select.el:230-236). Declared divergence: on Linux PRIMARY is a real X11/Wayland selection that another client may take after us; the display backend has no owner query, so that hand-over is not observed. The ported keyboard.c post-command path already republishes PRIMARY unconditionally on every command with an active region, so this does not introduce a new clobbering path there. Also from the review: `PrivatePasteboard` is a closed Disowned/Owned enum instead of an Option; its doc ledgers the unported NS pieces (`ns_send_types`, change-count foreign-owner detection, `ns-sent-selection-hooks`, the nil-value error); `ClipboardSelection`'s doc states the per-platform meaning of Primary. Tests (red first): the new real-GUI regression in neomacs-gui-tests drives the Lisp contract through the built binary and failed on the previous commit with owned_before=false and after_deactivate="old"; it passes now on macOS arm64 (NEOMACS_GUI_TEST_BACKEND=macos). Linux and Windows were not run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FzChXMKZaxRsqnoXaMzh8N
Represent selection ownership as an exhaustive Rust enum and carry it through the clipboard worker, display host, and Lisp primitive boundary.\n\nKeep process-local PRIMARY contents and ownership in one typed state, report unobservable Linux ownership conservatively, and treat an owned empty string as an existing selection. Cover worker, transport, headless, TTY, Lisp, and subr-surface behavior.
Assert the process-local macOS/Windows contract and the conservative Linux contract separately through the real GUI harness. Cover raw ownership states, deactivate-mark behavior, empty selection existence, and disowning where the backend supports an observable local owner.
11b875a to
0ce67d1
Compare
Problem
On macOS every region deactivation logged
and
neomacs-primary-selection-setturned that error into a Lisperrorsignal.select-active-regionsdefaults to t (GNU keyboard.c:14338), sodeactivate-markcallsgui-set-selection 'PRIMARYafter every mouse drag or shift-selection, and the arboard backend's non-Linux branch rejected the request unconditionally.GNU baseline (emacs-31.0.90)
GNU never rejects PRIMARY on a platform without X selections:
"Selection"(src/nsselect.m:56, :547), written byns-own-selection-internal(:397-446), read byns-get-selection(:514-541), cleared byns-disown-selection-internal(:449-466), and reports ownership by comparing the pasteboard change count it recorded (ns-selection-owner-p, :494-511).deactivate-mark(lisp/simple.el:7056-7066) republishes the region to PRIMARY only whengui-backend-selection-owner-pholds or nobody owns PRIMARY (Bug#11772).Change
Commit 1 — the arboard backend owns a closed
PrivatePasteboard { Disowned, Owned(String) }for PRIMARY on non-Linux targets, so store, load and disown succeed and stay process-local while CLIPBOARD still goes to the system clipboard. Linux is untouched: it keeps the Wayland data-device backend or arboard's X11 PRIMARY kind (branch byte-identical).Commit 2 — found by adversarial review of commit 1: once the store held text,
gui-backend-selection-exists-panswered t while theneowindow-system had nogui-backend-selection-owner-p(the generic default answers nil), which is exactly the foreign-owner statedeactivate-markrefuses to overwrite. A region deactivated inside one command left the earlier PRIMARY value in place.neo-win.elnow records ownership when it sets or disowns PRIMARY through the backend and answers the owner predicate from that record, following w32; nil means PRIMARY as in GNU.Declared divergences
ns_send_types(:131-134, :418) and compares change counts (:459-461, :528-529). Neomacs never exposes such a pasteboard, so a foreign owner cannot arise and the in-process value is authoritative.ns-sent-selection-hooks(:438-443) is not run; the "Selection value may not be nil" error (:413-414) has no counterpart because neo-win.el routes nil to a disown.x-selection-owner-p, x-win.el:1359-1361). The ported keyboard.c post-command path already republishes PRIMARY unconditionally on every command with an active region, so this adds no new clobbering path.gui--selection-value-internalonly trusts the predicate for CLIPBOARD on x and haiku (lisp/select.el:230-236).stubs.rskeeps its own thread-local PRIMARY text, so there are now two process-local PRIMARY stores in the tree. Worth folding together in a follow-up.Tests (red first)
arboard_backend_keeps_primary_in_a_private_pasteboardfailed on main with the exact warning text;private_pasteboard_round_trips_store_load_and_clearpins the store.real_gui_primary_selection_follows_deactivate_mark_after_prior_ownership(neomacs-gui-tests, fixtureprimary-selection.el) drives the Lisp contract through the built binary: after(gui-set-selection 'PRIMARY "old"), selecting and deactivating"new"inside one command must read back"new"withowner-pt, and disowning must read nil withowner-pnil. It failed on commit 1 withowned_before=false,after_deactivate="old"and passes after commit 2 withNEOMACS_GUI_TEST_BACKEND=macos.cargo fmt --checkandcargo clippy --testsonneomacs-display-runtimeandneomacs-gui-testsare clean.Not run
Verified on macOS arm64 only. The Linux branch is unchanged and was reasoned about, not built; Windows compiles the same non-Linux branch as before and was not built.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FzChXMKZaxRsqnoXaMzh8N