Clipboard: only the first copy from a Tk app (git gui, gitk) reaches Windows; later copies are silently dropped
Windows build number:
10.0.26200.9106
Your Distribution version:
Debian
Your WSL versions:
WSL version: 2.6.3.0
Kernel version: 6.6.87.2-1
WSLg version: 1.0.71
MSRDC version: 1.2.6353
Direct3D version: 1.611.1-81528511
DXCore version: 10.0.26100.1-240331-1435.ge-release
Windows version: 10.0.26200.9106Steps to reproduce:
Copying repeatedly from a Tk application — git gui and gitk are the common ones —
only ever updates the Windows clipboard on the first copy. Every later copy is
silently dropped; pasting into a Windows application keeps yielding the first value.
Minimal reproduction with no git involved, wish only:
# repro.tcl -- run with: wish repro.tcl
wm title . ClipRepro
pack [label .l -text "sets CLIPBOARD twice, 5s apart"]
proc setclip {v} { clipboard clear; clipboard append $v; puts "set: $v" }
after 1000 { setclip FIRSTCOPY_AAA }
after 6000 { setclip SECONDCOPY_BBBBBBB }
after 15000 { exit 0 }wish repro.tcl- After ~2s, paste into any Windows app →
FIRSTCOPY_AAA(correct) - After ~8s, paste again into a Windows app → still
FIRSTCOPY_AAA(wrong, expectedSECONDCOPY_BBBBBBB)
Important when verifying: do not test with PowerShell Get-Clipboard. That issues a
fresh clipboard request, which makes weston fetch live data from the X owner and returns
the correct value, masking the bug entirely. It has to be tested with an ordinary paste
into a Windows application, which uses the cached clipboard contents. This cost me a lot
of time — several synthetic tests "passed" while the bug was fully present.
Actual behavior:
The X11 side is entirely correct. Polling selection get -selection CLIPBOARD from a
separate X client shows the clipboard content changing on every copy:
23:40:51 CHANGED -> unsigned
23:40:56 CHANGED -> DSP_MMAP_BASE_ADDRESSBut weston.log records an ownership-change event for only the first of the two:
[23:40:51.713] xfixes selection notify event: owner 4197786
[23:40:51.714] wrote fd:109 8 (chunk size 8) of 8 bytes
[23:40:51.714] transfer write complete
<-- second copy at 23:40:56 produces no event at all
[23:41:25.895] wrote fd:109 21 (chunk size 21) of 21 bytes
[23:41:25.895] transfer write completeThe byte counts identify the values exactly: unsigned is 8 characters and
DSP_MMAP_BASE_ADDRESS is 21. The 21-byte transfer at 23:41:25 was triggered by an
explicit clipboard request made ~30s later — proving weston can still read the current
value, it simply was never told the content had changed, so it never announced the update
to the Windows clipboard.
Cause:
Tk calls XSetSelectionOwner only when it does not already own CLIPBOARD. On the first
copy it takes ownership and weston sees the XFixes notify. On subsequent copies Tk already
owns the selection, so it updates its internal buffer without re-asserting ownership.
That is legal ICCCM behavior — a selection owner is not required to re-assert when its content changes, because requestors always fetch live data from the owner. But WSLg propagates the clipboard to Windows only on ownership-change events, so a content change under stable ownership is never forwarded.
GTK and Qt applications are unaffected because they call XSetSelectionOwner on every
copy. The bug is specific to toolkits that skip the redundant call, which makes it look
application-specific when it is not.
Expected behavior:
Every copy from an X11 client reaches the Windows clipboard, regardless of whether the selection owner changed.
A targeted fix would be for weston to re-request the selection and refresh the Windows
clipboard whenever a SelectionRequest/conversion reveals changed content, or to
re-validate the cached contents on WM_RENDERFORMAT, rather than relying solely on
ownership-change notifications.
Workaround:
A small helper that holds CLIPBOARD ownership and re-takes it whenever the content
changes. This both fires the event weston needs and leaves the application not owning
the selection, so its next copy re-asserts naturally:
#!/usr/bin/wish
wm withdraw .
set last ""
proc readclip {} {
foreach type {UTF8_STRING STRING} {
if {![catch {selection get -selection CLIPBOARD -type $type} v]} { return $v }
}
return ""
}
proc tick {} {
global last
set v [readclip]
if {$v ne "" && $v ne $last} {
set last $v
clipboard clear
clipboard append -- $v
}
after 400 tick
}
tickWith it running, the same two-copy test produces four notify events instead of one — app, helper, app, helper — and both copies reach Windows:
[23:43:01.147] xfixes selection notify event: owner 8388627 <- app, 1st copy
[23:43:01.148] wrote fd:109 13 (chunk size 13) of 13 bytes
[23:43:01.199] xfixes selection notify event: owner 6291459 <- helper takes ownership
[23:43:01.200] wrote fd:109 13 (chunk size 13) of 13 bytes
[23:43:06.147] xfixes selection notify event: owner 8388627 <- app, 2nd copy (now seen)
[23:43:06.148] wrote fd:109 18 (chunk size 18) of 18 bytes
[23:43:06.403] xfixes selection notify event: owner 6291459 <- helper takes ownership
[23:43:06.404] wrote fd:109 18 (chunk size 18) of 18 bytesIt costs ~0.1% CPU and ~11 MB. It is a workaround, not a fix — losing the helper restores the broken behavior, and it only handles text targets.
Source: microsoft/wslg