fix(input): compute held wheel value after direction reset, skip empty pre-v8 frames
- commit
- 3d55321528a48f0b08accea22f366893ac409a9e
- author
- brice · · 2026-09-25 19:58 UTC
- parent
- 6ea6bb9
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +33 -19
M internal/adapters/wayland/axis_test.go +11 -4
| @@ -10,11 +10,13 @@ import ( | |||
| 10 | 10 | // discrete step once 120 builds up; a direction change drops the rest. | |
| 11 | 11 | func TestWheelSteps(t *testing.T) { | |
| 12 | 12 | s := &Server{} | |
| 13 | - | wheel := func(v, h int32) [2]int32 { | |
| 14 | - | return s.wheelSteps(ports.PointerAxis{ | |
| 13 | + | var values [2]float64 | |
| 14 | + | wheel := func(v, h int32) (steps [2]int32) { | |
| 15 | + | steps, values = s.wheelSteps(ports.PointerAxis{ | |
| 15 | 16 | Vertical: ports.ScrollAxis{Set: v != 0, V120: v, Value: float64(v) / 8}, | |
| 16 | 17 | Horizontal: ports.ScrollAxis{Set: h != 0, V120: h}, | |
| 17 | 18 | }) | |
| 19 | + | return steps | |
| 18 | 20 | } | |
| 19 | 21 | for i, tc := range []struct { | |
| 20 | 22 | v, h int32 | |
| @@ -38,10 +40,15 @@ func TestWheelSteps(t *testing.T) { | |||
| 38 | 40 | if s.wheelHeld[0] != 7.5 { | |
| 39 | 41 | t.Fatal("held", s.wheelHeld[0]) | |
| 40 | 42 | } | |
| 41 | - | if wheel(60, 0) != [2]int32{1, 0} || s.wheelHeld[0] != 0 { | |
| 43 | + | if wheel(60, 0) != [2]int32{1, 0} || s.wheelHeld[0] != 0 || values[0] != 15 { | |
| 42 | 44 | t.Fatal("held after step", s.wheelHeld[0]) | |
| 43 | 45 | } | |
| 44 | - | if s.wheelSteps(ports.PointerAxis{Source: ports.AxisFinger, Vertical: ports.ScrollAxis{Set: true, V120: 240}}) != [2]int32{} { | |
| 46 | + | // A direction change drops what was held the other way. | |
| 47 | + | wheel(60, 0) | |
| 48 | + | if wheel(-180, 0) != [2]int32{-1, 0} || values[0] != -22.5 { | |
| 49 | + | t.Fatal("after reversal", values) | |
| 50 | + | } | |
| 51 | + | if steps, _ := s.wheelSteps(ports.PointerAxis{Source: ports.AxisFinger, Vertical: ports.ScrollAxis{Set: true, V120: 240}}); steps != [2]int32{} { | |
| 45 | 52 | t.Fatal("finger scroll made discrete steps") | |
| 46 | 53 | } | |
| 47 | 54 | } | |
| @@ -10,11 +10,13 @@ import ( | |||||
| 10 | // discrete step once 120 builds up; a direction change drops the rest. | 10 | // discrete step once 120 builds up; a direction change drops the rest. | ||
| 11 | func TestWheelSteps(t *testing.T) { | 11 | func TestWheelSteps(t *testing.T) { | ||
| 12 | s := &Server{} | 12 | s := &Server{} | ||
| 13 | - | wheel := func(v, h int32) [2]int32 { | 13 | + | var values [2]float64 |
| 14 | - | return s.wheelSteps(ports.PointerAxis{ | 14 | + | wheel := func(v, h int32) (steps [2]int32) { |
| 15 | + | steps, values = s.wheelSteps(ports.PointerAxis{ | |||
| 15 | Vertical: ports.ScrollAxis{Set: v != 0, V120: v, Value: float64(v) / 8}, | 16 | Vertical: ports.ScrollAxis{Set: v != 0, V120: v, Value: float64(v) / 8}, | ||
| 16 | Horizontal: ports.ScrollAxis{Set: h != 0, V120: h}, | 17 | Horizontal: ports.ScrollAxis{Set: h != 0, V120: h}, | ||
| 17 | }) | 18 | }) | ||
| 19 | + | return steps | |||
| 18 | } | 20 | } | ||
| 19 | for i, tc := range []struct { | 21 | for i, tc := range []struct { | ||
| 20 | v, h int32 | 22 | v, h int32 | ||
| @@ -38,10 +40,15 @@ func TestWheelSteps(t *testing.T) { | |||||
| 38 | if s.wheelHeld[0] != 7.5 { | 40 | if s.wheelHeld[0] != 7.5 { | ||
| 39 | t.Fatal("held", s.wheelHeld[0]) | 41 | t.Fatal("held", s.wheelHeld[0]) | ||
| 40 | } | 42 | } | ||
| 41 | - | if wheel(60, 0) != [2]int32{1, 0} || s.wheelHeld[0] != 0 { | 43 | + | if wheel(60, 0) != [2]int32{1, 0} || s.wheelHeld[0] != 0 || values[0] != 15 { |
| 42 | t.Fatal("held after step", s.wheelHeld[0]) | 44 | t.Fatal("held after step", s.wheelHeld[0]) | ||
| 43 | } | 45 | } | ||
| 44 | - | if s.wheelSteps(ports.PointerAxis{Source: ports.AxisFinger, Vertical: ports.ScrollAxis{Set: true, V120: 240}}) != [2]int32{} { | 46 | + | // A direction change drops what was held the other way. |
| 47 | + | wheel(60, 0) | |||
| 48 | + | if wheel(-180, 0) != [2]int32{-1, 0} || values[0] != -22.5 { | |||
| 49 | + | t.Fatal("after reversal", values) | |||
| 50 | + | } | |||
| 51 | + | if steps, _ := s.wheelSteps(ports.PointerAxis{Source: ports.AxisFinger, Vertical: ports.ScrollAxis{Set: true, V120: 240}}); steps != [2]int32{} { | |||
| 45 | t.Fatal("finger scroll made discrete steps") | 52 | t.Fatal("finger scroll made discrete steps") | ||
| 46 | } | 53 | } | ||
| 47 | } | 54 | } | ||
M internal/adapters/wayland/server.go +22 -15
| @@ -403,10 +403,9 @@ func (s *Server) apply(cmd ports.ClientCommand) { | |||
| 403 | 403 | if c.ID != s.pointerFocus { | |
| 404 | 404 | return | |
| 405 | 405 | } | |
| 406 | - | held := s.wheelHeld | |
| 407 | - | steps := s.wheelSteps(c.Axis) | |
| 406 | + | steps, values := s.wheelSteps(c.Axis) | |
| 408 | 407 | for _, p := range s.windowPointers(s.windows[c.ID]) { | |
| 409 | - | sendAxis(p, c.Axis, steps, held) | |
| 408 | + | sendAxis(p, c.Axis, steps, values) | |
| 410 | 409 | } | |
| 411 | 410 | case ports.SetKeymap: | |
| 412 | 411 | s.setKeymap(c) | |
| @@ -621,10 +620,11 @@ func (s *Server) changeFocus(id ports.WindowID) { | |||
| 621 | 620 | // wheelSteps adds wheel v120 to the rest per axis and returns the whole | |
| 622 | 621 | // detents for axis_discrete (seat before v8). High-resolution wheels send | |
| 623 | 622 | // fractions of 120; a direction change drops the rest, as wlroots does. | |
| 624 | - | // wheelHeld keeps the axis value of the frames without a step. | |
| 625 | - | func (s *Server) wheelSteps(a ports.PointerAxis) (steps [2]int32) { | |
| 623 | + | // wheelHeld keeps the axis value of the frames without a step; values is | |
| 624 | + | // what a pre-v8 client gets with a step: the held value plus this frame's. | |
| 625 | + | func (s *Server) wheelSteps(a ports.PointerAxis) (steps [2]int32, values [2]float64) { | |
| 626 | 626 | if a.Source != ports.AxisWheel { | |
| 627 | - | return steps | |
| 627 | + | return steps, values | |
| 628 | 628 | } | |
| 629 | 629 | for i, ax := range [2]ports.ScrollAxis{a.Vertical, a.Horizontal} { | |
| 630 | 630 | if !ax.Set || ax.V120 == 0 { | |
| @@ -638,22 +638,31 @@ func (s *Server) wheelSteps(a ports.PointerAxis) (steps [2]int32) { | |||
| 638 | 638 | steps[i] = s.wheelRest[i] / 120 | |
| 639 | 639 | s.wheelRest[i] -= steps[i] * 120 | |
| 640 | 640 | if steps[i] != 0 { | |
| 641 | - | s.wheelHeld[i] = 0 | |
| 641 | + | values[i], s.wheelHeld[i] = s.wheelHeld[i], 0 | |
| 642 | 642 | } | |
| 643 | 643 | } | |
| 644 | - | return steps | |
| 644 | + | return steps, values | |
| 645 | 645 | } | |
| 646 | 646 | ||
| 647 | 647 | // sendAxis sends one scroll frame, each event gated by the pointer version. | |
| 648 | 648 | // steps are whole wheel detents for axis_discrete. A pre-v8 client gets a | |
| 649 | - | // wheel frame only with a step, carrying the value held since the last one | |
| 650 | - | // (held, before this frame), so it never counts smooth and discrete scroll. | |
| 651 | - | func sendAxis(p *wayland.Pointer, a ports.PointerAxis, steps [2]int32, held [2]float64) { | |
| 649 | + | // wheel axis only with a step, carrying values (all the value since the | |
| 650 | + | // last step), so it never counts smooth and discrete scroll. | |
| 651 | + | func sendAxis(p *wayland.Pointer, a ports.PointerAxis, steps [2]int32, values [2]float64) { | |
| 652 | 652 | v := p.Version() | |
| 653 | + | axes := [2]ports.ScrollAxis{a.Vertical, a.Horizontal} | |
| 654 | + | for i := range axes { | |
| 655 | + | if v < 8 && a.Source == ports.AxisWheel && axes[i].V120 != 0 && steps[i] == 0 { | |
| 656 | + | axes[i].Set = false | |
| 657 | + | } | |
| 658 | + | } | |
| 659 | + | if !axes[0].Set && !axes[1].Set { | |
| 660 | + | return | |
| 661 | + | } | |
| 653 | 662 | if v >= 5 { | |
| 654 | 663 | p.SendAxisSource(uint32(a.Source)) | |
| 655 | 664 | } | |
| 656 | - | for axis, s := range [2]ports.ScrollAxis{a.Vertical, a.Horizontal} { | |
| 665 | + | for axis, s := range axes { | |
| 657 | 666 | if !s.Set { | |
| 658 | 667 | continue | |
| 659 | 668 | } | |
| @@ -668,10 +677,8 @@ func sendAxis(p *wayland.Pointer, a ports.PointerAxis, steps [2]int32, held [2]f | |||
| 668 | 677 | switch { | |
| 669 | 678 | case v >= 8: | |
| 670 | 679 | p.SendAxisValue120(uint32(axis), s.V120) | |
| 671 | - | case steps[axis] == 0: | |
| 672 | - | continue | |
| 673 | 680 | default: | |
| 674 | - | value += held[axis] | |
| 681 | + | value = values[axis] | |
| 675 | 682 | if v >= 5 { | |
| 676 | 683 | p.SendAxisDiscrete(uint32(axis), steps[axis]) | |
| 677 | 684 | } | |
| @@ -403,10 +403,9 @@ func (s *Server) apply(cmd ports.ClientCommand) { | |||||
| 403 | if c.ID != s.pointerFocus { | 403 | if c.ID != s.pointerFocus { | ||
| 404 | return | 404 | return | ||
| 405 | } | 405 | } | ||
| 406 | - | held := s.wheelHeld | 406 | + | steps, values := s.wheelSteps(c.Axis) |
| 407 | - | steps := s.wheelSteps(c.Axis) | |||
| 408 | for _, p := range s.windowPointers(s.windows[c.ID]) { | 407 | for _, p := range s.windowPointers(s.windows[c.ID]) { | ||
| 409 | - | sendAxis(p, c.Axis, steps, held) | 408 | + | sendAxis(p, c.Axis, steps, values) |
| 410 | } | 409 | } | ||
| 411 | case ports.SetKeymap: | 410 | case ports.SetKeymap: | ||
| 412 | s.setKeymap(c) | 411 | s.setKeymap(c) | ||
| @@ -621,10 +620,11 @@ func (s *Server) changeFocus(id ports.WindowID) { | |||||
| 621 | // wheelSteps adds wheel v120 to the rest per axis and returns the whole | 620 | // wheelSteps adds wheel v120 to the rest per axis and returns the whole | ||
| 622 | // detents for axis_discrete (seat before v8). High-resolution wheels send | 621 | // detents for axis_discrete (seat before v8). High-resolution wheels send | ||
| 623 | // fractions of 120; a direction change drops the rest, as wlroots does. | 622 | // fractions of 120; a direction change drops the rest, as wlroots does. | ||
| 624 | - | // wheelHeld keeps the axis value of the frames without a step. | 623 | + | // wheelHeld keeps the axis value of the frames without a step; values is |
| 625 | - | func (s *Server) wheelSteps(a ports.PointerAxis) (steps [2]int32) { | 624 | + | // what a pre-v8 client gets with a step: the held value plus this frame's. |
| 625 | + | func (s *Server) wheelSteps(a ports.PointerAxis) (steps [2]int32, values [2]float64) { | |||
| 626 | if a.Source != ports.AxisWheel { | 626 | if a.Source != ports.AxisWheel { | ||
| 627 | - | return steps | 627 | + | return steps, values |
| 628 | } | 628 | } | ||
| 629 | for i, ax := range [2]ports.ScrollAxis{a.Vertical, a.Horizontal} { | 629 | for i, ax := range [2]ports.ScrollAxis{a.Vertical, a.Horizontal} { | ||
| 630 | if !ax.Set || ax.V120 == 0 { | 630 | if !ax.Set || ax.V120 == 0 { | ||
| @@ -638,22 +638,31 @@ func (s *Server) wheelSteps(a ports.PointerAxis) (steps [2]int32) { | |||||
| 638 | steps[i] = s.wheelRest[i] / 120 | 638 | steps[i] = s.wheelRest[i] / 120 | ||
| 639 | s.wheelRest[i] -= steps[i] * 120 | 639 | s.wheelRest[i] -= steps[i] * 120 | ||
| 640 | if steps[i] != 0 { | 640 | if steps[i] != 0 { | ||
| 641 | - | s.wheelHeld[i] = 0 | 641 | + | values[i], s.wheelHeld[i] = s.wheelHeld[i], 0 |
| 642 | } | 642 | } | ||
| 643 | } | 643 | } | ||
| 644 | - | return steps | 644 | + | return steps, values |
| 645 | } | 645 | } | ||
| 646 | 646 | ||||
| 647 | // sendAxis sends one scroll frame, each event gated by the pointer version. | 647 | // sendAxis sends one scroll frame, each event gated by the pointer version. | ||
| 648 | // steps are whole wheel detents for axis_discrete. A pre-v8 client gets a | 648 | // steps are whole wheel detents for axis_discrete. A pre-v8 client gets a | ||
| 649 | - | // wheel frame only with a step, carrying the value held since the last one | 649 | + | // wheel axis only with a step, carrying values (all the value since the |
| 650 | - | // (held, before this frame), so it never counts smooth and discrete scroll. | 650 | + | // last step), so it never counts smooth and discrete scroll. |
| 651 | - | func sendAxis(p *wayland.Pointer, a ports.PointerAxis, steps [2]int32, held [2]float64) { | 651 | + | func sendAxis(p *wayland.Pointer, a ports.PointerAxis, steps [2]int32, values [2]float64) { |
| 652 | v := p.Version() | 652 | v := p.Version() | ||
| 653 | + | axes := [2]ports.ScrollAxis{a.Vertical, a.Horizontal} | |||
| 654 | + | for i := range axes { | |||
| 655 | + | if v < 8 && a.Source == ports.AxisWheel && axes[i].V120 != 0 && steps[i] == 0 { | |||
| 656 | + | axes[i].Set = false | |||
| 657 | + | } | |||
| 658 | + | } | |||
| 659 | + | if !axes[0].Set && !axes[1].Set { | |||
| 660 | + | return | |||
| 661 | + | } | |||
| 653 | if v >= 5 { | 662 | if v >= 5 { | ||
| 654 | p.SendAxisSource(uint32(a.Source)) | 663 | p.SendAxisSource(uint32(a.Source)) | ||
| 655 | } | 664 | } | ||
| 656 | - | for axis, s := range [2]ports.ScrollAxis{a.Vertical, a.Horizontal} { | 665 | + | for axis, s := range axes { |
| 657 | if !s.Set { | 666 | if !s.Set { | ||
| 658 | continue | 667 | continue | ||
| 659 | } | 668 | } | ||
| @@ -668,10 +677,8 @@ func sendAxis(p *wayland.Pointer, a ports.PointerAxis, steps [2]int32, held [2]f | |||||
| 668 | switch { | 677 | switch { | ||
| 669 | case v >= 8: | 678 | case v >= 8: | ||
| 670 | p.SendAxisValue120(uint32(axis), s.V120) | 679 | p.SendAxisValue120(uint32(axis), s.V120) | ||
| 671 | - | case steps[axis] == 0: | |||
| 672 | - | continue | |||
| 673 | default: | 680 | default: | ||
| 674 | - | value += held[axis] | 681 | + | value = values[axis] |
| 675 | if v >= 5 { | 682 | if v >= 5 { | ||
| 676 | p.SendAxisDiscrete(uint32(axis), steps[axis]) | 683 | p.SendAxisDiscrete(uint32(axis), steps[axis]) | ||
| 677 | } | 684 | } | ||