Last active
October 8, 2025 13:12
-
-
Save xyproto/be6e8ca8448917cd1396b8b83a8c85af to your computer and use it in GitHub Desktop.
Make the mouse cursor move in boid-like ways, for Windows (shake to pause)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "fmt" | |
| "math" | |
| "syscall" | |
| "time" | |
| "unsafe" | |
| ) | |
| type POINT struct { | |
| X, Y int32 | |
| } | |
| type RECT struct { | |
| Left, Top, Right, Bottom int32 | |
| } | |
| type Boid struct { | |
| X, Y float64 | |
| VelX, VelY float64 | |
| } | |
| type MovementEvent struct { | |
| deltaX float64 | |
| time time.Time | |
| } | |
| var ( | |
| globalStartTime time.Time | |
| cursorBoid Boid | |
| maxSpeed float64 = 5.0 | |
| maxForce float64 = 0.2 | |
| orbitRadius float64 = 40.0 | |
| screenBounds RECT | |
| ) | |
| var ( | |
| user32 = syscall.NewLazyDLL("user32.dll") | |
| setCursorPosProc = user32.NewProc("SetCursorPos") | |
| getCursorPosProc = user32.NewProc("GetCursorPos") | |
| getSystemMetricsProc = user32.NewProc("GetSystemMetrics") | |
| getDesktopWindowProc = user32.NewProc("GetDesktopWindow") | |
| getWindowRectProc = user32.NewProc("GetWindowRect") | |
| ) | |
| const ( | |
| SM_XVIRTUALSCREEN = 76 | |
| SM_YVIRTUALSCREEN = 77 | |
| SM_CXVIRTUALSCREEN = 78 | |
| SM_CYVIRTUALSCREEN = 79 | |
| ) | |
| func SetCursorPos(x, y int) bool { | |
| ret, _, _ := syscall.SyscallN(setCursorPosProc.Addr(), uintptr(x), uintptr(y)) | |
| return ret != 0 | |
| } | |
| func GetCursorPos() (POINT, error) { | |
| var pt POINT | |
| ret, _, err := syscall.SyscallN(getCursorPosProc.Addr(), uintptr(unsafe.Pointer(&pt))) | |
| if ret == 0 { | |
| return POINT{}, fmt.Errorf("failed to get cursor position: %v", err) | |
| } | |
| return pt, nil | |
| } | |
| func getVirtualScreenBounds() RECT { | |
| x, _, _ := getSystemMetricsProc.Call(SM_XVIRTUALSCREEN) | |
| y, _, _ := getSystemMetricsProc.Call(SM_YVIRTUALSCREEN) | |
| w, _, _ := getSystemMetricsProc.Call(SM_CXVIRTUALSCREEN) | |
| h, _, _ := getSystemMetricsProc.Call(SM_CYVIRTUALSCREEN) | |
| return RECT{ | |
| Left: int32(x), | |
| Top: int32(y), | |
| Right: int32(x) + int32(w) - 1, | |
| Bottom: int32(y) + int32(h) - 1, | |
| } | |
| } | |
| func constrainToBounds(x, y float64) (float64, float64) { | |
| if x < float64(screenBounds.Left) { | |
| x = float64(screenBounds.Left) | |
| } | |
| if x > float64(screenBounds.Right) { | |
| x = float64(screenBounds.Right) | |
| } | |
| if y < float64(screenBounds.Top) { | |
| y = float64(screenBounds.Top) | |
| } | |
| if y > float64(screenBounds.Bottom) { | |
| y = float64(screenBounds.Bottom) | |
| } | |
| return x, y | |
| } | |
| func normalize(x, y float64) (float64, float64) { | |
| mag := math.Sqrt(x*x + y*y) | |
| if mag == 0 { | |
| return 0, 0 | |
| } | |
| return x / mag, y / mag | |
| } | |
| func limit(x, y, maxMag float64) (float64, float64) { | |
| mag := math.Sqrt(x*x + y*y) | |
| if mag > maxMag { | |
| return (x / mag) * maxMag, (y / mag) * maxMag | |
| } | |
| return x, y | |
| } | |
| func (b *Boid) seek(targetX, targetY float64) (float64, float64) { | |
| desiredX := targetX - b.X | |
| desiredY := targetY - b.Y | |
| desiredX, desiredY = normalize(desiredX, desiredY) | |
| desiredX *= maxSpeed | |
| desiredY *= maxSpeed | |
| steerX := desiredX - b.VelX | |
| steerY := desiredY - b.VelY | |
| return limit(steerX, steerY, maxForce) | |
| } | |
| func (b *Boid) orbit(targetX, targetY float64, userMovementSpeed float64) (float64, float64) { | |
| toTargetX := b.X - targetX | |
| toTargetY := b.Y - targetY | |
| distance := math.Sqrt(toTargetX*toTargetX + toTargetY*toTargetY) | |
| dynamicRadius := orbitRadius * (0.5 + userMovementSpeed*0.15) | |
| if dynamicRadius > 100 { | |
| dynamicRadius = 100 | |
| } | |
| var steerX, steerY float64 | |
| if distance < dynamicRadius*0.6 { | |
| if distance > 0 { | |
| awayX, awayY := normalize(toTargetX, toTargetY) | |
| steerX, steerY = b.seek(b.X+awayX*dynamicRadius*1.5, b.Y+awayY*dynamicRadius*1.5) | |
| } | |
| } else if distance > dynamicRadius*1.8 { | |
| steerX, steerY = b.seek(targetX, targetY) | |
| } else { | |
| perpX := -toTargetY | |
| perpY := toTargetX | |
| perpX, perpY = normalize(perpX, perpY) | |
| tangentialSpeed := maxSpeed * 0.9 | |
| desiredX := perpX * tangentialSpeed | |
| desiredY := perpY * tangentialSpeed | |
| timeFloat := float64(time.Now().UnixNano()) / 1000000000.0 | |
| randomFactorX := 1.2 | |
| randomFactorY := 0.8 | |
| desiredX += (math.Sin(timeFloat*0.7) * randomFactorX) | |
| desiredY += (math.Cos(timeFloat*1.3) * randomFactorY) | |
| desiredX += (math.Sin(timeFloat*2.1) * 0.5) | |
| desiredY += (math.Cos(timeFloat*2.7) * 0.5) | |
| steerX = desiredX - b.VelX | |
| steerY = desiredY - b.VelY | |
| steerX, steerY = limit(steerX, steerY, maxForce) | |
| } | |
| return steerX, steerY | |
| } | |
| func (b *Boid) update(steerX, steerY float64) { | |
| b.VelX += steerX | |
| b.VelY += steerY | |
| b.VelX, b.VelY = limit(b.VelX, b.VelY, maxSpeed) | |
| b.X += b.VelX | |
| b.Y += b.VelY | |
| b.X, b.Y = constrainToBounds(b.X, b.Y) | |
| } | |
| func detectShake(recentMovements []MovementEvent, currentTime time.Time) bool { | |
| cutoffTime := currentTime.Add(-500 * time.Millisecond) | |
| hasRapidLeft := false | |
| hasRapidRight := false | |
| for _, movement := range recentMovements { | |
| if movement.time.Before(cutoffTime) { | |
| continue | |
| } | |
| if movement.deltaX > 60 { | |
| hasRapidRight = true | |
| } else if movement.deltaX < -60 { | |
| hasRapidLeft = true | |
| } | |
| if hasRapidLeft && hasRapidRight { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| func main() { | |
| delay := 16 * time.Millisecond | |
| movementThreshold := 3.0 | |
| screenBounds = getVirtualScreenBounds() | |
| globalStartTime = time.Now() | |
| initialPos, err := GetCursorPos() | |
| if err != nil { | |
| fmt.Printf("Error getting initial cursor position: %v\n", err) | |
| return | |
| } | |
| cursorBoid = Boid{ | |
| X: float64(initialPos.X), | |
| Y: float64(initialPos.Y), | |
| VelX: 0, | |
| VelY: 0, | |
| } | |
| virtualCenterX := float64(initialPos.X) | |
| virtualCenterY := float64(initialPos.Y) | |
| virtualCenterX, virtualCenterY = constrainToBounds(virtualCenterX, virtualCenterY) | |
| recentMovements := make([]float64, 8) | |
| movementIndex := 0 | |
| recentHorizontalMovements := make([]MovementEvent, 20) | |
| movementEventIndex := 0 | |
| pauseUntil := time.Time{} | |
| for { | |
| currentTime := time.Now() | |
| if currentTime.Before(pauseUntil) { | |
| time.Sleep(delay) | |
| continue | |
| } | |
| if currentTime.Equal(pauseUntil) || (pauseUntil != time.Time{} && !currentTime.Before(pauseUntil) && pauseUntil.Before(currentTime.Add(-delay))) { | |
| currentPos, err := GetCursorPos() | |
| if err == nil { | |
| virtualCenterX = float64(currentPos.X) | |
| virtualCenterY = float64(currentPos.Y) | |
| virtualCenterX, virtualCenterY = constrainToBounds(virtualCenterX, virtualCenterY) | |
| cursorBoid.X = virtualCenterX | |
| cursorBoid.Y = virtualCenterY | |
| cursorBoid.VelX = 0 | |
| cursorBoid.VelY = 0 | |
| } | |
| pauseUntil = time.Time{} | |
| } | |
| actualPos, err := GetCursorPos() | |
| if err != nil { | |
| fmt.Printf("Error getting cursor position: %v\n", err) | |
| time.Sleep(delay) | |
| continue | |
| } | |
| actualX := float64(actualPos.X) | |
| actualY := float64(actualPos.Y) | |
| expectedX := cursorBoid.X | |
| expectedY := cursorBoid.Y | |
| deltaX := actualX - expectedX | |
| deltaY := actualY - expectedY | |
| movementMagnitude := math.Sqrt(deltaX*deltaX + deltaY*deltaY) | |
| if movementMagnitude > movementThreshold { | |
| virtualCenterX += deltaX | |
| virtualCenterY += deltaY | |
| virtualCenterX, virtualCenterY = constrainToBounds(virtualCenterX, virtualCenterY) | |
| recentMovements[movementIndex] = math.Sqrt(deltaX*deltaX + deltaY*deltaY) | |
| movementIndex = (movementIndex + 1) % len(recentMovements) | |
| recentHorizontalMovements[movementEventIndex] = MovementEvent{ | |
| deltaX: deltaX, | |
| time: currentTime, | |
| } | |
| movementEventIndex = (movementEventIndex + 1) % len(recentHorizontalMovements) | |
| if detectShake(recentHorizontalMovements, currentTime) { | |
| pauseUntil = currentTime.Add(10 * time.Second) | |
| for i := range recentHorizontalMovements { | |
| recentHorizontalMovements[i] = MovementEvent{} | |
| } | |
| continue | |
| } | |
| } | |
| var avgMovementSpeed float64 = 0 | |
| for _, movement := range recentMovements { | |
| avgMovementSpeed += movement | |
| } | |
| avgMovementSpeed /= float64(len(recentMovements)) | |
| maxSpeed = 3.0 + avgMovementSpeed*0.4 | |
| if maxSpeed > 8.0 { | |
| maxSpeed = 8.0 | |
| } | |
| maxForce = 0.15 + avgMovementSpeed*0.03 | |
| if maxForce > 0.4 { | |
| maxForce = 0.4 | |
| } | |
| steerX, steerY := cursorBoid.orbit(virtualCenterX, virtualCenterY, avgMovementSpeed) | |
| cursorBoid.update(steerX, steerY) | |
| finalX, finalY := constrainToBounds(cursorBoid.X, cursorBoid.Y) | |
| if !SetCursorPos(int(finalX), int(finalY)) { | |
| fmt.Println("Error setting cursor position. Try running as administrator.") | |
| } | |
| time.Sleep(delay) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment