feat(libinput): stream pinch, hold and any-finger swipes as input events
- commit
- dc119ed58cbfe68067de8efd4da94840b9001a2f
- author
- brice · · 2026-10-08 09:32 UTC
- committer
- brice · · 2026-10-08 09:59 UTC
- parent
- 4fd770d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +281 -53
M internal/adapters/libinput/libinput.go +91 -26
| @@ -33,6 +33,11 @@ const ( | |||
| 33 | 33 | evSwipeBegin = 800 | |
| 34 | 34 | evSwipeUpdate = 801 | |
| 35 | 35 | evSwipeEnd = 802 | |
| 36 | + | evPinchBegin = 803 | |
| 37 | + | evPinchUpdate = 804 | |
| 38 | + | evPinchEnd = 805 | |
| 39 | + | evHoldBegin = 806 | |
| 40 | + | evHoldEnd = 807 | |
| 36 | 41 | ) | |
| 37 | 42 | ||
| 38 | 43 | var ( | |
| @@ -76,6 +81,8 @@ var ( | |||
| 76 | 81 | gestureDX func(gev uintptr) float64 | |
| 77 | 82 | gestureDY func(gev uintptr) float64 | |
| 78 | 83 | gestureUsec func(gev uintptr) uint64 | |
| 84 | + | gestureScale func(gev uintptr) float64 | |
| 85 | + | gestureAngle func(gev uintptr) float64 | |
| 79 | 86 | deviceRef func(dev uintptr) uintptr | |
| 80 | 87 | deviceUnref func(dev uintptr) uintptr | |
| 81 | 88 | hasNatural func(dev uintptr) int32 | |
| @@ -149,6 +156,8 @@ func load() error { | |||
| 149 | 156 | reg(&gestureDX, "event_gesture_get_dx_unaccelerated") | |
| 150 | 157 | reg(&gestureDY, "event_gesture_get_dy_unaccelerated") | |
| 151 | 158 | reg(&gestureUsec, "event_gesture_get_time_usec") | |
| 159 | + | reg(&gestureScale, "event_gesture_get_scale") | |
| 160 | + | reg(&gestureAngle, "event_gesture_get_angle_delta") | |
| 152 | 161 | reg(&deviceRef, "device_ref") | |
| 153 | 162 | reg(&deviceUnref, "device_unref") | |
| 154 | 163 | reg(&hasNatural, "device_config_scroll_has_natural_scroll") | |
| @@ -325,14 +334,14 @@ func Run(ctx context.Context, opts Options, input chan<- ports.InputEvent) error | |||
| 325 | 334 | } | |
| 326 | 335 | ||
| 327 | 336 | // inputState is the device state Run owns: the device config, the devices | |
| 328 | - | // it configures (referenced until removed) and the swipe in progress on | |
| 329 | - | // each touchpad. dev applies the config to libinput; it is a zero-size | |
| 337 | + | // it configures (referenced until removed) and the gesture in progress on | |
| 338 | + | // the touchpads. dev applies the config to libinput; it is a zero-size | |
| 330 | 339 | // type, so storing it in the interface allocates nothing. | |
| 331 | 340 | type inputState struct { | |
| 332 | - | cfg ports.InputDevicesConfig | |
| 333 | - | dev deviceConfig | |
| 334 | - | devices map[uintptr]bool | |
| 335 | - | swipes swipes | |
| 341 | + | cfg ports.InputDevicesConfig | |
| 342 | + | dev deviceConfig | |
| 343 | + | devices map[uintptr]bool | |
| 344 | + | gestures gestures | |
| 336 | 345 | } | |
| 337 | 346 | ||
| 338 | 347 | // deviceConfig is libinput's device configuration side: what a device is | |
| @@ -478,33 +487,74 @@ func (s *inputState) release() { | |||
| 478 | 487 | clear(s.devices) | |
| 479 | 488 | } | |
| 480 | 489 | ||
| 481 | - | // swipes streams one three- or four-finger swipe at a time to core, which | |
| 482 | - | // follows | |
| 483 | - | // the fingers. owner is the touchpad of the swipe in progress (0: none); | |
| 484 | - | // another touchpad's swipe is ignored until it ends, so core never sees | |
| 485 | - | // two streams interleaved. | |
| 486 | - | type swipes struct{ owner uintptr } | |
| 490 | + | // gestures streams one touchpad gesture at a time to core: a swipe of any | |
| 491 | + | // finger count, a pinch or a hold. Core follows the fingers of a three- or | |
| 492 | + | // four-finger swipe and hands the other gestures to the window under the | |
| 493 | + | // pointer. owner is the touchpad of the gesture in progress (0: none) and | |
| 494 | + | // kind its kind; another touchpad's gesture, or another kind's event, is | |
| 495 | + | // ignored until it ends, so core never sees two streams interleaved. | |
| 496 | + | type gestures struct { | |
| 497 | + | owner uintptr | |
| 498 | + | kind ports.GestureKind | |
| 499 | + | } | |
| 487 | 500 | ||
| 488 | - | func (s *swipes) begin(dev uintptr, fingers int, at time.Duration) ports.InputEvent { | |
| 489 | - | if (fingers != 3 && fingers != 4) || s.owner != 0 { | |
| 501 | + | func (g *gestures) begin(dev uintptr, kind ports.GestureKind, fingers int, at time.Duration) ports.InputEvent { | |
| 502 | + | if dev == 0 || fingers <= 0 || g.owner != 0 { | |
| 490 | 503 | return nil | |
| 491 | 504 | } | |
| 492 | - | s.owner = dev | |
| 505 | + | g.owner, g.kind = dev, kind | |
| 506 | + | switch kind { | |
| 507 | + | case ports.GesturePinch: | |
| 508 | + | return ports.PinchBegin{Fingers: fingers, Time: at} | |
| 509 | + | case ports.GestureHold: | |
| 510 | + | return ports.HoldBegin{Fingers: fingers, Time: at} | |
| 511 | + | } | |
| 493 | 512 | return ports.SwipeBegin{Fingers: fingers, Time: at} | |
| 494 | 513 | } | |
| 495 | 514 | ||
| 496 | - | func (s *swipes) update(dev uintptr, dx, dy float64, at time.Duration) ports.InputEvent { | |
| 497 | - | if dev == 0 || s.owner != dev { | |
| 515 | + | // running reports whether dev has a gesture of the given kind in progress. | |
| 516 | + | func (g *gestures) running(dev uintptr, kind ports.GestureKind) bool { | |
| 517 | + | return dev != 0 && g.owner == dev && g.kind == kind | |
| 518 | + | } | |
| 519 | + | ||
| 520 | + | func (g *gestures) swipeUpdate(dev uintptr, dx, dy float64, at time.Duration) ports.InputEvent { | |
| 521 | + | if !g.running(dev, ports.GestureSwipe) { | |
| 498 | 522 | return nil | |
| 499 | 523 | } | |
| 500 | 524 | return ports.SwipeUpdate{DX: dx, DY: dy, Time: at} | |
| 501 | 525 | } | |
| 502 | 526 | ||
| 503 | - | func (s *swipes) end(dev uintptr, cancelled bool, at time.Duration) ports.InputEvent { | |
| 504 | - | if dev == 0 || s.owner != dev { | |
| 527 | + | func (g *gestures) pinchUpdate(dev uintptr, dx, dy, scale, rotation float64, at time.Duration) ports.InputEvent { | |
| 528 | + | if !g.running(dev, ports.GesturePinch) { | |
| 529 | + | return nil | |
| 530 | + | } | |
| 531 | + | return ports.PinchUpdate{DX: dx, DY: dy, Scale: scale, Rotation: rotation, Time: at} | |
| 532 | + | } | |
| 533 | + | ||
| 534 | + | // end finishes the gesture of the given kind on dev. | |
| 535 | + | func (g *gestures) end(dev uintptr, kind ports.GestureKind, cancelled bool, at time.Duration) ports.InputEvent { | |
| 536 | + | if !g.running(dev, kind) { | |
| 505 | 537 | return nil | |
| 506 | 538 | } | |
| 507 | - | s.owner = 0 | |
| 539 | + | return g.finish(cancelled, at) | |
| 540 | + | } | |
| 541 | + | ||
| 542 | + | // abort cancels whatever gesture dev has in progress (the device went). | |
| 543 | + | func (g *gestures) abort(dev uintptr) ports.InputEvent { | |
| 544 | + | if dev == 0 || g.owner != dev { | |
| 545 | + | return nil | |
| 546 | + | } | |
| 547 | + | return g.finish(true, 0) | |
| 548 | + | } | |
| 549 | + | ||
| 550 | + | func (g *gestures) finish(cancelled bool, at time.Duration) ports.InputEvent { | |
| 551 | + | g.owner = 0 | |
| 552 | + | switch g.kind { | |
| 553 | + | case ports.GesturePinch: | |
| 554 | + | return ports.PinchEnd{Cancelled: cancelled, Time: at} | |
| 555 | + | case ports.GestureHold: | |
| 556 | + | return ports.HoldEnd{Cancelled: cancelled, Time: at} | |
| 557 | + | } | |
| 508 | 558 | return ports.SwipeEnd{Cancelled: cancelled, Time: at} | |
| 509 | 559 | } | |
| 510 | 560 | ||
| @@ -552,17 +602,32 @@ func translateEvent(ev uintptr, opts Options, p *pointer, in *inputState, state | |||
| 552 | 602 | delete(in.devices, dev) | |
| 553 | 603 | deviceUnref(dev) | |
| 554 | 604 | } | |
| 555 | - | // Core waits for the end of a swipe the device began. | |
| 556 | - | return in.swipes.end(dev, true, 0), nil | |
| 557 | - | case evSwipeBegin: | |
| 605 | + | // Core waits for the end of a gesture the device began. | |
| 606 | + | return in.gestures.abort(dev), nil | |
| 607 | + | case evSwipeBegin, evPinchBegin, evHoldBegin: | |
| 558 | 608 | ge := gestureEvent(ev) | |
| 559 | - | return in.swipes.begin(eventDevice(ev), int(gestureFingers(ge)), usec(gestureUsec(ge))), nil | |
| 609 | + | kind := ports.GestureSwipe | |
| 610 | + | if eventType(ev) == evPinchBegin { | |
| 611 | + | kind = ports.GesturePinch | |
| 612 | + | } else if eventType(ev) == evHoldBegin { | |
| 613 | + | kind = ports.GestureHold | |
| 614 | + | } | |
| 615 | + | return in.gestures.begin(eventDevice(ev), kind, int(gestureFingers(ge)), usec(gestureUsec(ge))), nil | |
| 560 | 616 | case evSwipeUpdate: | |
| 561 | 617 | ge := gestureEvent(ev) | |
| 562 | - | return in.swipes.update(eventDevice(ev), gestureDX(ge), gestureDY(ge), usec(gestureUsec(ge))), nil | |
| 618 | + | return in.gestures.swipeUpdate(eventDevice(ev), gestureDX(ge), gestureDY(ge), usec(gestureUsec(ge))), nil | |
| 619 | + | case evPinchUpdate: | |
| 620 | + | ge := gestureEvent(ev) | |
| 621 | + | return in.gestures.pinchUpdate(eventDevice(ev), gestureDX(ge), gestureDY(ge), gestureScale(ge), gestureAngle(ge), usec(gestureUsec(ge))), nil | |
| 563 | 622 | case evSwipeEnd: | |
| 564 | 623 | ge := gestureEvent(ev) | |
| 565 | - | return in.swipes.end(eventDevice(ev), gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil | |
| 624 | + | return in.gestures.end(eventDevice(ev), ports.GestureSwipe, gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil | |
| 625 | + | case evPinchEnd: | |
| 626 | + | ge := gestureEvent(ev) | |
| 627 | + | return in.gestures.end(eventDevice(ev), ports.GesturePinch, gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil | |
| 628 | + | case evHoldEnd: | |
| 629 | + | ge := gestureEvent(ev) | |
| 630 | + | return in.gestures.end(eventDevice(ev), ports.GestureHold, gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil | |
| 566 | 631 | case evKeyboardKey: | |
| 567 | 632 | k := keyboardEvent(ev) | |
| 568 | 633 | code, pressed := keyboardKey(k), keyboardState(k) == 1 | |
| @@ -33,6 +33,11 @@ const ( | |||||
| 33 | evSwipeBegin = 800 | 33 | evSwipeBegin = 800 | ||
| 34 | evSwipeUpdate = 801 | 34 | evSwipeUpdate = 801 | ||
| 35 | evSwipeEnd = 802 | 35 | evSwipeEnd = 802 | ||
| 36 | + | evPinchBegin = 803 | |||
| 37 | + | evPinchUpdate = 804 | |||
| 38 | + | evPinchEnd = 805 | |||
| 39 | + | evHoldBegin = 806 | |||
| 40 | + | evHoldEnd = 807 | |||
| 36 | ) | 41 | ) | ||
| 37 | 42 | ||||
| 38 | var ( | 43 | var ( | ||
| @@ -76,6 +81,8 @@ var ( | |||||
| 76 | gestureDX func(gev uintptr) float64 | 81 | gestureDX func(gev uintptr) float64 | ||
| 77 | gestureDY func(gev uintptr) float64 | 82 | gestureDY func(gev uintptr) float64 | ||
| 78 | gestureUsec func(gev uintptr) uint64 | 83 | gestureUsec func(gev uintptr) uint64 | ||
| 84 | + | gestureScale func(gev uintptr) float64 | |||
| 85 | + | gestureAngle func(gev uintptr) float64 | |||
| 79 | deviceRef func(dev uintptr) uintptr | 86 | deviceRef func(dev uintptr) uintptr | ||
| 80 | deviceUnref func(dev uintptr) uintptr | 87 | deviceUnref func(dev uintptr) uintptr | ||
| 81 | hasNatural func(dev uintptr) int32 | 88 | hasNatural func(dev uintptr) int32 | ||
| @@ -149,6 +156,8 @@ func load() error { | |||||
| 149 | reg(&gestureDX, "event_gesture_get_dx_unaccelerated") | 156 | reg(&gestureDX, "event_gesture_get_dx_unaccelerated") | ||
| 150 | reg(&gestureDY, "event_gesture_get_dy_unaccelerated") | 157 | reg(&gestureDY, "event_gesture_get_dy_unaccelerated") | ||
| 151 | reg(&gestureUsec, "event_gesture_get_time_usec") | 158 | reg(&gestureUsec, "event_gesture_get_time_usec") | ||
| 159 | + | reg(&gestureScale, "event_gesture_get_scale") | |||
| 160 | + | reg(&gestureAngle, "event_gesture_get_angle_delta") | |||
| 152 | reg(&deviceRef, "device_ref") | 161 | reg(&deviceRef, "device_ref") | ||
| 153 | reg(&deviceUnref, "device_unref") | 162 | reg(&deviceUnref, "device_unref") | ||
| 154 | reg(&hasNatural, "device_config_scroll_has_natural_scroll") | 163 | reg(&hasNatural, "device_config_scroll_has_natural_scroll") | ||
| @@ -325,14 +334,14 @@ func Run(ctx context.Context, opts Options, input chan<- ports.InputEvent) error | |||||
| 325 | } | 334 | } | ||
| 326 | 335 | ||||
| 327 | // inputState is the device state Run owns: the device config, the devices | 336 | // inputState is the device state Run owns: the device config, the devices | ||
| 328 | - | // it configures (referenced until removed) and the swipe in progress on | 337 | + | // it configures (referenced until removed) and the gesture in progress on |
| 329 | - | // each touchpad. dev applies the config to libinput; it is a zero-size | 338 | + | // the touchpads. dev applies the config to libinput; it is a zero-size |
| 330 | // type, so storing it in the interface allocates nothing. | 339 | // type, so storing it in the interface allocates nothing. | ||
| 331 | type inputState struct { | 340 | type inputState struct { | ||
| 332 | - | cfg ports.InputDevicesConfig | 341 | + | cfg ports.InputDevicesConfig |
| 333 | - | dev deviceConfig | 342 | + | dev deviceConfig |
| 334 | - | devices map[uintptr]bool | 343 | + | devices map[uintptr]bool |
| 335 | - | swipes swipes | 344 | + | gestures gestures |
| 336 | } | 345 | } | ||
| 337 | 346 | ||||
| 338 | // deviceConfig is libinput's device configuration side: what a device is | 347 | // deviceConfig is libinput's device configuration side: what a device is | ||
| @@ -478,33 +487,74 @@ func (s *inputState) release() { | |||||
| 478 | clear(s.devices) | 487 | clear(s.devices) | ||
| 479 | } | 488 | } | ||
| 480 | 489 | ||||
| 481 | - | // swipes streams one three- or four-finger swipe at a time to core, which | 490 | + | // gestures streams one touchpad gesture at a time to core: a swipe of any |
| 482 | - | // follows | 491 | + | // finger count, a pinch or a hold. Core follows the fingers of a three- or |
| 483 | - | // the fingers. owner is the touchpad of the swipe in progress (0: none); | 492 | + | // four-finger swipe and hands the other gestures to the window under the |
| 484 | - | // another touchpad's swipe is ignored until it ends, so core never sees | 493 | + | // pointer. owner is the touchpad of the gesture in progress (0: none) and |
| 485 | - | // two streams interleaved. | 494 | + | // kind its kind; another touchpad's gesture, or another kind's event, is |
| 486 | - | type swipes struct{ owner uintptr } | 495 | + | // ignored until it ends, so core never sees two streams interleaved. |
| 496 | + | type gestures struct { | |||
| 497 | + | owner uintptr | |||
| 498 | + | kind ports.GestureKind | |||
| 499 | + | } | |||
| 487 | 500 | ||||
| 488 | - | func (s *swipes) begin(dev uintptr, fingers int, at time.Duration) ports.InputEvent { | 501 | + | func (g *gestures) begin(dev uintptr, kind ports.GestureKind, fingers int, at time.Duration) ports.InputEvent { |
| 489 | - | if (fingers != 3 && fingers != 4) || s.owner != 0 { | 502 | + | if dev == 0 || fingers <= 0 || g.owner != 0 { |
| 490 | return nil | 503 | return nil | ||
| 491 | } | 504 | } | ||
| 492 | - | s.owner = dev | 505 | + | g.owner, g.kind = dev, kind |
| 506 | + | switch kind { | |||
| 507 | + | case ports.GesturePinch: | |||
| 508 | + | return ports.PinchBegin{Fingers: fingers, Time: at} | |||
| 509 | + | case ports.GestureHold: | |||
| 510 | + | return ports.HoldBegin{Fingers: fingers, Time: at} | |||
| 511 | + | } | |||
| 493 | return ports.SwipeBegin{Fingers: fingers, Time: at} | 512 | return ports.SwipeBegin{Fingers: fingers, Time: at} | ||
| 494 | } | 513 | } | ||
| 495 | 514 | ||||
| 496 | - | func (s *swipes) update(dev uintptr, dx, dy float64, at time.Duration) ports.InputEvent { | 515 | + | // running reports whether dev has a gesture of the given kind in progress. |
| 497 | - | if dev == 0 || s.owner != dev { | 516 | + | func (g *gestures) running(dev uintptr, kind ports.GestureKind) bool { |
| 517 | + | return dev != 0 && g.owner == dev && g.kind == kind | |||
| 518 | + | } | |||
| 519 | + | ||||
| 520 | + | func (g *gestures) swipeUpdate(dev uintptr, dx, dy float64, at time.Duration) ports.InputEvent { | |||
| 521 | + | if !g.running(dev, ports.GestureSwipe) { | |||
| 498 | return nil | 522 | return nil | ||
| 499 | } | 523 | } | ||
| 500 | return ports.SwipeUpdate{DX: dx, DY: dy, Time: at} | 524 | return ports.SwipeUpdate{DX: dx, DY: dy, Time: at} | ||
| 501 | } | 525 | } | ||
| 502 | 526 | ||||
| 503 | - | func (s *swipes) end(dev uintptr, cancelled bool, at time.Duration) ports.InputEvent { | 527 | + | func (g *gestures) pinchUpdate(dev uintptr, dx, dy, scale, rotation float64, at time.Duration) ports.InputEvent { |
| 504 | - | if dev == 0 || s.owner != dev { | 528 | + | if !g.running(dev, ports.GesturePinch) { |
| 529 | + | return nil | |||
| 530 | + | } | |||
| 531 | + | return ports.PinchUpdate{DX: dx, DY: dy, Scale: scale, Rotation: rotation, Time: at} | |||
| 532 | + | } | |||
| 533 | + | ||||
| 534 | + | // end finishes the gesture of the given kind on dev. | |||
| 535 | + | func (g *gestures) end(dev uintptr, kind ports.GestureKind, cancelled bool, at time.Duration) ports.InputEvent { | |||
| 536 | + | if !g.running(dev, kind) { | |||
| 505 | return nil | 537 | return nil | ||
| 506 | } | 538 | } | ||
| 507 | - | s.owner = 0 | 539 | + | return g.finish(cancelled, at) |
| 540 | + | } | |||
| 541 | + | ||||
| 542 | + | // abort cancels whatever gesture dev has in progress (the device went). | |||
| 543 | + | func (g *gestures) abort(dev uintptr) ports.InputEvent { | |||
| 544 | + | if dev == 0 || g.owner != dev { | |||
| 545 | + | return nil | |||
| 546 | + | } | |||
| 547 | + | return g.finish(true, 0) | |||
| 548 | + | } | |||
| 549 | + | ||||
| 550 | + | func (g *gestures) finish(cancelled bool, at time.Duration) ports.InputEvent { | |||
| 551 | + | g.owner = 0 | |||
| 552 | + | switch g.kind { | |||
| 553 | + | case ports.GesturePinch: | |||
| 554 | + | return ports.PinchEnd{Cancelled: cancelled, Time: at} | |||
| 555 | + | case ports.GestureHold: | |||
| 556 | + | return ports.HoldEnd{Cancelled: cancelled, Time: at} | |||
| 557 | + | } | |||
| 508 | return ports.SwipeEnd{Cancelled: cancelled, Time: at} | 558 | return ports.SwipeEnd{Cancelled: cancelled, Time: at} | ||
| 509 | } | 559 | } | ||
| 510 | 560 | ||||
| @@ -552,17 +602,32 @@ func translateEvent(ev uintptr, opts Options, p *pointer, in *inputState, state | |||||
| 552 | delete(in.devices, dev) | 602 | delete(in.devices, dev) | ||
| 553 | deviceUnref(dev) | 603 | deviceUnref(dev) | ||
| 554 | } | 604 | } | ||
| 555 | - | // Core waits for the end of a swipe the device began. | 605 | + | // Core waits for the end of a gesture the device began. |
| 556 | - | return in.swipes.end(dev, true, 0), nil | 606 | + | return in.gestures.abort(dev), nil |
| 557 | - | case evSwipeBegin: | 607 | + | case evSwipeBegin, evPinchBegin, evHoldBegin: |
| 558 | ge := gestureEvent(ev) | 608 | ge := gestureEvent(ev) | ||
| 559 | - | return in.swipes.begin(eventDevice(ev), int(gestureFingers(ge)), usec(gestureUsec(ge))), nil | 609 | + | kind := ports.GestureSwipe |
| 610 | + | if eventType(ev) == evPinchBegin { | |||
| 611 | + | kind = ports.GesturePinch | |||
| 612 | + | } else if eventType(ev) == evHoldBegin { | |||
| 613 | + | kind = ports.GestureHold | |||
| 614 | + | } | |||
| 615 | + | return in.gestures.begin(eventDevice(ev), kind, int(gestureFingers(ge)), usec(gestureUsec(ge))), nil | |||
| 560 | case evSwipeUpdate: | 616 | case evSwipeUpdate: | ||
| 561 | ge := gestureEvent(ev) | 617 | ge := gestureEvent(ev) | ||
| 562 | - | return in.swipes.update(eventDevice(ev), gestureDX(ge), gestureDY(ge), usec(gestureUsec(ge))), nil | 618 | + | return in.gestures.swipeUpdate(eventDevice(ev), gestureDX(ge), gestureDY(ge), usec(gestureUsec(ge))), nil |
| 619 | + | case evPinchUpdate: | |||
| 620 | + | ge := gestureEvent(ev) | |||
| 621 | + | return in.gestures.pinchUpdate(eventDevice(ev), gestureDX(ge), gestureDY(ge), gestureScale(ge), gestureAngle(ge), usec(gestureUsec(ge))), nil | |||
| 563 | case evSwipeEnd: | 622 | case evSwipeEnd: | ||
| 564 | ge := gestureEvent(ev) | 623 | ge := gestureEvent(ev) | ||
| 565 | - | return in.swipes.end(eventDevice(ev), gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil | 624 | + | return in.gestures.end(eventDevice(ev), ports.GestureSwipe, gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil |
| 625 | + | case evPinchEnd: | |||
| 626 | + | ge := gestureEvent(ev) | |||
| 627 | + | return in.gestures.end(eventDevice(ev), ports.GesturePinch, gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil | |||
| 628 | + | case evHoldEnd: | |||
| 629 | + | ge := gestureEvent(ev) | |||
| 630 | + | return in.gestures.end(eventDevice(ev), ports.GestureHold, gestureCanceled(ge) != 0, usec(gestureUsec(ge))), nil | |||
| 566 | case evKeyboardKey: | 631 | case evKeyboardKey: | ||
| 567 | k := keyboardEvent(ev) | 632 | k := keyboardEvent(ev) | ||
| 568 | code, pressed := keyboardKey(k), keyboardState(k) == 1 | 633 | code, pressed := keyboardKey(k), keyboardState(k) == 1 | ||
M internal/adapters/libinput/libinput_test.go +91 -25
| @@ -42,61 +42,126 @@ func TestHotkey(t *testing.T) { | |||
| 42 | 42 | } | |
| 43 | 43 | } | |
| 44 | 44 | ||
| 45 | - | func TestSwipesIgnoreSecondTouchpad(t *testing.T) { | |
| 46 | - | var s swipes | |
| 45 | + | func TestGesturesIgnoreSecondTouchpad(t *testing.T) { | |
| 46 | + | var g gestures | |
| 47 | 47 | const pad, other = 1, 2 | |
| 48 | - | s.begin(pad, 3, time.Second) | |
| 49 | - | if ev := s.begin(other, 3, time.Second); ev != nil { | |
| 48 | + | g.begin(pad, ports.GestureSwipe, 3, time.Second) | |
| 49 | + | if ev := g.begin(other, ports.GestureSwipe, 3, time.Second); ev != nil { | |
| 50 | 50 | t.Fatalf("second touchpad began: %v", ev) | |
| 51 | 51 | } | |
| 52 | - | if ev := s.update(other, 1, 1, time.Second); ev != nil { | |
| 52 | + | if ev := g.swipeUpdate(other, 1, 1, time.Second); ev != nil { | |
| 53 | 53 | t.Fatalf("second touchpad moved: %v", ev) | |
| 54 | 54 | } | |
| 55 | - | if ev := s.end(other, false, time.Second); ev != nil { | |
| 55 | + | if ev := g.end(other, ports.GestureSwipe, false, time.Second); ev != nil { | |
| 56 | 56 | t.Fatalf("second touchpad ended the swipe: %v", ev) | |
| 57 | 57 | } | |
| 58 | - | if ev := s.update(pad, 1, 1, 2*time.Second); ev == nil { | |
| 58 | + | if ev := g.swipeUpdate(pad, 1, 1, 2*time.Second); ev == nil { | |
| 59 | 59 | t.Fatal("first touchpad lost its swipe") | |
| 60 | 60 | } | |
| 61 | - | s.end(pad, false, 3*time.Second) | |
| 62 | - | if ev := s.begin(other, 3, 4*time.Second); ev == nil { | |
| 61 | + | g.end(pad, ports.GestureSwipe, false, 3*time.Second) | |
| 62 | + | if ev := g.begin(other, ports.GestureSwipe, 3, 4*time.Second); ev == nil { | |
| 63 | 63 | t.Fatal("second touchpad cannot swipe after the first ended") | |
| 64 | 64 | } | |
| 65 | 65 | } | |
| 66 | 66 | ||
| 67 | - | func TestSwipesStreamThreeFingers(t *testing.T) { | |
| 68 | - | var s swipes | |
| 67 | + | func TestGesturesStreamSwipe(t *testing.T) { | |
| 68 | + | var g gestures | |
| 69 | 69 | const pad, other = 1, 2 | |
| 70 | - | if ev := s.begin(pad, 2, time.Second); ev != nil { | |
| 71 | - | t.Fatalf("two fingers: %v", ev) | |
| 70 | + | if ev := g.begin(pad, ports.GestureSwipe, 0, time.Second); ev != nil { | |
| 71 | + | t.Fatalf("no fingers: %v", ev) | |
| 72 | 72 | } | |
| 73 | - | if ev := s.update(pad, 1, 1, time.Second); ev != nil { | |
| 73 | + | if ev := g.swipeUpdate(pad, 1, 1, time.Second); ev != nil { | |
| 74 | 74 | t.Fatalf("update without a swipe: %v", ev) | |
| 75 | 75 | } | |
| 76 | - | if ev := s.begin(pad, 3, time.Second); ev != (ports.SwipeBegin{Fingers: 3, Time: time.Second}) { | |
| 76 | + | if ev := g.begin(pad, ports.GestureSwipe, 3, time.Second); ev != (ports.SwipeBegin{Fingers: 3, Time: time.Second}) { | |
| 77 | 77 | t.Fatalf("begin: %v", ev) | |
| 78 | 78 | } | |
| 79 | - | if ev := s.update(pad, -3, 2, 2*time.Second); ev != (ports.SwipeUpdate{DX: -3, DY: 2, Time: 2 * time.Second}) { | |
| 79 | + | if ev := g.swipeUpdate(pad, -3, 2, 2*time.Second); ev != (ports.SwipeUpdate{DX: -3, DY: 2, Time: 2 * time.Second}) { | |
| 80 | 80 | t.Fatalf("update: %v", ev) | |
| 81 | 81 | } | |
| 82 | - | if ev := s.end(other, false, 0); ev != nil { | |
| 82 | + | if ev := g.end(other, ports.GestureSwipe, false, 0); ev != nil { | |
| 83 | 83 | t.Fatalf("end on another device: %v", ev) | |
| 84 | 84 | } | |
| 85 | - | if ev := s.end(pad, true, 3*time.Second); ev != (ports.SwipeEnd{Cancelled: true, Time: 3 * time.Second}) { | |
| 85 | + | if ev := g.end(pad, ports.GestureSwipe, true, 3*time.Second); ev != (ports.SwipeEnd{Cancelled: true, Time: 3 * time.Second}) { | |
| 86 | 86 | t.Fatalf("end: %v", ev) | |
| 87 | 87 | } | |
| 88 | - | if ev := s.end(pad, false, 0); ev != nil { | |
| 88 | + | if ev := g.end(pad, ports.GestureSwipe, false, 0); ev != nil { | |
| 89 | 89 | t.Fatalf("second end: %v", ev) | |
| 90 | 90 | } | |
| 91 | 91 | } | |
| 92 | 92 | ||
| 93 | - | func TestSwipesStreamFourFingers(t *testing.T) { | |
| 94 | - | var s swipes | |
| 95 | - | if ev := s.begin(1, 4, time.Second); ev != (ports.SwipeBegin{Fingers: 4, Time: time.Second}) { | |
| 96 | - | t.Fatalf("four fingers: %v", ev) | |
| 93 | + | // Swipes of every finger count stream; core picks which ones it keeps. | |
| 94 | + | func TestGesturesStreamAnyFingerCount(t *testing.T) { | |
| 95 | + | for _, fingers := range []int{2, 3, 4, 5} { | |
| 96 | + | var g gestures | |
| 97 | + | if ev := g.begin(1, ports.GestureSwipe, fingers, time.Second); ev != (ports.SwipeBegin{Fingers: fingers, Time: time.Second}) { | |
| 98 | + | t.Fatalf("%d fingers: %v", fingers, ev) | |
| 99 | + | } | |
| 100 | + | if ev := g.begin(1, ports.GestureSwipe, 5, time.Second); ev != nil { | |
| 101 | + | t.Fatalf("a second swipe began during a swipe: %v", ev) | |
| 102 | + | } | |
| 103 | + | } | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | func TestGesturesStreamPinch(t *testing.T) { | |
| 107 | + | var g gestures | |
| 108 | + | const pad = 1 | |
| 109 | + | if ev := g.begin(pad, ports.GesturePinch, 2, time.Second); ev != (ports.PinchBegin{Fingers: 2, Time: time.Second}) { | |
| 110 | + | t.Fatalf("begin: %v", ev) | |
| 111 | + | } | |
| 112 | + | if ev := g.swipeUpdate(pad, 1, 1, time.Second); ev != nil { | |
| 113 | + | t.Fatalf("swipe update during a pinch: %v", ev) | |
| 97 | 114 | } | |
| 98 | - | if ev := s.begin(1, 5, time.Second); ev != nil { | |
| 99 | - | t.Fatalf("five fingers during a swipe: %v", ev) | |
| 115 | + | if ev := g.end(pad, ports.GestureHold, false, time.Second); ev != nil { | |
| 116 | + | t.Fatalf("hold end during a pinch: %v", ev) | |
| 117 | + | } | |
| 118 | + | want := ports.PinchUpdate{DX: 1, DY: -2, Scale: 1.5, Rotation: -10, Time: 2 * time.Second} | |
| 119 | + | if ev := g.pinchUpdate(pad, 1, -2, 1.5, -10, 2*time.Second); ev != want { | |
| 120 | + | t.Fatalf("update: %v", ev) | |
| 121 | + | } | |
| 122 | + | if ev := g.end(pad, ports.GesturePinch, false, 3*time.Second); ev != (ports.PinchEnd{Time: 3 * time.Second}) { | |
| 123 | + | t.Fatalf("end: %v", ev) | |
| 124 | + | } | |
| 125 | + | if ev := g.pinchUpdate(pad, 1, 1, 1, 0, 4*time.Second); ev != nil { | |
| 126 | + | t.Fatalf("update after the end: %v", ev) | |
| 127 | + | } | |
| 128 | + | } | |
| 129 | + | ||
| 130 | + | func TestGesturesStreamHold(t *testing.T) { | |
| 131 | + | var g gestures | |
| 132 | + | const pad = 1 | |
| 133 | + | if ev := g.begin(pad, ports.GestureHold, 3, time.Second); ev != (ports.HoldBegin{Fingers: 3, Time: time.Second}) { | |
| 134 | + | t.Fatalf("begin: %v", ev) | |
| 135 | + | } | |
| 136 | + | if ev := g.begin(pad, ports.GesturePinch, 2, time.Second); ev != nil { | |
| 137 | + | t.Fatalf("pinch began during a hold: %v", ev) | |
| 138 | + | } | |
| 139 | + | if ev := g.end(pad, ports.GestureHold, true, 2*time.Second); ev != (ports.HoldEnd{Cancelled: true, Time: 2 * time.Second}) { | |
| 140 | + | t.Fatalf("end: %v", ev) | |
| 141 | + | } | |
| 142 | + | } | |
| 143 | + | ||
| 144 | + | // A device that goes during a gesture ends it cancelled, whatever its kind. | |
| 145 | + | func TestGesturesAbortOnRemoval(t *testing.T) { | |
| 146 | + | for kind, want := range map[ports.GestureKind]ports.InputEvent{ | |
| 147 | + | ports.GestureSwipe: ports.SwipeEnd{Cancelled: true}, | |
| 148 | + | ports.GesturePinch: ports.PinchEnd{Cancelled: true}, | |
| 149 | + | ports.GestureHold: ports.HoldEnd{Cancelled: true}, | |
| 150 | + | } { | |
| 151 | + | var g gestures | |
| 152 | + | g.begin(1, kind, 3, time.Second) | |
| 153 | + | if ev := g.abort(2); ev != nil { | |
| 154 | + | t.Fatalf("kind %d: another device aborted: %v", kind, ev) | |
| 155 | + | } | |
| 156 | + | if ev := g.abort(1); ev != want { | |
| 157 | + | t.Fatalf("kind %d: abort %v, want %v", kind, ev, want) | |
| 158 | + | } | |
| 159 | + | if ev := g.abort(1); ev != nil { | |
| 160 | + | t.Fatalf("kind %d: second abort %v", kind, ev) | |
| 161 | + | } | |
| 162 | + | if ev := g.begin(1, kind, 3, time.Second); ev == nil { | |
| 163 | + | t.Fatalf("kind %d: no gesture after the abort", kind) | |
| 164 | + | } | |
| 100 | 165 | } | |
| 101 | 166 | } | |
| 102 | 167 | ||
| @@ -262,6 +327,7 @@ func TestSecurityInputAllKindsProductionEpoch(t *testing.T) { | |||
| 262 | 327 | ports.KeyEvent{Keysym: "secret", Pressed: true}, ports.PointerMotion{X: 1}, | |
| 263 | 328 | ports.PointerButton{Button: 272}, ports.PointerAxis{}, | |
| 264 | 329 | ports.SwipeBegin{}, ports.SwipeUpdate{}, ports.SwipeEnd{}, | |
| 330 | + | ports.PinchBegin{}, ports.PinchUpdate{}, ports.PinchEnd{}, ports.HoldBegin{}, ports.HoldEnd{}, | |
| 265 | 331 | } | |
| 266 | 332 | security.EXPECT().Snapshot().Return(old).Times(len(events)) | |
| 267 | 333 | security.EXPECT().Snapshot().Return(newState).Once() | |
| @@ -42,61 +42,126 @@ func TestHotkey(t *testing.T) { | |||||
| 42 | } | 42 | } | ||
| 43 | } | 43 | } | ||
| 44 | 44 | ||||
| 45 | - | func TestSwipesIgnoreSecondTouchpad(t *testing.T) { | 45 | + | func TestGesturesIgnoreSecondTouchpad(t *testing.T) { |
| 46 | - | var s swipes | 46 | + | var g gestures |
| 47 | const pad, other = 1, 2 | 47 | const pad, other = 1, 2 | ||
| 48 | - | s.begin(pad, 3, time.Second) | 48 | + | g.begin(pad, ports.GestureSwipe, 3, time.Second) |
| 49 | - | if ev := s.begin(other, 3, time.Second); ev != nil { | 49 | + | if ev := g.begin(other, ports.GestureSwipe, 3, time.Second); ev != nil { |
| 50 | t.Fatalf("second touchpad began: %v", ev) | 50 | t.Fatalf("second touchpad began: %v", ev) | ||
| 51 | } | 51 | } | ||
| 52 | - | if ev := s.update(other, 1, 1, time.Second); ev != nil { | 52 | + | if ev := g.swipeUpdate(other, 1, 1, time.Second); ev != nil { |
| 53 | t.Fatalf("second touchpad moved: %v", ev) | 53 | t.Fatalf("second touchpad moved: %v", ev) | ||
| 54 | } | 54 | } | ||
| 55 | - | if ev := s.end(other, false, time.Second); ev != nil { | 55 | + | if ev := g.end(other, ports.GestureSwipe, false, time.Second); ev != nil { |
| 56 | t.Fatalf("second touchpad ended the swipe: %v", ev) | 56 | t.Fatalf("second touchpad ended the swipe: %v", ev) | ||
| 57 | } | 57 | } | ||
| 58 | - | if ev := s.update(pad, 1, 1, 2*time.Second); ev == nil { | 58 | + | if ev := g.swipeUpdate(pad, 1, 1, 2*time.Second); ev == nil { |
| 59 | t.Fatal("first touchpad lost its swipe") | 59 | t.Fatal("first touchpad lost its swipe") | ||
| 60 | } | 60 | } | ||
| 61 | - | s.end(pad, false, 3*time.Second) | 61 | + | g.end(pad, ports.GestureSwipe, false, 3*time.Second) |
| 62 | - | if ev := s.begin(other, 3, 4*time.Second); ev == nil { | 62 | + | if ev := g.begin(other, ports.GestureSwipe, 3, 4*time.Second); ev == nil { |
| 63 | t.Fatal("second touchpad cannot swipe after the first ended") | 63 | t.Fatal("second touchpad cannot swipe after the first ended") | ||
| 64 | } | 64 | } | ||
| 65 | } | 65 | } | ||
| 66 | 66 | ||||
| 67 | - | func TestSwipesStreamThreeFingers(t *testing.T) { | 67 | + | func TestGesturesStreamSwipe(t *testing.T) { |
| 68 | - | var s swipes | 68 | + | var g gestures |
| 69 | const pad, other = 1, 2 | 69 | const pad, other = 1, 2 | ||
| 70 | - | if ev := s.begin(pad, 2, time.Second); ev != nil { | 70 | + | if ev := g.begin(pad, ports.GestureSwipe, 0, time.Second); ev != nil { |
| 71 | - | t.Fatalf("two fingers: %v", ev) | 71 | + | t.Fatalf("no fingers: %v", ev) |
| 72 | } | 72 | } | ||
| 73 | - | if ev := s.update(pad, 1, 1, time.Second); ev != nil { | 73 | + | if ev := g.swipeUpdate(pad, 1, 1, time.Second); ev != nil { |
| 74 | t.Fatalf("update without a swipe: %v", ev) | 74 | t.Fatalf("update without a swipe: %v", ev) | ||
| 75 | } | 75 | } | ||
| 76 | - | if ev := s.begin(pad, 3, time.Second); ev != (ports.SwipeBegin{Fingers: 3, Time: time.Second}) { | 76 | + | if ev := g.begin(pad, ports.GestureSwipe, 3, time.Second); ev != (ports.SwipeBegin{Fingers: 3, Time: time.Second}) { |
| 77 | t.Fatalf("begin: %v", ev) | 77 | t.Fatalf("begin: %v", ev) | ||
| 78 | } | 78 | } | ||
| 79 | - | if ev := s.update(pad, -3, 2, 2*time.Second); ev != (ports.SwipeUpdate{DX: -3, DY: 2, Time: 2 * time.Second}) { | 79 | + | if ev := g.swipeUpdate(pad, -3, 2, 2*time.Second); ev != (ports.SwipeUpdate{DX: -3, DY: 2, Time: 2 * time.Second}) { |
| 80 | t.Fatalf("update: %v", ev) | 80 | t.Fatalf("update: %v", ev) | ||
| 81 | } | 81 | } | ||
| 82 | - | if ev := s.end(other, false, 0); ev != nil { | 82 | + | if ev := g.end(other, ports.GestureSwipe, false, 0); ev != nil { |
| 83 | t.Fatalf("end on another device: %v", ev) | 83 | t.Fatalf("end on another device: %v", ev) | ||
| 84 | } | 84 | } | ||
| 85 | - | if ev := s.end(pad, true, 3*time.Second); ev != (ports.SwipeEnd{Cancelled: true, Time: 3 * time.Second}) { | 85 | + | if ev := g.end(pad, ports.GestureSwipe, true, 3*time.Second); ev != (ports.SwipeEnd{Cancelled: true, Time: 3 * time.Second}) { |
| 86 | t.Fatalf("end: %v", ev) | 86 | t.Fatalf("end: %v", ev) | ||
| 87 | } | 87 | } | ||
| 88 | - | if ev := s.end(pad, false, 0); ev != nil { | 88 | + | if ev := g.end(pad, ports.GestureSwipe, false, 0); ev != nil { |
| 89 | t.Fatalf("second end: %v", ev) | 89 | t.Fatalf("second end: %v", ev) | ||
| 90 | } | 90 | } | ||
| 91 | } | 91 | } | ||
| 92 | 92 | ||||
| 93 | - | func TestSwipesStreamFourFingers(t *testing.T) { | 93 | + | // Swipes of every finger count stream; core picks which ones it keeps. |
| 94 | - | var s swipes | 94 | + | func TestGesturesStreamAnyFingerCount(t *testing.T) { |
| 95 | - | if ev := s.begin(1, 4, time.Second); ev != (ports.SwipeBegin{Fingers: 4, Time: time.Second}) { | 95 | + | for _, fingers := range []int{2, 3, 4, 5} { |
| 96 | - | t.Fatalf("four fingers: %v", ev) | 96 | + | var g gestures |
| 97 | + | if ev := g.begin(1, ports.GestureSwipe, fingers, time.Second); ev != (ports.SwipeBegin{Fingers: fingers, Time: time.Second}) { | |||
| 98 | + | t.Fatalf("%d fingers: %v", fingers, ev) | |||
| 99 | + | } | |||
| 100 | + | if ev := g.begin(1, ports.GestureSwipe, 5, time.Second); ev != nil { | |||
| 101 | + | t.Fatalf("a second swipe began during a swipe: %v", ev) | |||
| 102 | + | } | |||
| 103 | + | } | |||
| 104 | + | } | |||
| 105 | + | ||||
| 106 | + | func TestGesturesStreamPinch(t *testing.T) { | |||
| 107 | + | var g gestures | |||
| 108 | + | const pad = 1 | |||
| 109 | + | if ev := g.begin(pad, ports.GesturePinch, 2, time.Second); ev != (ports.PinchBegin{Fingers: 2, Time: time.Second}) { | |||
| 110 | + | t.Fatalf("begin: %v", ev) | |||
| 111 | + | } | |||
| 112 | + | if ev := g.swipeUpdate(pad, 1, 1, time.Second); ev != nil { | |||
| 113 | + | t.Fatalf("swipe update during a pinch: %v", ev) | |||
| 97 | } | 114 | } | ||
| 98 | - | if ev := s.begin(1, 5, time.Second); ev != nil { | 115 | + | if ev := g.end(pad, ports.GestureHold, false, time.Second); ev != nil { |
| 99 | - | t.Fatalf("five fingers during a swipe: %v", ev) | 116 | + | t.Fatalf("hold end during a pinch: %v", ev) |
| 117 | + | } | |||
| 118 | + | want := ports.PinchUpdate{DX: 1, DY: -2, Scale: 1.5, Rotation: -10, Time: 2 * time.Second} | |||
| 119 | + | if ev := g.pinchUpdate(pad, 1, -2, 1.5, -10, 2*time.Second); ev != want { | |||
| 120 | + | t.Fatalf("update: %v", ev) | |||
| 121 | + | } | |||
| 122 | + | if ev := g.end(pad, ports.GesturePinch, false, 3*time.Second); ev != (ports.PinchEnd{Time: 3 * time.Second}) { | |||
| 123 | + | t.Fatalf("end: %v", ev) | |||
| 124 | + | } | |||
| 125 | + | if ev := g.pinchUpdate(pad, 1, 1, 1, 0, 4*time.Second); ev != nil { | |||
| 126 | + | t.Fatalf("update after the end: %v", ev) | |||
| 127 | + | } | |||
| 128 | + | } | |||
| 129 | + | ||||
| 130 | + | func TestGesturesStreamHold(t *testing.T) { | |||
| 131 | + | var g gestures | |||
| 132 | + | const pad = 1 | |||
| 133 | + | if ev := g.begin(pad, ports.GestureHold, 3, time.Second); ev != (ports.HoldBegin{Fingers: 3, Time: time.Second}) { | |||
| 134 | + | t.Fatalf("begin: %v", ev) | |||
| 135 | + | } | |||
| 136 | + | if ev := g.begin(pad, ports.GesturePinch, 2, time.Second); ev != nil { | |||
| 137 | + | t.Fatalf("pinch began during a hold: %v", ev) | |||
| 138 | + | } | |||
| 139 | + | if ev := g.end(pad, ports.GestureHold, true, 2*time.Second); ev != (ports.HoldEnd{Cancelled: true, Time: 2 * time.Second}) { | |||
| 140 | + | t.Fatalf("end: %v", ev) | |||
| 141 | + | } | |||
| 142 | + | } | |||
| 143 | + | ||||
| 144 | + | // A device that goes during a gesture ends it cancelled, whatever its kind. | |||
| 145 | + | func TestGesturesAbortOnRemoval(t *testing.T) { | |||
| 146 | + | for kind, want := range map[ports.GestureKind]ports.InputEvent{ | |||
| 147 | + | ports.GestureSwipe: ports.SwipeEnd{Cancelled: true}, | |||
| 148 | + | ports.GesturePinch: ports.PinchEnd{Cancelled: true}, | |||
| 149 | + | ports.GestureHold: ports.HoldEnd{Cancelled: true}, | |||
| 150 | + | } { | |||
| 151 | + | var g gestures | |||
| 152 | + | g.begin(1, kind, 3, time.Second) | |||
| 153 | + | if ev := g.abort(2); ev != nil { | |||
| 154 | + | t.Fatalf("kind %d: another device aborted: %v", kind, ev) | |||
| 155 | + | } | |||
| 156 | + | if ev := g.abort(1); ev != want { | |||
| 157 | + | t.Fatalf("kind %d: abort %v, want %v", kind, ev, want) | |||
| 158 | + | } | |||
| 159 | + | if ev := g.abort(1); ev != nil { | |||
| 160 | + | t.Fatalf("kind %d: second abort %v", kind, ev) | |||
| 161 | + | } | |||
| 162 | + | if ev := g.begin(1, kind, 3, time.Second); ev == nil { | |||
| 163 | + | t.Fatalf("kind %d: no gesture after the abort", kind) | |||
| 164 | + | } | |||
| 100 | } | 165 | } | ||
| 101 | } | 166 | } | ||
| 102 | 167 | ||||
| @@ -262,6 +327,7 @@ func TestSecurityInputAllKindsProductionEpoch(t *testing.T) { | |||||
| 262 | ports.KeyEvent{Keysym: "secret", Pressed: true}, ports.PointerMotion{X: 1}, | 327 | ports.KeyEvent{Keysym: "secret", Pressed: true}, ports.PointerMotion{X: 1}, | ||
| 263 | ports.PointerButton{Button: 272}, ports.PointerAxis{}, | 328 | ports.PointerButton{Button: 272}, ports.PointerAxis{}, | ||
| 264 | ports.SwipeBegin{}, ports.SwipeUpdate{}, ports.SwipeEnd{}, | 329 | ports.SwipeBegin{}, ports.SwipeUpdate{}, ports.SwipeEnd{}, | ||
| 330 | + | ports.PinchBegin{}, ports.PinchUpdate{}, ports.PinchEnd{}, ports.HoldBegin{}, ports.HoldEnd{}, | |||
| 265 | } | 331 | } | ||
| 266 | security.EXPECT().Snapshot().Return(old).Times(len(events)) | 332 | security.EXPECT().Snapshot().Return(old).Times(len(events)) | ||
| 267 | security.EXPECT().Snapshot().Return(newState).Once() | 333 | security.EXPECT().Snapshot().Return(newState).Once() | ||
M internal/ports/client_commands.go +49 -0
| @@ -192,6 +192,55 @@ type PointerAxisTo struct { | |||
| 192 | 192 | ||
| 193 | 193 | func (PointerAxisTo) clientCommand() {} | |
| 194 | 194 | ||
| 195 | + | // GestureKind is a touchpad gesture a client can receive | |
| 196 | + | // (zwp_pointer_gestures_v1). | |
| 197 | + | type GestureKind uint8 | |
| 198 | + | ||
| 199 | + | const ( | |
| 200 | + | GestureSwipe GestureKind = iota | |
| 201 | + | GesturePinch | |
| 202 | + | GestureHold | |
| 203 | + | ) | |
| 204 | + | ||
| 205 | + | // GestureBeginTo starts a gesture on a window with the pointer focus. | |
| 206 | + | // Wayland assigns the serial. Time is the device timestamp | |
| 207 | + | // (CLOCK_MONOTONIC), like the updates and the end of the gesture. | |
| 208 | + | type GestureBeginTo struct { | |
| 209 | + | ID WindowID | |
| 210 | + | Kind GestureKind | |
| 211 | + | Fingers int | |
| 212 | + | Time time.Duration | |
| 213 | + | } | |
| 214 | + | ||
| 215 | + | func (GestureBeginTo) clientCommand() {} | |
| 216 | + | ||
| 217 | + | // GestureUpdateTo moves the gesture of a window. DX and DY are the | |
| 218 | + | // centre's movement; Scale and Rotation are only for a pinch (see | |
| 219 | + | // PinchUpdate). A hold has no updates. | |
| 220 | + | type GestureUpdateTo struct { | |
| 221 | + | ID WindowID | |
| 222 | + | Kind GestureKind | |
| 223 | + | DX, DY float64 | |
| 224 | + | Scale float64 | |
| 225 | + | Rotation float64 | |
| 226 | + | Time time.Duration | |
| 227 | + | } | |
| 228 | + | ||
| 229 | + | func (GestureUpdateTo) clientCommand() {} | |
| 230 | + | ||
| 231 | + | // GestureEndTo ends the gesture of a window. Cancelled is set when the | |
| 232 | + | // gesture did not complete: the pointer focus moved, the window went away | |
| 233 | + | // or the device cancelled it. Ending a gesture that is not running does | |
| 234 | + | // nothing. | |
| 235 | + | type GestureEndTo struct { | |
| 236 | + | ID WindowID | |
| 237 | + | Kind GestureKind | |
| 238 | + | Cancelled bool | |
| 239 | + | Time time.Duration | |
| 240 | + | } | |
| 241 | + | ||
| 242 | + | func (GestureEndTo) clientCommand() {} | |
| 243 | + | ||
| 195 | 244 | // ForwardKey carries core → wayland unbound keys. | |
| 196 | 245 | type ForwardKey struct { | |
| 197 | 246 | ID WindowID | |
| @@ -192,6 +192,55 @@ type PointerAxisTo struct { | |||||
| 192 | 192 | ||||
| 193 | func (PointerAxisTo) clientCommand() {} | 193 | func (PointerAxisTo) clientCommand() {} | ||
| 194 | 194 | ||||
| 195 | + | // GestureKind is a touchpad gesture a client can receive | |||
| 196 | + | // (zwp_pointer_gestures_v1). | |||
| 197 | + | type GestureKind uint8 | |||
| 198 | + | ||||
| 199 | + | const ( | |||
| 200 | + | GestureSwipe GestureKind = iota | |||
| 201 | + | GesturePinch | |||
| 202 | + | GestureHold | |||
| 203 | + | ) | |||
| 204 | + | ||||
| 205 | + | // GestureBeginTo starts a gesture on a window with the pointer focus. | |||
| 206 | + | // Wayland assigns the serial. Time is the device timestamp | |||
| 207 | + | // (CLOCK_MONOTONIC), like the updates and the end of the gesture. | |||
| 208 | + | type GestureBeginTo struct { | |||
| 209 | + | ID WindowID | |||
| 210 | + | Kind GestureKind | |||
| 211 | + | Fingers int | |||
| 212 | + | Time time.Duration | |||
| 213 | + | } | |||
| 214 | + | ||||
| 215 | + | func (GestureBeginTo) clientCommand() {} | |||
| 216 | + | ||||
| 217 | + | // GestureUpdateTo moves the gesture of a window. DX and DY are the | |||
| 218 | + | // centre's movement; Scale and Rotation are only for a pinch (see | |||
| 219 | + | // PinchUpdate). A hold has no updates. | |||
| 220 | + | type GestureUpdateTo struct { | |||
| 221 | + | ID WindowID | |||
| 222 | + | Kind GestureKind | |||
| 223 | + | DX, DY float64 | |||
| 224 | + | Scale float64 | |||
| 225 | + | Rotation float64 | |||
| 226 | + | Time time.Duration | |||
| 227 | + | } | |||
| 228 | + | ||||
| 229 | + | func (GestureUpdateTo) clientCommand() {} | |||
| 230 | + | ||||
| 231 | + | // GestureEndTo ends the gesture of a window. Cancelled is set when the | |||
| 232 | + | // gesture did not complete: the pointer focus moved, the window went away | |||
| 233 | + | // or the device cancelled it. Ending a gesture that is not running does | |||
| 234 | + | // nothing. | |||
| 235 | + | type GestureEndTo struct { | |||
| 236 | + | ID WindowID | |||
| 237 | + | Kind GestureKind | |||
| 238 | + | Cancelled bool | |||
| 239 | + | Time time.Duration | |||
| 240 | + | } | |||
| 241 | + | ||||
| 242 | + | func (GestureEndTo) clientCommand() {} | |||
| 243 | + | ||||
| 195 | // ForwardKey carries core → wayland unbound keys. | 244 | // ForwardKey carries core → wayland unbound keys. | ||
| 196 | type ForwardKey struct { | 245 | type ForwardKey struct { | ||
| 197 | ID WindowID | 246 | ID WindowID | ||
M internal/ports/input_events.go +50 -2
| @@ -88,8 +88,10 @@ type PointerAxis struct { | |||
| 88 | 88 | ||
| 89 | 89 | func (PointerAxis) inputEvent() {} | |
| 90 | 90 | ||
| 91 | - | // SwipeBegin starts a three- or four-finger touchpad swipe. Time is the | |
| 92 | - | // device timestamp (CLOCK_MONOTONIC), shared by the updates and the end. | |
| 91 | + | // SwipeBegin starts a touchpad swipe of any finger count; core keeps three | |
| 92 | + | // and four fingers and forwards the others to the window under the | |
| 93 | + | // pointer. Time is the device timestamp (CLOCK_MONOTONIC), shared by the | |
| 94 | + | // updates and the end. | |
| 93 | 95 | type SwipeBegin struct { | |
| 94 | 96 | Fingers int | |
| 95 | 97 | Time time.Duration | |
| @@ -112,3 +114,49 @@ type SwipeEnd struct { | |||
| 112 | 114 | func (SwipeBegin) inputEvent() {} | |
| 113 | 115 | func (SwipeUpdate) inputEvent() {} | |
| 114 | 116 | func (SwipeEnd) inputEvent() {} | |
| 117 | + | ||
| 118 | + | // PinchBegin starts a touchpad pinch. Time is the device timestamp | |
| 119 | + | // (CLOCK_MONOTONIC), shared by the updates and the end. A pinch always goes | |
| 120 | + | // to the window under the pointer. | |
| 121 | + | type PinchBegin struct { | |
| 122 | + | Fingers int | |
| 123 | + | Time time.Duration | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | // PinchUpdate moves a pinch: the fingers' centre by the unaccelerated | |
| 127 | + | // deltas DX, DY (touchpad units), Scale is the spread of the fingers | |
| 128 | + | // relative to the begin (1.0 there) and Rotation the angle turned since the | |
| 129 | + | // previous event, in degrees clockwise. | |
| 130 | + | type PinchUpdate struct { | |
| 131 | + | DX, DY float64 | |
| 132 | + | Scale float64 | |
| 133 | + | Rotation float64 | |
| 134 | + | Time time.Duration | |
| 135 | + | } | |
| 136 | + | ||
| 137 | + | // PinchEnd lifts the fingers. Cancelled is set when libinput cancelled the | |
| 138 | + | // pinch. | |
| 139 | + | type PinchEnd struct { | |
| 140 | + | Cancelled bool | |
| 141 | + | Time time.Duration | |
| 142 | + | } | |
| 143 | + | ||
| 144 | + | // HoldBegin starts a touchpad hold: fingers resting on the pad. Like a | |
| 145 | + | // pinch it goes to the window under the pointer. | |
| 146 | + | type HoldBegin struct { | |
| 147 | + | Fingers int | |
| 148 | + | Time time.Duration | |
| 149 | + | } | |
| 150 | + | ||
| 151 | + | // HoldEnd lifts the fingers. Cancelled is set when the fingers moved or | |
| 152 | + | // another finger landed, so the hold became something else. | |
| 153 | + | type HoldEnd struct { | |
| 154 | + | Cancelled bool | |
| 155 | + | Time time.Duration | |
| 156 | + | } | |
| 157 | + | ||
| 158 | + | func (PinchBegin) inputEvent() {} | |
| 159 | + | func (PinchUpdate) inputEvent() {} | |
| 160 | + | func (PinchEnd) inputEvent() {} | |
| 161 | + | func (HoldBegin) inputEvent() {} | |
| 162 | + | func (HoldEnd) inputEvent() {} | |
| @@ -88,8 +88,10 @@ type PointerAxis struct { | |||||
| 88 | 88 | ||||
| 89 | func (PointerAxis) inputEvent() {} | 89 | func (PointerAxis) inputEvent() {} | ||
| 90 | 90 | ||||
| 91 | - | // SwipeBegin starts a three- or four-finger touchpad swipe. Time is the | 91 | + | // SwipeBegin starts a touchpad swipe of any finger count; core keeps three |
| 92 | - | // device timestamp (CLOCK_MONOTONIC), shared by the updates and the end. | 92 | + | // and four fingers and forwards the others to the window under the |
| 93 | + | // pointer. Time is the device timestamp (CLOCK_MONOTONIC), shared by the | |||
| 94 | + | // updates and the end. | |||
| 93 | type SwipeBegin struct { | 95 | type SwipeBegin struct { | ||
| 94 | Fingers int | 96 | Fingers int | ||
| 95 | Time time.Duration | 97 | Time time.Duration | ||
| @@ -112,3 +114,49 @@ type SwipeEnd struct { | |||||
| 112 | func (SwipeBegin) inputEvent() {} | 114 | func (SwipeBegin) inputEvent() {} | ||
| 113 | func (SwipeUpdate) inputEvent() {} | 115 | func (SwipeUpdate) inputEvent() {} | ||
| 114 | func (SwipeEnd) inputEvent() {} | 116 | func (SwipeEnd) inputEvent() {} | ||
| 117 | + | ||||
| 118 | + | // PinchBegin starts a touchpad pinch. Time is the device timestamp | |||
| 119 | + | // (CLOCK_MONOTONIC), shared by the updates and the end. A pinch always goes | |||
| 120 | + | // to the window under the pointer. | |||
| 121 | + | type PinchBegin struct { | |||
| 122 | + | Fingers int | |||
| 123 | + | Time time.Duration | |||
| 124 | + | } | |||
| 125 | + | ||||
| 126 | + | // PinchUpdate moves a pinch: the fingers' centre by the unaccelerated | |||
| 127 | + | // deltas DX, DY (touchpad units), Scale is the spread of the fingers | |||
| 128 | + | // relative to the begin (1.0 there) and Rotation the angle turned since the | |||
| 129 | + | // previous event, in degrees clockwise. | |||
| 130 | + | type PinchUpdate struct { | |||
| 131 | + | DX, DY float64 | |||
| 132 | + | Scale float64 | |||
| 133 | + | Rotation float64 | |||
| 134 | + | Time time.Duration | |||
| 135 | + | } | |||
| 136 | + | ||||
| 137 | + | // PinchEnd lifts the fingers. Cancelled is set when libinput cancelled the | |||
| 138 | + | // pinch. | |||
| 139 | + | type PinchEnd struct { | |||
| 140 | + | Cancelled bool | |||
| 141 | + | Time time.Duration | |||
| 142 | + | } | |||
| 143 | + | ||||
| 144 | + | // HoldBegin starts a touchpad hold: fingers resting on the pad. Like a | |||
| 145 | + | // pinch it goes to the window under the pointer. | |||
| 146 | + | type HoldBegin struct { | |||
| 147 | + | Fingers int | |||
| 148 | + | Time time.Duration | |||
| 149 | + | } | |||
| 150 | + | ||||
| 151 | + | // HoldEnd lifts the fingers. Cancelled is set when the fingers moved or | |||
| 152 | + | // another finger landed, so the hold became something else. | |||
| 153 | + | type HoldEnd struct { | |||
| 154 | + | Cancelled bool | |||
| 155 | + | Time time.Duration | |||
| 156 | + | } | |||
| 157 | + | ||||
| 158 | + | func (PinchBegin) inputEvent() {} | |||
| 159 | + | func (PinchUpdate) inputEvent() {} | |||
| 160 | + | func (PinchEnd) inputEvent() {} | |||
| 161 | + | func (HoldBegin) inputEvent() {} | |||
| 162 | + | func (HoldEnd) inputEvent() {} | |||