detached a589526

fix(wayland): sum relative pointer deltas when collapsing queued motions

commit
a589526e41b1148ba827652728db82a8139f29b6
author
brice · · 2026-09-26 14:24 UTC
parent
bc84cb3
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +13 -5
M internal/adapters/wayland/commands_test.go +5 -4
@@ -12,16 +12,17 @@ import (
1212
1313 func TestDrainCommandsCollapsesMotion(t *testing.T) {
1414 cmds := make(chan ports.ClientCommand, 16)
15- cmds <- ports.PointerMotionTo{ID: 1, X: 2}
16- cmds <- ports.PointerMotionTo{ID: 1, X: 3}
15+ cmds <- ports.PointerMotionTo{ID: 1, X: 2, DX: 0.25, UnaccelDX: 0.5}
16+ cmds <- ports.PointerMotionTo{ID: 1, X: 3, DX: 0.25, DY: -0.5, UnaccelDX: 0.5, UnaccelDY: -1}
1717 cmds <- ports.PointerButtonTo{ID: 1, Button: 272, Pressed: true}
1818 cmds <- ports.PointerMotionTo{ID: 1, X: 4}
1919 cmds <- ports.PointerFocus{ID: 2}
2020 cmds <- ports.PointerMotionTo{ID: 2, X: 5}
2121 cmds <- ports.PointerMotionTo{ID: 2, X: 6}
22- batch, open := drainCommands(ports.PointerMotionTo{ID: 1, X: 1}, cmds)
22+ batch, open := drainCommands(ports.PointerMotionTo{ID: 1, X: 1, DX: 0.25, UnaccelDX: 0.5}, cmds)
2323 want := []ports.ClientCommand{
24- ports.PointerMotionTo{ID: 1, X: 3},
24+ // Slow motions add up: none of the sub-pixel deltas is lost.
25+ ports.PointerMotionTo{ID: 1, X: 3, DX: 0.75, DY: -0.5, UnaccelDX: 1.5, UnaccelDY: -1},
2526 ports.PointerButtonTo{ID: 1, Button: 272, Pressed: true},
2627 ports.PointerMotionTo{ID: 1, X: 4},
2728 ports.PointerFocus{ID: 2},
@@ -12,16 +12,17 @@ import (
12 12
13 func TestDrainCommandsCollapsesMotion(t *testing.T) {13 func TestDrainCommandsCollapsesMotion(t *testing.T) {
14 cmds := make(chan ports.ClientCommand, 16)14 cmds := make(chan ports.ClientCommand, 16)
15- cmds <- ports.PointerMotionTo{ID: 1, X: 2}15+ cmds <- ports.PointerMotionTo{ID: 1, X: 2, DX: 0.25, UnaccelDX: 0.5}
16- cmds <- ports.PointerMotionTo{ID: 1, X: 3}16+ cmds <- ports.PointerMotionTo{ID: 1, X: 3, DX: 0.25, DY: -0.5, UnaccelDX: 0.5, UnaccelDY: -1}
17 cmds <- ports.PointerButtonTo{ID: 1, Button: 272, Pressed: true}17 cmds <- ports.PointerButtonTo{ID: 1, Button: 272, Pressed: true}
18 cmds <- ports.PointerMotionTo{ID: 1, X: 4}18 cmds <- ports.PointerMotionTo{ID: 1, X: 4}
19 cmds <- ports.PointerFocus{ID: 2}19 cmds <- ports.PointerFocus{ID: 2}
20 cmds <- ports.PointerMotionTo{ID: 2, X: 5}20 cmds <- ports.PointerMotionTo{ID: 2, X: 5}
21 cmds <- ports.PointerMotionTo{ID: 2, X: 6}21 cmds <- ports.PointerMotionTo{ID: 2, X: 6}
22- batch, open := drainCommands(ports.PointerMotionTo{ID: 1, X: 1}, cmds)22+ batch, open := drainCommands(ports.PointerMotionTo{ID: 1, X: 1, DX: 0.25, UnaccelDX: 0.5}, cmds)
23 want := []ports.ClientCommand{23 want := []ports.ClientCommand{
24- ports.PointerMotionTo{ID: 1, X: 3},24+ // Slow motions add up: none of the sub-pixel deltas is lost.
25+ ports.PointerMotionTo{ID: 1, X: 3, DX: 0.75, DY: -0.5, UnaccelDX: 1.5, UnaccelDY: -1},
25 ports.PointerButtonTo{ID: 1, Button: 272, Pressed: true},26 ports.PointerButtonTo{ID: 1, Button: 272, Pressed: true},
26 ports.PointerMotionTo{ID: 1, X: 4},27 ports.PointerMotionTo{ID: 1, X: 4},
27 ports.PointerFocus{ID: 2},28 ports.PointerFocus{ID: 2},
M internal/adapters/wayland/server.go +8 -1
@@ -432,7 +432,8 @@ func (s *Server) forwardContents(ctx context.Context) {
432432 const maxCommandBatch = 256
433433
434434 // drainCommands returns first plus the commands already queued, with runs of
435-// pointer motion for the same window collapsed into the latest one. open is
435+// pointer motion for the same window collapsed into the latest one, their
436+// relative deltas summed. open is
436437 // false once the channel is closed.
437438 func drainCommands(first ports.ClientCommand, cmds <-chan ports.ClientCommand) (batch []ports.ClientCommand, open bool) {
438439 batch = append(batch, first)
@@ -444,6 +445,12 @@ func drainCommands(first ports.ClientCommand, cmds <-chan ports.ClientCommand) (
444445 }
445446 if m, isMotion := c.(ports.PointerMotionTo); isMotion {
446447 if last, lastMotion := batch[len(batch)-1].(ports.PointerMotionTo); lastMotion && last.ID == m.ID {
448+ // The position is the latest; relative deltas add up so
449+ // locked pointers (games) lose no movement.
450+ m.DX += last.DX
451+ m.DY += last.DY
452+ m.UnaccelDX += last.UnaccelDX
453+ m.UnaccelDY += last.UnaccelDY
447454 batch[len(batch)-1] = m
448455 continue
449456 }
@@ -432,7 +432,8 @@ func (s *Server) forwardContents(ctx context.Context) {
432 const maxCommandBatch = 256432 const maxCommandBatch = 256
433 433
434 // drainCommands returns first plus the commands already queued, with runs of434 // drainCommands returns first plus the commands already queued, with runs of
435-// pointer motion for the same window collapsed into the latest one. open is435+// pointer motion for the same window collapsed into the latest one, their
436+// relative deltas summed. open is
436 // false once the channel is closed.437 // false once the channel is closed.
437 func drainCommands(first ports.ClientCommand, cmds <-chan ports.ClientCommand) (batch []ports.ClientCommand, open bool) {438 func drainCommands(first ports.ClientCommand, cmds <-chan ports.ClientCommand) (batch []ports.ClientCommand, open bool) {
438 batch = append(batch, first)439 batch = append(batch, first)
@@ -444,6 +445,12 @@ func drainCommands(first ports.ClientCommand, cmds <-chan ports.ClientCommand) (
444 }445 }
445 if m, isMotion := c.(ports.PointerMotionTo); isMotion {446 if m, isMotion := c.(ports.PointerMotionTo); isMotion {
446 if last, lastMotion := batch[len(batch)-1].(ports.PointerMotionTo); lastMotion && last.ID == m.ID {447 if last, lastMotion := batch[len(batch)-1].(ports.PointerMotionTo); lastMotion && last.ID == m.ID {
448+ // The position is the latest; relative deltas add up so
449+ // locked pointers (games) lose no movement.
450+ m.DX += last.DX
451+ m.DY += last.DY
452+ m.UnaccelDX += last.UnaccelDX
453+ m.UnaccelDY += last.UnaccelDY
447 batch[len(batch)-1] = m454 batch[len(batch)-1] = m
448 continue455 continue
449 }456 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close