mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-18 09:14:01 -04:00
feat(screenshot): add touch & stylus support for region capture (#3353)
* feat(screenshot): add touch and stylus input support for region capture
* fix(screenshot): guard against finishSelection on touch up during scroll phase
port 1.6
(cherry picked from commit a9fce46fec)
This commit is contained in:
committed by
dms-ci[bot]
parent
710f20d085
commit
3f09d7b1c8
@@ -108,6 +108,7 @@ type RegionSelector struct {
|
||||
shm *client.Shm
|
||||
seat *client.Seat
|
||||
pointer *client.Pointer
|
||||
touch *client.Touch
|
||||
keyboard *client.Keyboard
|
||||
keymap *keymap.Keymap
|
||||
layerShell *wlr_layer_shell.ZwlrLayerShellV1
|
||||
@@ -138,6 +139,8 @@ type RegionSelector struct {
|
||||
selection SelectionState
|
||||
pointerX float64
|
||||
pointerY float64
|
||||
hasTouchPoint bool
|
||||
touchPointId int32
|
||||
preSelect Region
|
||||
showCapturedCursor bool
|
||||
shiftHeld bool
|
||||
@@ -1103,6 +1106,9 @@ func (r *RegionSelector) cleanup() {
|
||||
if r.pointer != nil {
|
||||
r.pointer.Release()
|
||||
}
|
||||
if r.touch != nil {
|
||||
_ = r.touch.Release()
|
||||
}
|
||||
if r.keyboard != nil {
|
||||
r.keyboard.Release()
|
||||
}
|
||||
|
||||
@@ -19,6 +19,12 @@ func (r *RegionSelector) setupInput() {
|
||||
r.setupPointerHandlers()
|
||||
}
|
||||
}
|
||||
if e.Capabilities&uint32(client.SeatCapabilityTouch) != 0 && r.touch == nil {
|
||||
if touch, err := r.seat.GetTouch(); err == nil {
|
||||
r.touch = touch
|
||||
r.setupTouchHandlers()
|
||||
}
|
||||
}
|
||||
if e.Capabilities&uint32(client.SeatCapabilityKeyboard) != 0 && r.keyboard == nil {
|
||||
if keyboard, err := r.seat.GetKeyboard(); err == nil {
|
||||
r.keyboard = keyboard
|
||||
@@ -146,6 +152,143 @@ func (r *RegionSelector) setupPointerHandlers() {
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RegionSelector) setupTouchHandlers() {
|
||||
r.touch.SetDownHandler(func(e client.TouchDownEvent) {
|
||||
r.handleTouchDown(e.Surface, e.Id, e.X, e.Y)
|
||||
})
|
||||
|
||||
r.touch.SetMotionHandler(func(e client.TouchMotionEvent) {
|
||||
r.handleTouchMotion(e.Id, e.X, e.Y)
|
||||
})
|
||||
|
||||
r.touch.SetUpHandler(func(e client.TouchUpEvent) {
|
||||
r.handleTouchUp(e.Id)
|
||||
})
|
||||
|
||||
r.touch.SetCancelHandler(func(e client.TouchCancelEvent) {
|
||||
r.handleTouchCancel()
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RegionSelector) handleTouchDown(surface *client.Surface, touchId int32, x, y float64) {
|
||||
if r.hasTouchPoint {
|
||||
return
|
||||
}
|
||||
|
||||
r.activeSurface = nil
|
||||
for _, os := range r.surfaces {
|
||||
if os.wlSurface != nil && surface != nil && os.wlSurface.ID() == surface.ID() {
|
||||
r.activeSurface = os
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if r.activeSurface == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.hasTouchPoint = true
|
||||
r.touchPointId = touchId
|
||||
|
||||
if r.phase == phaseScroll {
|
||||
if r.activeSurface != r.selection.surface {
|
||||
return
|
||||
}
|
||||
switch r.scrollBarHit(x, y) {
|
||||
case "done", "preview":
|
||||
r.finishScroll()
|
||||
case "cancel":
|
||||
r.cancelled = true
|
||||
r.running = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
touchX := x + float64(r.activeSurface.output.x)
|
||||
touchY := y + float64(r.activeSurface.output.y)
|
||||
|
||||
if r.ctrlHeld && r.selection.hasSelection {
|
||||
if handle := r.resizeHandleAt(touchX, touchY); handle != handleNone {
|
||||
if r.beginSelectionResize(handle, touchX, touchY) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if r.beginSelectionMove(touchX, touchY) {
|
||||
r.selection.dragging = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.preSelect = Region{}
|
||||
r.movingSelection = false
|
||||
r.resizingHandle = handleNone
|
||||
r.selection.hasSelection = true
|
||||
r.selection.dragging = true
|
||||
r.selection.surface = r.activeSurface
|
||||
r.selection.anchorX = touchX
|
||||
r.selection.anchorY = touchY
|
||||
r.selection.currentX = r.selection.anchorX
|
||||
r.selection.currentY = r.selection.anchorY
|
||||
|
||||
for _, os := range r.surfaces {
|
||||
r.redrawSurface(os)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RegionSelector) handleTouchMotion(touchId int32, x, y float64) {
|
||||
if !r.hasTouchPoint || touchId != r.touchPointId || r.activeSurface == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.phase == phaseScroll {
|
||||
return
|
||||
}
|
||||
|
||||
if !r.selection.dragging {
|
||||
return
|
||||
}
|
||||
|
||||
r.updateSelectionCurrent(r.activeSurface, x, y)
|
||||
}
|
||||
|
||||
func (r *RegionSelector) handleTouchUp(touchId int32) {
|
||||
if !r.hasTouchPoint || touchId != r.touchPointId {
|
||||
return
|
||||
}
|
||||
|
||||
r.hasTouchPoint = false
|
||||
r.selection.dragging = false
|
||||
r.movingSelection = false
|
||||
r.resizingHandle = handleNone
|
||||
|
||||
if r.phase == phaseScroll {
|
||||
return
|
||||
}
|
||||
|
||||
for _, os := range r.surfaces {
|
||||
r.redrawSurface(os)
|
||||
}
|
||||
|
||||
if r.screenshoter != nil && r.screenshoter.config.NoConfirm && r.selection.hasSelection {
|
||||
r.finishSelection()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RegionSelector) handleTouchCancel() {
|
||||
if !r.hasTouchPoint {
|
||||
return
|
||||
}
|
||||
|
||||
r.hasTouchPoint = false
|
||||
r.selection.dragging = false
|
||||
r.movingSelection = false
|
||||
r.resizingHandle = handleNone
|
||||
|
||||
for _, os := range r.surfaces {
|
||||
r.redrawSurface(os)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RegionSelector) updateSelectionCurrent(os *OutputSurface, surfaceX, surfaceY float64) {
|
||||
if os == nil || os.output == nil || !r.selection.dragging {
|
||||
return
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package screenshot
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/AvengeMedia/dankgo/wayland/client"
|
||||
)
|
||||
|
||||
func newTestSurface(id uint32) *client.Surface {
|
||||
s := &client.Surface{}
|
||||
s.SetID(id)
|
||||
return s
|
||||
}
|
||||
|
||||
func TestTouchInputSequence(t *testing.T) {
|
||||
surface := newTestSurface(100)
|
||||
|
||||
os := &OutputSurface{
|
||||
output: &WaylandOutput{
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
logicalW: 1920,
|
||||
logicalH: 1080,
|
||||
wlSurface: surface,
|
||||
}
|
||||
|
||||
r := &RegionSelector{
|
||||
running: true,
|
||||
surfaces: []*OutputSurface{os},
|
||||
}
|
||||
|
||||
// 1. Touch Down
|
||||
r.handleTouchDown(surface, 1, 100, 200)
|
||||
|
||||
if !r.hasTouchPoint || r.touchPointId != 1 {
|
||||
t.Fatalf("expected hasTouchPoint=true, touchPointId=1, got hasTouchPoint=%v, id=%d", r.hasTouchPoint, r.touchPointId)
|
||||
}
|
||||
if !r.selection.dragging || !r.selection.hasSelection {
|
||||
t.Fatalf("expected dragging=true, hasSelection=true")
|
||||
}
|
||||
if r.selection.anchorX != 100 || r.selection.anchorY != 200 {
|
||||
t.Fatalf("expected anchor (100, 200), got (%.1f, %.1f)", r.selection.anchorX, r.selection.anchorY)
|
||||
}
|
||||
|
||||
// 2. Ignore secondary touch point while primary is active
|
||||
surface2 := newTestSurface(200)
|
||||
r.handleTouchDown(surface2, 2, 500, 600)
|
||||
if r.touchPointId != 1 {
|
||||
t.Fatalf("expected touchPointId to stay 1, got %d", r.touchPointId)
|
||||
}
|
||||
|
||||
// 3. Touch Motion with matching id
|
||||
r.handleTouchMotion(1, 400, 500)
|
||||
if r.selection.currentX != 400 || r.selection.currentY != 500 {
|
||||
t.Fatalf("expected current (400, 500), got (%.1f, %.1f)", r.selection.currentX, r.selection.currentY)
|
||||
}
|
||||
|
||||
// 4. Touch Motion with mismatched id should be ignored
|
||||
r.handleTouchMotion(2, 900, 900)
|
||||
if r.selection.currentX != 400 || r.selection.currentY != 500 {
|
||||
t.Fatalf("expected current to remain (400, 500), got (%.1f, %.1f)", r.selection.currentX, r.selection.currentY)
|
||||
}
|
||||
|
||||
// 5. Touch Up with mismatched id should not release
|
||||
r.handleTouchUp(2)
|
||||
if !r.hasTouchPoint || !r.selection.dragging {
|
||||
t.Fatalf("expected touch point to remain active on mismatched touch up")
|
||||
}
|
||||
|
||||
// 6. Touch Up with matching id
|
||||
r.handleTouchUp(1)
|
||||
if r.hasTouchPoint || r.selection.dragging {
|
||||
t.Fatalf("expected hasTouchPoint=false, dragging=false after touch up")
|
||||
}
|
||||
if !r.selection.hasSelection {
|
||||
t.Fatalf("expected selection to be preserved after touch up")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTouchCancel(t *testing.T) {
|
||||
surface := newTestSurface(100)
|
||||
|
||||
os := &OutputSurface{
|
||||
output: &WaylandOutput{
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
logicalW: 1920,
|
||||
logicalH: 1080,
|
||||
wlSurface: surface,
|
||||
}
|
||||
|
||||
r := &RegionSelector{
|
||||
running: true,
|
||||
surfaces: []*OutputSurface{os},
|
||||
}
|
||||
|
||||
r.handleTouchDown(surface, 5, 50, 60)
|
||||
if !r.hasTouchPoint || !r.selection.dragging {
|
||||
t.Fatalf("expected active touch point")
|
||||
}
|
||||
|
||||
r.handleTouchCancel()
|
||||
if r.hasTouchPoint || r.selection.dragging {
|
||||
t.Fatalf("expected touch point and dragging to be cleared on cancel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTouchUpScrollPhase(t *testing.T) {
|
||||
surface := newTestSurface(100)
|
||||
|
||||
os := &OutputSurface{
|
||||
output: &WaylandOutput{
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
logicalW: 1920,
|
||||
logicalH: 1080,
|
||||
wlSurface: surface,
|
||||
}
|
||||
|
||||
sc := &Screenshoter{
|
||||
config: Config{NoConfirm: true},
|
||||
}
|
||||
|
||||
r := &RegionSelector{
|
||||
screenshoter: sc,
|
||||
phase: phaseScroll,
|
||||
running: true,
|
||||
surfaces: []*OutputSurface{os},
|
||||
selection: SelectionState{
|
||||
hasSelection: true,
|
||||
surface: os,
|
||||
},
|
||||
}
|
||||
|
||||
r.hasTouchPoint = true
|
||||
r.touchPointId = 3
|
||||
r.selection.dragging = true
|
||||
|
||||
r.handleTouchUp(3)
|
||||
|
||||
if r.hasTouchPoint || r.selection.dragging {
|
||||
t.Fatalf("expected touch point and dragging cleared")
|
||||
}
|
||||
|
||||
// In scroll phase with NoConfirm, handleTouchUp must not call finishSelection / change running state
|
||||
if !r.running {
|
||||
t.Fatalf("expected running to stay true, did not expect finishSelection side-effects")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user