Repository navigation
feat: Extended Clipboard: clipboardEmpty, supports.unicodeClipboard, no stale client text - #40
Merged
Merged
Conversation
…no stale client text - clipboardEmpty: the server's clipboard changed and holds no text (a notify or provide without the text format). Not emitted for the notify without text that TigerVNC and TurboVNC send every client, the sender included, when a client's clipboard replaces text of the server's own. - Once the server reports a newer clipboard of its own (a notify with text, a provide, or a plain ServerCutText), the client answers a request with no provide and a peek with a notify without text. - supports.unicodeClipboard: updateClipboard sends any text as UTF-8 (the server takes text with the notify action). Without the text format in the server's caps, updateClipboard sends ClientCutText. Closes #36 Closes #37
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.
Three changes to the client's Extended Clipboard state. All of them are backward compatible: the
clipboardevent fires exactly as before, and the new API is one event and onesupportsfield.#36:
clipboardEmptyA
notifywithout the text format, or aprovidewithout text, now emitsclipboardEmpty(no arguments): the server's clipboard changed and holds no text. It was emptied, or it holds only other formats, such as an image.I chose a new event over
clipboardwith''. It's the more backward-compatible option: existingclipboardlisteners see exactly the events they saw before. With''they would get a new event in a common TigerVNC case. Say an X client has text on the clipboard and VNC client A callsupdateClipboard. Every other client gets the server's "no text"notifyfirst (see below), so with''a viewer that mirrorsclipboardwould clear its local clipboard. Any code that waits for the nextclipboardafter a peer's change would also receive''instead of the text. That includes this repo's own "UTF-8 text from one client to another" integration test.clipboardwith''still means what it meant: the server sent empty text. To wait for whatever a copy on the server produced, listen for both events. The README's events section documents this.The echo: what TigerVNC does
Traced in TigerVNC v1.16.2. The same code has been there since the Extended Clipboard landed in 1.10, and TurboVNC 3.3.1's
cutpaste.cis a port of it.SConnection::handleClipboardNotifyreceives the client'snotify(text). It callsVNCServerST::handleClipboardAnnounce, which makes that client theclipboardClientand calls Xvnc'svncHandleClipboardAnnounce.vncHandleClipboardAnnouncetakes PRIMARY (withSetPrimary, the default), then CLIPBOARD. It does this throughvncOwnSelection, which runs the X server'sSelectionCallbackfor each.vncSelectionCallbackchecks whether the selection just taken isactiveSelection, the X selection whose text the server last announced. If it is, it callsvncMaybeRequestCache(), setsactiveSelection = None, and callsvncAnnounceClipboard(FALSE).VNCServerST::announceClipboardloops over all clients, the sender included.SConnection::announceClipboard(false)writesnotifywith no formats to each Extended Clipboard client.vncMaybeRequestCachefirst sends arequest(text) to theclipboardClientto cache its data. The sender therefore seesrequest(text), thennotify(no formats), both while the server handles itsnotify.So the echo comes only when
activeSelectionwas set. That variable is set in exactly one place: when the server finds text on a selection that an X client took, followed byvncAnnounceClipboard(TRUE). That call sends every client anotify(text) or aprovide. The variable is cleared only together with thenotifywithout text.Some consequences of this code:
activeSelectionisNone. Another client's change then sends nothing at all, and neither does an X client that copies something without text.notifywithout text always comes after a text announcement, and at most once per announcement.notifyareannounceClipboardand the reply to apeek.CConnection::handleClipboardNotifydrops its own clipboard on anotifywith text, but not on one without text.The rule (deterministic, no timer)
The client tracks one flag: whether the server may still hold text of its own.
notifywith text, aprovidewith text, or a ServerCutText sets it. Anotifyorprovidewithout text clears it.updateClipboardreplaces such text, onenotifywithout text is expected. Thatnotifyis consumed without an event, and the client keeps its text (as TigerVNC's viewer does).With TigerVNC and TurboVNC, this exactly matches when
activeSelectionis set, except in one race described below.#37 part 1: the client's text after the server's clipboard changes
These now make the client stop holding the clipboard:
notifywith textprovide(with or without text)After that, a
requestgets noprovide, and apeekgets anotifywithout text.A
notifywithout text leaves the client's text in place, which is what TigerVNC's viewer does. With TigerVNC thatnotifyis either the echo or comes after a text announcement, and the text announcement has already dropped the client's text. Keeping the text means a missed echo can't stop the client from answering the server's requests, which would break pasting on the server.#37 part 2:
supports.unicodeClipboardsupports.unicodeClipboardis true whenupdateClipboardsends any text as UTF-8: the server's caps list the text format and thenotifyaction. When it's false, text may go as Latin-1 with?for other characters, so a caller can refuse such text instead.supports.extendedClipboardis unchanged. The field is recomputed whenever the server sends caps again (TigerVNC does on every SetEncodings).updateClipboardnow also checks the text format before sending anotify(text). A server whose caps don't list text gets ClientCutText instead of an announcement it would never act on. Short text within the server's unsolicited limit still goes as aprovideto a server that takesprovidebut notnotify. That's why the field promises UTF-8 only for text of any length whennotifyis there.Tests
test/client.test.ts, "Extended Clipboard state", runs against the scripted peer:notifywithout text, aprovidewith no formats, and aprovidewith an image only each emitclipboardEmpty, and norequestis sent.updateClipboard. The server sendsrequest(text) and the client provides. The server sendsnotify(no formats): no event, and a laterrequeststill gets the client's text. A secondupdateClipboardreplaces nothing, so the nextnotifywithout text emitsclipboardEmpty.notifywithout text that is a change. This covers two cases: the server last said it had no text, or it reported newer text before the expected echo.notifywith text, aprovide, aprovidewithout text, and a plain ServerCutText, arequestgets noprovideand apeekgets anotifywithout text.supports.unicodeClipboard. It's false before caps and false withprovideonly. In that case short text goes as UTF-8 and long text as Latin-1. It's true with text andnotify, and false again when the caps lack the text format, in which caseupdateClipboardsends ClientCutText.Eight of these nine tests fail on
master. The ninth, the echo right after connecting, passes there only becausemasteremits nothing for anotifywithout text.test/integration/features.test.tsgets a new test for TigerVNC and TurboVNC. It uses two clients, A and B. A's text is taken over byrfb2-clipboard, so the server holds text of its own. A then callsupdateClipboardagain, and the test expectsclipboardEmptyon B only, never on A, before both see the new text. It passes in this PR's CI against TigerVNC 1.15.0 and TurboVNC 3.3.1 (run). B gets exactly oneclipboardEmpty, which shows the server did send its "no text"notifyto every client, and A gets none.The issue's repro script, run against
npm run buildoutput:Step 1 still prints
[]forclipboard, by design: the "no text" report is the new event.Limits
notifywithout text afterupdateClipboardis taken as the echo if the server had text before, or hadn't said anything yet. ThatclipboardEmptyis missed, just as nothing was reported before this change.notifyis in flight, the client sees the server's text first and the echo after it. The echo is then reported asclipboardEmpty, and the client has already dropped its text. Only a sync point such as a Fence after thenotifycould order these. I left that out to keep the wire traffic unchanged.Closes #36
Closes #37