Skip to content

Commit 145ef64

Browse files
authored
fix drag mouse (#79)
Did some testing on this site: https://master--5fc05e08a4a65d0021ae0bf2.chromatic.com/?path=/story/core-draggable-hooks-usedraggable--basic-setup discovered that the number of steps and the delay between them as you drag the mouse is important to finesse found success with the defaults in this PR, but making them configurable in case users want to deviate # Checklist - [ ] A link to a related issue in our repository - [ ] A description of the changes proposed in the pull request. - [ ] @mentions of the person or team responsible for reviewing proposed changes. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Implements smooth drag using relative step movements with configurable per-segment step count and per-step delay, updates OpenAPI, and adds tests. > > - **Backend (server/cmd/api/api/computer.go)**: > - **DragMouse**: Move along `path` via `mousemove_relative` steps with small sleeps for smoothing; derive steps using `generateRelativeSteps`; support `steps_per_segment` (default `10`) and `step_delay_ms` (default `50`). > - Add `generateRelativeSteps(dx, dy, steps)` helper to distribute integer relative moves. > - **Tests (server/cmd/api/api/computer_test.go)**: > - Add unit tests covering zero, axis-aligned, and diagonal/sloped cases for `generateRelativeSteps`. > - **API/Schema**: > - `DragMouseRequest`: add `steps_per_segment` and `step_delay_ms` (with defaults/min constraints) in `server/openapi.yaml` and generated `server/lib/oapi/oapi.go`. > - `PressKeyRequest.keys` doc: note items may be combos like `Ctrl+t`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9c427f3. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 4ef116e commit 145ef64

File tree

4 files changed

+233
-94
lines changed

4 files changed

+233
-94
lines changed

server/cmd/api/api/computer.go

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/base64"
66
"fmt"
77
"io"
8+
"math"
89
"os"
910
"os/exec"
1011
"strconv"
@@ -669,10 +670,44 @@ func (s *ApiService) DragMouse(ctx context.Context, request oapi.DragMouseReques
669670
time.Sleep(time.Duration(*body.Delay) * time.Millisecond)
670671
}
671672

672-
// Phase 2: move along path (excluding first point)
673+
// Phase 2: move along path (excluding first point) using fixed-count relative steps
674+
// Insert a small delay between each relative move to smooth the drag
673675
args2 := []string{}
676+
// Determine per-segment steps and per-step delay from request (with defaults)
677+
stepsPerSegment := 10
678+
if body.StepsPerSegment != nil && *body.StepsPerSegment >= 1 {
679+
stepsPerSegment = *body.StepsPerSegment
680+
}
681+
stepDelayMs := 50
682+
if body.StepDelayMs != nil && *body.StepDelayMs >= 0 {
683+
stepDelayMs = *body.StepDelayMs
684+
}
685+
stepDelaySeconds := fmt.Sprintf("%.3f", float64(stepDelayMs)/1000.0)
686+
687+
// Precompute total number of relative steps so we can avoid a trailing sleep
688+
totalSteps := 0
689+
prev := start
690+
for _, pt := range body.Path[1:] {
691+
x0, y0 := prev[0], prev[1]
692+
x1, y1 := pt[0], pt[1]
693+
totalSteps += len(generateRelativeSteps(x1-x0, y1-y0, stepsPerSegment))
694+
prev = pt
695+
}
696+
697+
prev = start
698+
stepIndex := 0
674699
for _, pt := range body.Path[1:] {
675-
args2 = append(args2, "mousemove", "--sync", strconv.Itoa(pt[0]), strconv.Itoa(pt[1]))
700+
x0, y0 := prev[0], prev[1]
701+
x1, y1 := pt[0], pt[1]
702+
for _, step := range generateRelativeSteps(x1-x0, y1-y0, stepsPerSegment) {
703+
args2 = append(args2, "mousemove_relative", strconv.Itoa(step[0]), strconv.Itoa(step[1]))
704+
// add a tiny delay between moves, but not after the last step
705+
if stepIndex < totalSteps-1 && stepDelayMs > 0 {
706+
args2 = append(args2, "sleep", stepDelaySeconds)
707+
}
708+
stepIndex++
709+
}
710+
prev = pt
676711
}
677712
if len(args2) > 0 {
678713
log.Info("executing xdotool (drag move)", "args", args2)
@@ -709,3 +744,37 @@ func (s *ApiService) DragMouse(ctx context.Context, request oapi.DragMouseReques
709744

710745
return oapi.DragMouse200Response{}, nil
711746
}
747+
748+
// generateRelativeSteps produces a sequence of relative steps that approximate a
749+
// straight line from (0,0) to (dx,dy) using at most the provided number of
750+
// steps. Each returned element is a pair {stepX, stepY}. The steps are
751+
// distributed so that the cumulative sum equals exactly (dx, dy). If dx and dy
752+
// are both zero, no steps are returned. If the requested step count is less
753+
// than the distance, the per-step movement will be greater than one pixel.
754+
func generateRelativeSteps(dx, dy, steps int) [][2]int {
755+
if steps <= 0 {
756+
return nil
757+
}
758+
if dx == 0 && dy == 0 {
759+
return nil
760+
}
761+
762+
out := make([][2]int, 0, steps)
763+
764+
// Use cumulative rounding to distribute integers across the requested
765+
// number of steps while preserving the exact totals.
766+
prevCX := 0
767+
prevCY := 0
768+
for i := 1; i <= steps; i++ {
769+
// Target cumulative positions after i steps
770+
cx := int(math.Round(float64(i*dx) / float64(steps)))
771+
cy := int(math.Round(float64(i*dy) / float64(steps)))
772+
sx := cx - prevCX
773+
sy := cy - prevCY
774+
prevCX = cx
775+
prevCY = cy
776+
out = append(out, [2]int{sx, sy})
777+
}
778+
779+
return out
780+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package api
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func sumSteps(steps [][2]int) (int, int) {
10+
sx, sy := 0, 0
11+
for _, s := range steps {
12+
sx += s[0]
13+
sy += s[1]
14+
}
15+
return sx, sy
16+
}
17+
18+
func countSteps(steps [][2]int) int { return len(steps) }
19+
20+
func TestGenerateRelativeSteps_Zero(t *testing.T) {
21+
steps := generateRelativeSteps(0, 0, 5)
22+
require.Len(t, steps, 0, "expected 0 steps")
23+
}
24+
25+
func TestGenerateRelativeSteps_AxisAligned(t *testing.T) {
26+
cases := []struct {
27+
dx, dy int
28+
}{
29+
{5, 0}, {-7, 0}, {0, 9}, {0, -3},
30+
}
31+
for _, c := range cases {
32+
steps := generateRelativeSteps(c.dx, c.dy, 5)
33+
sx, sy := sumSteps(steps)
34+
require.Equal(t, c.dx, sx, "sum mismatch dx")
35+
require.Equal(t, c.dy, sy, "sum mismatch dy")
36+
require.Equal(t, 5, countSteps(steps), "count mismatch")
37+
}
38+
}
39+
40+
func TestGenerateRelativeSteps_DiagonalsAndSlopes(t *testing.T) {
41+
cases := []struct{ dx, dy int }{
42+
{5, 5}, {-4, -4}, {8, 3}, {3, 8}, {-9, 2}, {2, -9},
43+
}
44+
for _, c := range cases {
45+
steps := generateRelativeSteps(c.dx, c.dy, 5)
46+
sx, sy := sumSteps(steps)
47+
require.Equal(t, c.dx, sx, "sum mismatch dx")
48+
require.Equal(t, c.dy, sy, "sum mismatch dy")
49+
require.Equal(t, 5, countSteps(steps), "count mismatch")
50+
}
51+
}

0 commit comments

Comments
 (0)