Public Access
Make the terminal's right button copy and paste, and never open a menu
Selecting output and right-clicking it opened the WebView's own context menu with Copy on it, which is correct behaviour for a web page and wrong for a terminal. What a terminal's right button has meant since the Windows console got QuickEdit, and since PuTTY shipped, is this: with something selected it copies and drops the selection, with nothing selected it pastes. No menu, no second click. The gesture can only live in the page. The selection is drawn into a canvas the host never sees, and once a terminal has focus the host's window receives no input events at all — the same reason the Ctrl+plus chords are handled there. ◆ THE CLIPBOARD IS THE HOST'S IN BOTH DIRECTIONS, AND THE READING IS WHY. navigator.clipboard.readText() is gated behind the clipboard-read permission in every Chromium, and an embedded WebView with no PermissionRequested handler answers a permission request by putting a prompt on the screen. Trading a context menu for a permission dialog is not this change. So the page asks and the host answers, exactly as requestFontSizeStep does. Writing does not go the other way either, although navigator.clipboard.writeText would have worked from the page on Windows: a clipboard that is the host's in one direction and the page's in the other is two mechanisms to keep working across WebView2, WebKitGTK and Android's WebView. The host has owned a clipboard delegate since the keychain grew its COPY buttons, so each App.axaml.cs grows a ClipboardReader beside its ClipboardWriter and MainWindowViewModel takes it. Three opcodes: client ClipboardCopy and ClipboardPaste, server Clipboard. ◆ Clipboard IS NOT Paste, AND THE DIFFERENCE IS THE BRACKETING. Paste carries a snippet off the sidebar, and the page deliberately *types* a single-line one rather than bracketing it, because bash marks a bracketed paste as an active region and a snippet should read as though it had been typed. A clipboard paste is the case those markers are for — readline uses them to decide it has been pasted into — so Clipboard is always bracketed and never followed by an Enter. One opcode for both would have needed a flag meaning "which of the two kinds of paste is this", on a payload whose existing flag byte means something else. Both client frames are dispatched before the session lookup, which is what keeps the gesture working in a pane whose shell has just ended: squinting at the message a remote left behind is exactly when somebody copies the line. Dispatch was past MA0051's length once it grew a second such branch, so the frames no pump needs to see moved into Announce() and the font-size chord went with them. ◆ CAPTURED AND STOPPED, RATHER THAN MERELY PREVENTED. preventDefault alone suppresses the menu and still lets xterm's own contextmenu handler run, which moves its helper textarea under the pointer at z-index 1000 and leaves the selection sitting in it, selected. Both of those exist to serve the native menu. Taking the event in the capture phase on the pane and stopping it there is what keeps the gesture wholly the page's. rightClickSelectsWord is now false, which on macOS is a change of default rather than a restatement of it: xterm sets it true there alone, so a right-click would have selected the word under the pointer and copied that instead of pasting — wrong on one platform only, which is the worst way for it to be wrong. The renderer harness grows the pane it has been inviting since RendererReconnectionTests: a fake Terminal, the two addons, and a real UTF-8 codec, because both directions run through the page's own TextEncoder and TextDecoder and a Latin-1 stub would pass a test with an em dash in the selection. Its warning about declaring names terminal.js declares now covers the half that does not SyntaxError — a harness frame() was silently replaced by the page's frame() and failed six tests on a type error a long way from the cause. Manual checks 1.11 and 11.13 are what is left for a person: that a real contextmenu event reaches a capture listener, that no native menu is drawn once it is prevented, and that xterm's real paste() brackets. 11.13 also records what is unverified — whether Android's WebView delivers a pointer's secondary click as a contextmenu event at all. ClipboardReader is wired on that head regardless, because the alternative is a head where the paste half silently does nothing and nobody finds out; that head is not compiled here either, this machine having no android workload. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2810f7c12b
commit
1908189100
16 files changed
+1557
-22
No files matched your search
@@ -197,6 +197,56 @@ That is what the "is this still the page's socket" guard in `connect()` exists t
|
||||
input while the banner is *clear* is neither: the socket is open and dead, which nothing on this page can
|
||||
currently see — see the note at the end of 11.12a.
|
||||
|
||||
### 1.11 The right button copies and pastes, and never opens a menu
|
||||
|
||||
Open a shell and run something that prints a few lines — `ls -la /etc`. Drag a selection across part of the
|
||||
output with the left button, then **right-click inside the selection**.
|
||||
|
||||
**Pass:** the highlight disappears at once, **no menu appears anywhere**, and the text is on the clipboard —
|
||||
paste it into Notepad, or into the connect box, to be sure. The pane still takes typing straight afterwards.
|
||||
|
||||
Now **right-click again with nothing selected.**
|
||||
|
||||
**Pass:** the clipboard's text appears at the prompt as one insertion and **Enter is not pressed** — it sits
|
||||
there waiting. On bash it will be highlighted in reverse video until the next keystroke, which is right: that
|
||||
is bash marking a bracketed paste, and it is how you can see the markers arrived.
|
||||
|
||||
**And the multi-line case, which is the one worth the most care.** Copy three separate lines out of a text
|
||||
editor — `cd /tmp`, `pwd`, `ls` — and right-click in the terminal.
|
||||
|
||||
**Pass:** all three lines land at the prompt as one thing being edited and **nothing runs** until Enter is
|
||||
pressed. Three commands executing the moment you right-click is the failure this is looking for.
|
||||
|
||||
**On a Mac, one more:** right-click over a word with nothing selected and confirm it pastes rather than
|
||||
selecting the word under the pointer. xterm.js defaults `rightClickSelectsWord` to true on macOS alone;
|
||||
`terminal.js` turns it off, and this is the one platform where forgetting to would show.
|
||||
|
||||
**What the tests already own.** `RendererClipboardTests` runs `terminal.js` itself and proves which frame a
|
||||
right-click sends, which session it is addressed to, that the selection is dropped after a copy and that the
|
||||
answer comes back through `paste()` rather than `input()`. `ShellFlowTests` proves the round trip through the
|
||||
host's own clipboard. What is left here is the WebView: that a real `contextmenu` event reaches a listener
|
||||
registered in the capture phase, that no native menu is drawn once it is prevented, and that xterm's real
|
||||
`paste()` brackets the text — none of which a Jint engine can answer.
|
||||
|
||||
**Failure means:**
|
||||
|
||||
- **A context menu appears.** The event is not being prevented, or not being seen. `handleRightClick` takes
|
||||
it in the capture phase on the pane; a listener registered without the third argument to
|
||||
`addEventListener` would still prevent the menu, so a menu means the listener is not attached at all.
|
||||
- **A permission prompt appears** — *"…wants to see text and images copied to the clipboard"*. Somebody has
|
||||
routed the read through `navigator.clipboard.readText()`, which is exactly what the `ClipboardPaste`
|
||||
opcode exists to avoid. The clipboard is the host's.
|
||||
- **A drag started exactly where you last right-clicked selects nothing.** xterm's own `contextmenu`
|
||||
handler ran: it parks its invisible helper textarea under the pointer, 20 pixels square at z-index 1000,
|
||||
to serve the native menu. The page stops the event as well as preventing it, and `stopPropagation` is the
|
||||
half that was left out. Subtle enough to be worth looking for on purpose rather than waiting to notice.
|
||||
- **Three commands run on a multi-line paste.** The text went through `input()` rather than `paste()`, so
|
||||
bracketed paste never happened and the shell read each newline as "run this". That is the whole reason the
|
||||
`Clipboard` opcode is separate from `Paste`; see `TerminalFrame.cs`.
|
||||
- **Nothing happens at all, in either direction.** The head is not passing a clipboard. Both pass one — see
|
||||
`ClipboardWriter` and `ClipboardReader` in each `App.axaml.cs` — and the view model is deliberately silent
|
||||
when neither is there, because there is no status line on this screen to say it on.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Known Hosts as its own page
|
||||
@@ -1993,6 +2043,26 @@ without either end noticing, which a suspended renderer can leave behind — sho
|
||||
connected and swallows what is typed. If that is ever seen, it is a different bug from this one and needs a
|
||||
liveness probe rather than a faster retry.
|
||||
|
||||
### 11.13 The right button on a phone with a pointer · **needs a mouse, a trackpad or a dock**
|
||||
|
||||
The same page as 1.11 runs on this head, so the gesture is here too — but only for a device that has a
|
||||
secondary click at all. Attach a mouse or use a keyboard case's trackpad, or dock the phone, open a shell,
|
||||
select some output and right-click it; then right-click again with nothing selected.
|
||||
|
||||
**Pass:** exactly as 1.11 — the selection drops with no menu, and the second click pastes.
|
||||
|
||||
**Unverified, and that is why this check exists.** Nothing here has been run against Android's WebView with
|
||||
a pointer attached, so whether it delivers a secondary click as a DOM `contextmenu` event at all is an open
|
||||
question. `ClipboardReader` is wired on this head regardless, because the alternative is a head where the
|
||||
paste half silently does nothing and nobody finds out.
|
||||
|
||||
**Failure means:** if the copy works and the paste does not, the clipboard read is being refused — Android
|
||||
returns nothing from the clipboard to an app that is not the foreground one, and a WebView taking the
|
||||
secondary click may be leaving Avalonia's window in a state where `TopLevel` has none. If neither works,
|
||||
Android is not delivering the click as a `contextmenu` event, and the gesture needs a native listener on the
|
||||
`WebView` instead of a page one. Neither costs this head anything it has today: a phone without a pointer
|
||||
has no right button to miss.
|
||||
|
||||
---
|
||||
|
||||
## Phase 12 — Shared vaults: the operations that span two accounts
|
||||
|
||||
Reference in new issue
Block a user