English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Русский | Português
Golang 桌面自动化、自动测试以及 AI 计算机操作(Computer Use)。
控制鼠标、键盘,读取屏幕,进程、窗口句柄、图像与位图,以及全局事件监听。
RobotGo 支持 Mac、Windows 和 Linux;并且支持 arm64 与 x86-amd64 架构。
我正在打造 Codg,一个简单易用的 AI 智能体(Agent)工作系统:自动化、异步、并发、高效且高准确度。
RobotGo-Pro 提供 JavaScript、Python、Lua 等其他语言版本、技术支持、新功能以及最新的 robotgo 版本(“目前无开源版本”)。
捐赠者、赞助商或其他事宜,请联系:vzvway@gmail.com
ADB,封装了 Android adb 和 iOS WDA,提供与 robotgo 兼容的 API。
Go 1.26+(见 go.mod)以及一个后端:
- 默认(Cgo):
CGO_ENABLED=1、一个 C 编译器,以及下列平台库。 - 纯 Go(已测试): 无需 C 工具链;见无 Cgo 构建。
两者都需要为你所用功能安装对应的功能相关依赖。
Go 和 Xcode 命令行工具(Clang):
brew install go
xcode-select --install权限(Cgo 与纯 Go): 在 系统设置 > 隐私与安全性 中,为应用或终端授予 辅助功能(输入)和 屏幕录制(捕获)权限。
winget install GoLang.Go此外还需 一个 C 工具链。LLVM-MinGW:
winget install MartinStorsjo.LLVM-MinGW.UCRT
$env:CC = "clang"winget install BrechtSanders.WinLibs.POSIX.UCRT
$env:CC = "gcc"工具链的 bin 目录(例如 C:\mingw64\bin)必须位于 PATH 中且与 GOARCH
匹配。其他任何兼容 Cgo 的编译器同样可用,例如手动安装的
MinGW-w64。
Bitmap 仅为 MinGW-w64 附带 libpng; 使用其他工具链时,需自行构建 libpng。
构建:GCC、libc 头文件、X11/XTest 头文件。运行:带 XTEST 的 X 服务器并设置
DISPLAY。原生 Wayland 请见无 Cgo 构建。
Ubuntu / Debian:
# Go(发行版自带的包过旧时用 Snap)
sudo snap install go --classic
# 或:sudo apt install golang
# 核心(Cgo):编译器、libc、X11、XTest
sudo apt install gcc libc6-dev libx11-dev xorg-dev libxtst-dev
# 剪贴板(X11)
sudo apt install xsel xclip
# Bitmap:libpng
sudo apt install libpng++-dev
# GoHook:XCB、XKB
sudo apt install xcb libxcb-xkb-dev x11-xkb-utils libx11-xcb-dev libxkbcommon-x11-dev libxkbcommon-devFedora:
# 核心(Cgo):编译器、libc、X11、XTest
sudo dnf install gcc glibc-devel libX11-devel libXtst-devel
# 剪贴板(X11)
sudo dnf install xsel xclip
# Bitmap:libpng
sudo dnf install libpng-devel
# GoHook:XKB(Fedora < 34:用 xorg-x11-xkb-utils-devel 代替 xkbcomp-devel)
sudo dnf install libxkbcommon-devel libxkbcommon-x11-devel xkbcomp-devel在核心环境之上,仅在使用相应功能时才需要:
-
Linux 剪贴板(Cgo 与纯 Go): X11 上需
xclip或xsel; Wayland 上需wl-clipboard:sudo apt install wl-clipboard # Ubuntu / Debian sudo dnf install wl-clipboard # Fedora
-
Bitmap: libpng 头文件。
-
GoHook: Linux 上的 XCB/XKB 头文件。
Bitmap 和 GoHook 是独立的 Cgo 包;纯 Go 的 RobotGo 后端并不会消除它们对 C 的 依赖。
经过测试的纯 Go 后端可在 CGO_ENABLED=0 下构建与交叉编译(无需 GCC、MinGW、
Xcode 或 X11 头文件)。导入路径和 API 不变;后端由构建标签选择 ——
仅设置 CGO_ENABLED=0 不会选择任何后端。
目标 GOOS |
构建标签 | 包 | 运行时要求 |
|---|---|---|---|
windows |
win |
win | 仅需 Win32,无额外运行时 |
darwin |
mac |
darwin | 系统框架与隐私权限 |
linux |
x11 |
x11 | 带 XTEST 的 X 服务器,已设置 DISPLAY |
linux |
wayland |
wayland | wlroots 合成器,见 Wayland |
linux |
libei |
libei | RemoteDesktop portal(GNOME/KDE),见 libei |
purego 是一个快捷方式:macOS 上为 mac,Windows 上为 win,Linux 上为
wayland。在 Linux 上可追加 x11 或 libei 来覆盖(-tags "purego,x11")。
只使用一个与 GOOS 匹配的后端标签;诸如 x11,libei 的组合无法编译
(purego 加一个 Linux 覆盖标签是唯一例外)。
# 当前操作系统
CGO_ENABLED=0 go build -tags purego .
# 交叉编译
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -tags win .
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -tags mac .
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags x11 .
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags wayland .
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags libei .PowerShell:$env:CGO_ENABLED = "0"; go build -tags purego .
请构建模块根目录(.)或你自己的包,而不是 ./...:examples/ 和部分子包需要
仅 Cgo 可用的 API。
上述运行时要求依然适用。仅 Cgo 可用的 API,如 CaptureScreen / FreeBitmap,
并不存在;请使用 CaptureImg。不支持的操作返回 ErrNotSupported(或空结果)。
mac: 无窗口管理 ——ActiveName返回ErrNotSupported,GetTitle返回"",最小化/最大化/关闭为空操作。wayland、libei: Wayland 没有获取全局光标位置的协议。Location()在 Hyprland(IPC socket)和 KDE Plasma(通过 D-Bus 的 KWin 脚本)上查询成功时读取 真实光标;wayland还要求所有输出的缩放比例一致(缩放不同或旋转的输出会回退)。 否则以及其他环境(GNOME、Sway 及其他 wlroots 合成器)返回 RobotGo 最后注入的 位置,而非物理光标位置。剪贴板需要wl-clipboard。
需要一个 wlroots 合成器(Sway、Hyprland、Wayfire 等),已设置 WAYLAND_DISPLAY
和 XDG_RUNTIME_DIR,并暴露:
| 功能 | 协议全局对象 |
|---|---|
| 鼠标控制 | zwlr_virtual_pointer_manager_v1 |
| 键盘控制 | zwp_virtual_keyboard_manager_v1 |
| 屏幕捕获 | zwlr_screencopy_manager_v1 |
| 窗口管理 | zwlr_foreign_toplevel_manager_v1 |
GNOME 和 KDE 不提供这些协议;请在其上使用 libei。
输入通过 xdg-desktop-portal 的 RemoteDesktop D-Bus 接口完成:需要会话总线、
xdg-desktop-portal 以及一个实现该接口的后端(xdg-desktop-portal-gnome、
-kde 或 -wlr)。首次运行会显示授权对话框;恢复令牌缓存在
$XDG_STATE_HOME/robotgo 下。
绝对移动使用关联的 ScreenCast 流(默认);设置 libei.LinkScreenCast = false
则仅使用相对移动。屏幕捕获和窗口管理返回 ErrNotSupported。
go get github.com/go-vgo/robotgoimport "github.com/go-vgo/robotgo"使用 Bitmap 时出现 png.h: No such file or directory:请查看其 libpng 要求以及
issues/47。
go get -u github.com/go-vgo/robotgo注意 go1.10.x 的 C 文件编译缓存问题,golang #24355。
go mod vendor 问题,golang #26366。
package main
import (
"errors"
"fmt"
"github.com/go-vgo/robotgo"
)
func main() {
robotgo.MouseSleep = 300
robotgo.Move(100, 100)
fmt.Println(robotgo.Location())
robotgo.Move(100, -200) // multi screen supported
robotgo.MoveSmooth(120, -150)
fmt.Println(robotgo.Location())
robotgo.ScrollDir(10, "up")
robotgo.ScrollDir(20, "right")
robotgo.Scroll(0, -10)
robotgo.Scroll(100, 0)
robotgo.MilliSleep(100)
robotgo.ScrollSmooth(-10, 6)
// robotgo.ScrollRelative(10, -100)
robotgo.Move(10, 20)
robotgo.MoveRelative(0, -10)
robotgo.DragSmooth(10, 10)
robotgo.Click("wheelRight")
robotgo.Click("left", true)
robotgo.MoveSmooth(100, 200, 1.0, 10.0)
robotgo.Toggle("left")
robotgo.Toggle("left", "up")
// 检查返回的错误, 无效参数会返回错误
if err := robotgo.ScrollDir(10, "forward"); err != nil {
fmt.Println("robotgo.ScrollDir error:", err)
}
// 不同后端行为不同: Cgo 返回错误, 纯 Go 执行一次左键单击并返回 nil
if err := robotgo.Click("left", "double"); err != nil {
fmt.Println("robotgo.Click error:", err)
}
// MoveSmoothRelative 将平滑移动失败报告为 ErrSmoothMove
err := robotgo.MoveSmoothRelative(10, -10)
if errors.Is(err, robotgo.ErrSmoothMove) {
fmt.Println("smooth move failed:", err)
}
// 纯 Go 后端对不支持的操作返回 ErrNotSupported
err = robotgo.Move(100, 200)
if errors.Is(err, robotgo.ErrNotSupported) {
fmt.Println("robotgo.Move is not supported by this backend")
}
}package main
import (
"errors"
"fmt"
"github.com/go-vgo/robotgo"
)
func main() {
robotgo.Type("Hello World")
robotgo.Type("だんしゃり", 0, 1)
// robotgo.Type("テストする")
robotgo.Type("Hi, Seattle space needle, Golden gate bridge, One world trade center.")
robotgo.Type("Hi galaxy, hi stars, hi MT.Rainier, hi sea. こんにちは世界.")
robotgo.Sleep(1)
// ustr := uint32(robotgo.CharCodeAt("Test", 0))
// robotgo.UnicodeType(ustr)
robotgo.KeySleep = 100
robotgo.KeyTap("enter")
// robotgo.Type("en")
robotgo.KeyTap("i", "alt", "cmd")
arr := []string{"alt", "cmd"}
robotgo.KeyTap("i", arr)
robotgo.MilliSleep(100)
robotgo.KeyToggle("a")
robotgo.KeyToggle("a", "up")
robotgo.WriteAll("Test")
text, err := robotgo.ReadAll()
if err == nil {
fmt.Println(text)
}
// 未知的按键名会返回错误, 而不是按下错误的按键
if err := robotgo.KeyTap("notakey"); err != nil {
fmt.Println("robotgo.KeyTap error:", err)
}
// TypeStr 返回第一个输入错误, Type 只返回已输入的字符数
if err := robotgo.TypeStr("Hello, 世界"); err != nil {
fmt.Println("robotgo.TypeStr error:", err)
}
// 按住修饰键, 然后始终释放它并检查两个错误
if err := robotgo.KeyDown("shift"); err == nil {
err = errors.Join(robotgo.KeyTap("a"), robotgo.KeyUp("shift"))
if err != nil {
fmt.Println("shift + a error:", err)
}
}
}package main
import (
"fmt"
"strconv"
"github.com/go-vgo/robotgo"
"github.com/vcaesar/imgo"
)
func main() {
x, y := robotgo.Location()
fmt.Println("pos: ", x, y)
color := robotgo.GetPixelColor(100, 200)
fmt.Println("color---- ", color)
sx, sy := robotgo.GetScreenSize()
fmt.Println("get screen size: ", sx, sy)
bit := robotgo.CaptureScreen(10, 10, 30, 30)
defer robotgo.FreeBitmap(bit)
img := robotgo.ToImage(bit)
imgo.Save("test.png", img)
num := robotgo.DisplaysNum()
for i := 0; i < num; i++ {
robotgo.DisplayID = i
img1, _ := robotgo.CaptureImg()
path1 := "save_" + strconv.Itoa(i)
robotgo.Save(img1, path1+".png")
robotgo.SaveJpeg(img1, path1+".jpeg", 50)
img2, _ := robotgo.CaptureImg(10, 10, 20, 20)
robotgo.Save(img2, "test_"+strconv.Itoa(i)+".png")
x, y, w, h := robotgo.GetDisplayBounds(i)
img3, err := robotgo.CaptureImg(x, y, w, h)
fmt.Println("Capture error: ", err)
robotgo.Save(img3, path1+"_1.png")
}
}package main
import (
"fmt"
"github.com/go-vgo/robotgo"
"github.com/vcaesar/bitmap"
)
func main() {
bit := robotgo.CaptureScreen(10, 20, 30, 40)
// use `defer robotgo.FreeBitmap(bit)` to free the bitmap
defer robotgo.FreeBitmap(bit)
fmt.Println("bitmap...", bit)
img := robotgo.ToImage(bit)
// robotgo.SavePng(img, "test_1.png")
robotgo.Save(img, "test_1.png")
bit2 := robotgo.ToCBitmap(robotgo.ImgToBitmap(img))
fx, fy := bitmap.Find(bit2)
fmt.Println("FindBitmap------ ", fx, fy)
robotgo.Move(fx, fy)
arr := bitmap.FindAll(bit2)
fmt.Println("Find all bitmap: ", arr)
fx, fy = bitmap.Find(bit)
fmt.Println("FindBitmap------ ", fx, fy)
bitmap.Save(bit, "test.png")
}package main
import (
"fmt"
"math/rand"
"github.com/go-vgo/robotgo"
"github.com/vcaesar/gcv"
"github.com/vcaesar/bitmap"
)
func main() {
opencv()
}
func opencv() {
name := "test.png"
name1 := "test_001.png"
robotgo.SaveCapture(name1, 10, 10, 30, 30)
robotgo.SaveCapture(name)
fmt.Print("gcv find image: ")
fmt.Println(gcv.FindImgFile(name1, name))
fmt.Println(gcv.FindAllImgFile(name1, name))
bit := bitmap.Open(name1)
defer robotgo.FreeBitmap(bit)
fmt.Print("find bitmap: ")
fmt.Println(bitmap.Find(bit))
// bit0 := robotgo.CaptureScreen()
// img := robotgo.ToImage(bit0)
// bit1 := robotgo.CaptureScreen(10, 10, 30, 30)
// img1 := robotgo.ToImage(bit1)
// defer robotgo.FreeBitmapArr(bit0, bit1)
img, _ := robotgo.CaptureImg()
img1, _ := robotgo.CaptureImg(10, 10, 30, 30)
fmt.Print("gcv find image: ")
fmt.Println(gcv.FindImg(img1, img))
fmt.Println()
res := gcv.FindAllImg(img1, img)
fmt.Println(res[0].TopLeft.Y, res[0].Rects.TopLeft.X, res)
x, y := res[0].TopLeft.X, res[0].TopLeft.Y
robotgo.Move(x, y-rand.Intn(5))
robotgo.MilliSleep(100)
robotgo.Click()
res = gcv.FindAll(img1, img) // use find template and sift
fmt.Println("find all: ", res)
res1 := gcv.Find(img1, img)
fmt.Println("find: ", res1)
img2, _, _ := robotgo.DecodeImg("test_001.png")
x, y = gcv.FindX(img2, img)
fmt.Println(x, y)
}package main
import (
"fmt"
// "github.com/go-vgo/robotgo"
hook "github.com/robotn/gohook"
)
func main() {
add()
low()
event()
}
func add() {
fmt.Println("--- Please press ctrl + shift + q to stop hook ---")
hook.Register(hook.KeyDown, []string{"q", "ctrl", "shift"}, func(e hook.Event) {
fmt.Println("ctrl-shift-q")
hook.End()
})
fmt.Println("--- Please press w---")
hook.Register(hook.KeyDown, []string{"w"}, func(e hook.Event) {
fmt.Println("w")
})
s := hook.Start()
<-hook.Process(s)
}
func low() {
evChan := hook.Start()
defer hook.End()
for ev := range evChan {
fmt.Println("hook: ", ev)
}
}
func event() {
ok := hook.AddEvents("q", "ctrl", "shift")
if ok {
fmt.Println("add events...")
}
keve := hook.AddEvent("k")
if keve {
fmt.Println("you press... ", "k")
}
mleft := hook.AddEvent("mleft")
if mleft {
fmt.Println("you press... ", "mouse left button")
}
}package main
import (
"fmt"
"github.com/go-vgo/robotgo"
)
func main() {
fpid, err := robotgo.FindIds("Google")
if err == nil {
fmt.Println("pids... ", fpid)
if len(fpid) > 0 {
robotgo.Type("Hi galaxy!", fpid[0])
robotgo.KeyTap("a", fpid[0], "cmd")
robotgo.KeyToggle("a", fpid[0])
robotgo.KeyToggle("a", fpid[0], "up")
robotgo.ActivePid(fpid[0])
robotgo.Kill(fpid[0])
}
}
robotgo.ActiveName("chrome")
isExist, err := robotgo.PidExists(100)
if err == nil && isExist {
fmt.Println("pid exists is", isExist)
robotgo.Kill(100)
}
abool := robotgo.Alert("test", "robotgo")
if abool {
fmt.Println("ok@@@ ", "ok")
}
title := robotgo.GetTitle()
fmt.Println("title@@@ ", title)
}- 更好的多屏支持
- 更新窗口句柄
Robotgo 主要依据 “Apache License (Version 2.0)” 的条款进行分发,部分内容受各类 BSD 风格许可证约束。