diff --git a/lib/config/config.go b/lib/config/config.go
index 5f4580f..352e89f 100644
--- a/lib/config/config.go
+++ b/lib/config/config.go
@@ -16,6 +16,7 @@ const (
MaxExecTO = 120
StartupValueName = "LocalManagementAgent"
StartupRunKey = `Software\Microsoft\Windows\CurrentVersion\Run`
+ MaxInputText = 4096
)
func EnvOr(name, fallback string) string {
diff --git a/lib/input/input.go b/lib/input/input.go
new file mode 100644
index 0000000..0eed8fe
--- /dev/null
+++ b/lib/input/input.go
@@ -0,0 +1,116 @@
+package input
+
+import (
+ "errors"
+ "strings"
+ "syscall"
+ "unicode/utf16"
+ "unsafe"
+)
+
+const (
+ inputMouse = 0
+ inputKeyboard = 1
+
+ mouseLeftDown = 0x0002
+ mouseLeftUp = 0x0004
+ mouseRightDown = 0x0008
+ mouseRightUp = 0x0010
+
+ keyeventfKeyup = 0x0002
+ keyeventfUnicode = 0x0004
+)
+
+var (
+ ErrBadButton = errors.New("button must be left or right")
+ ErrEmptyText = errors.New("text must not be empty")
+
+ user32 = syscall.NewLazyDLL("user32.dll")
+ procSetCursorPos = user32.NewProc("SetCursorPos")
+ procSendInput = user32.NewProc("SendInput")
+ procSetProcessDPIAware = user32.NewProc("SetProcessDPIAware")
+)
+
+type mouseInput struct {
+ Type uint32
+ _ uint32
+ Dx int32
+ Dy int32
+ MouseData uint32
+ Flags uint32
+ Time uint32
+ ExtraInfo uintptr
+}
+
+type keybdInput struct {
+ Type uint32
+ _ uint32
+ Vk uint16
+ Scan uint16
+ Flags uint32
+ Time uint32
+ ExtraInfo uintptr
+ _ [8]byte
+}
+
+func EnableDPIAwareness() {
+ _, _, _ = procSetProcessDPIAware.Call()
+}
+
+func Click(x, y int, button string) error {
+ down, up, err := mouseFlags(button)
+ if err != nil {
+ return err
+ }
+ ok, _, callErr := procSetCursorPos.Call(uintptr(x), uintptr(y))
+ if ok == 0 {
+ return callErr
+ }
+ inputs := []mouseInput{
+ {Type: inputMouse, Flags: down},
+ {Type: inputMouse, Flags: up},
+ }
+ return sendMouse(inputs)
+}
+
+func TypeText(text string) error {
+ if text == "" {
+ return ErrEmptyText
+ }
+ units := utf16.Encode([]rune(text))
+ inputs := make([]keybdInput, 0, len(units)*2)
+ for _, unit := range units {
+ inputs = append(inputs,
+ keybdInput{Type: inputKeyboard, Scan: unit, Flags: keyeventfUnicode},
+ keybdInput{Type: inputKeyboard, Scan: unit, Flags: keyeventfUnicode | keyeventfKeyup},
+ )
+ }
+ return sendKeys(inputs)
+}
+
+func mouseFlags(button string) (down, up uint32, err error) {
+ switch strings.ToLower(strings.TrimSpace(button)) {
+ case "", "left":
+ return mouseLeftDown, mouseLeftUp, nil
+ case "right":
+ return mouseRightDown, mouseRightUp, nil
+ default:
+ return 0, 0, ErrBadButton
+ }
+}
+
+func sendMouse(inputs []mouseInput) error {
+ n, _, err := procSendInput.Call(uintptr(len(inputs)), uintptr(unsafe.Pointer(&inputs[0])), uintptr(unsafe.Sizeof(inputs[0])))
+ if n == 0 {
+ return err
+ }
+ return nil
+}
+
+func sendKeys(inputs []keybdInput) error {
+ n, _, err := procSendInput.Call(uintptr(len(inputs)), uintptr(unsafe.Pointer(&inputs[0])), uintptr(unsafe.Sizeof(inputs[0])))
+ if n == 0 {
+ return err
+ }
+ return nil
+}
diff --git a/lib/models/models.go b/lib/models/models.go
index 017bb22..6d2cb8d 100644
--- a/lib/models/models.go
+++ b/lib/models/models.go
@@ -38,4 +38,19 @@ type ExecResponse struct {
type CapturedImage struct {
ContentType string
Data []byte
+ Left int
+ Top int
+ Width int
+ Height int
+}
+
+type ClickRequest struct {
+ X int `json:"x"`
+ Y int `json:"y"`
+ Button string `json:"button"`
+ Monitor int `json:"monitor"`
+}
+
+type TextRequest struct {
+ Text string `json:"text"`
}
diff --git a/lib/screenshot/screenshot.go b/lib/screenshot/screenshot.go
index f8dea0b..1827752 100644
--- a/lib/screenshot/screenshot.go
+++ b/lib/screenshot/screenshot.go
@@ -13,6 +13,8 @@ import (
"tea.chunkbyte.com/kato/go-worm/lib/models"
)
+var ErrMonitorNotFound = errors.New("monitor not found")
+
var (
user32 = syscall.NewLazyDLL("user32.dll")
gdi32 = syscall.NewLazyDLL("gdi32.dll")
@@ -68,19 +70,80 @@ func Capture(format string, quality int) ([]models.CapturedImage, error) {
}
result := make([]models.CapturedImage, 0, len(monitors))
for _, monitor := range monitors {
- img, err := captureRect(monitor)
+ frame, err := captureAndEncode(monitor, format, quality)
if err != nil {
return nil, err
}
- data, contentType, err := encodeImage(img, format, quality)
- if err != nil {
- return nil, err
- }
- result = append(result, models.CapturedImage{ContentType: contentType, Data: data})
+ result = append(result, frame)
}
return result, nil
}
+func CaptureMonitor(index int, format string, quality int) (models.CapturedImage, error) {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+ if err := attachInputDesktop(); err != nil {
+ return models.CapturedImage{}, err
+ }
+ monitors, err := enumerateMonitors()
+ if err != nil {
+ return models.CapturedImage{}, err
+ }
+ if index < 0 || index >= len(monitors) {
+ return models.CapturedImage{}, ErrMonitorNotFound
+ }
+ return captureAndEncode(monitors[index], format, quality)
+}
+
+func MonitorCount() (int, error) {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+ if err := attachInputDesktop(); err != nil {
+ return 0, err
+ }
+ monitors, err := enumerateMonitors()
+ if err != nil {
+ return 0, err
+ }
+ return len(monitors), nil
+}
+
+func MonitorBounds(index int) (left, top, width, height int, err error) {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+ if err := attachInputDesktop(); err != nil {
+ return 0, 0, 0, 0, err
+ }
+ monitors, err := enumerateMonitors()
+ if err != nil {
+ return 0, 0, 0, 0, err
+ }
+ if index < 0 || index >= len(monitors) {
+ return 0, 0, 0, 0, ErrMonitorNotFound
+ }
+ r := monitors[index]
+ return int(r.Left), int(r.Top), int(r.Right - r.Left), int(r.Bottom - r.Top), nil
+}
+
+func captureAndEncode(monitor rect, format string, quality int) (models.CapturedImage, error) {
+ img, err := captureRect(monitor)
+ if err != nil {
+ return models.CapturedImage{}, err
+ }
+ data, contentType, err := encodeImage(img, format, quality)
+ if err != nil {
+ return models.CapturedImage{}, err
+ }
+ return models.CapturedImage{
+ ContentType: contentType,
+ Data: data,
+ Left: int(monitor.Left),
+ Top: int(monitor.Top),
+ Width: int(monitor.Right - monitor.Left),
+ Height: int(monitor.Bottom - monitor.Top),
+ }, nil
+}
+
func attachInputDesktop() error {
h, _, err := procOpenInputDesktop.Call(0, 0, 0x0001|0x0040)
if h == 0 {