//go:build windows package win32 import ( "fmt" "syscall" "time" "unicode/utf16" "unsafe" "golang.org/x/sys/windows" ) // SetClipboardText copies s onto the Windows clipboard as CF_UNICODETEXT. // OpenClipboard may transiently fail if another process holds the // clipboard; we retry a few times before giving up. func SetClipboardText(s string) error { const cfUnicodeText = 13 utf := utf16.Encode([]rune(s + "\x00")) size := len(utf) * 2 hMem, _, e := procGlobalAlloc.Call(0x0042 /*GMEM_MOVEABLE|GMEM_ZEROINIT*/, uintptr(size)) if hMem == 0 { return fmt.Errorf("win32: GlobalAlloc: %w", e) } dst, _, _ := procGlobalLock.Call(hMem) if dst == 0 { procGlobalFree.Call(hMem) return fmt.Errorf("win32: GlobalLock failed") } dstSlice := unsafe.Slice((*uint16)(unsafe.Pointer(dst)), len(utf)) copy(dstSlice, utf) procGlobalUnlock.Call(hMem) if err := openClipboardRetry(0); err != nil { procGlobalFree.Call(hMem) return err } procEmptyClipboard.Call() r, _, ce := procSetClipboardData.Call(cfUnicodeText, hMem) if r == 0 { procCloseClipboard.Call() procGlobalFree.Call(hMem) return fmt.Errorf("win32: SetClipboardData: %w", ce) } // Ownership of hMem transfers to the system on success — do not free. procCloseClipboard.Call() return nil } func openClipboardRetry(hwnd uintptr) error { var last error for i := 0; i < 8; i++ { r, _, e := procOpenClipboard.Call(hwnd) if r != 0 { return nil } last = e time.Sleep(20 * time.Millisecond) } return fmt.Errorf("win32: OpenClipboard: %w", last) } // GetForegroundWindow returns the HWND that currently has keyboard // focus. Use this to remember the target window before the user clicks // into winauth-go (which itself becomes foreground and would otherwise // receive injected keystrokes). func GetForegroundWindow() uintptr { r, _, _ := procGetForegroundWindow.Call() return r } // FocusWindow restores hwnd to the foreground. Hits the well-known // SetForegroundWindow restriction (only the foreground process may // hand focus to another); we work around it by attaching to the target // thread's input queue briefly, the trick documented in MSDN's // "AttachThreadInput" page. func FocusWindow(hwnd uintptr) error { if hwnd == 0 { return fmt.Errorf("win32: FocusWindow: nil hwnd") } curTID, _, _ := procGetCurrentThreadId.Call() targetTID, _, _ := procGetWindowThreadProcessId.Call(hwnd, 0) if targetTID == 0 { return fmt.Errorf("win32: GetWindowThreadProcessId failed") } if curTID != targetTID { procAttachThreadInput.Call(curTID, targetTID, 1) defer procAttachThreadInput.Call(curTID, targetTID, 0) } procSetForegroundWindowProc.Call(hwnd) procShowWindowProc.Call(hwnd, 9 /*SW_RESTORE*/) procBringWindowToTop.Call(hwnd) return nil } // TypeUnicode injects s as Unicode characters using SendInput KEYEVENTF_UNICODE. // Special characters in s pass through transparently; no translation // of newlines / tabs happens. If you need an Enter at the end, pass // "\n" and let the caller append it explicitly. func TypeUnicode(s string) error { if s == "" { return nil } utf := utf16.Encode([]rune(s)) // Each rune becomes 2 inputs (keydown + keyup). inputs := make([]inputUnion, 0, len(utf)*2) for _, u := range utf { inputs = append(inputs, makeUnicodeInput(u, false), makeUnicodeInput(u, true), ) } r, _, e := procSendInput.Call( uintptr(len(inputs)), uintptr(unsafe.Pointer(&inputs[0])), unsafe.Sizeof(inputs[0]), ) if int(r) != len(inputs) { if errno, ok := e.(syscall.Errno); ok && errno != 0 { return fmt.Errorf("win32: SendInput: sent %d/%d: %w", r, len(inputs), errno) } return fmt.Errorf("win32: SendInput: sent %d/%d", r, len(inputs)) } return nil } // PressKey injects a single virtual-key down+up pair (e.g. VK_RETURN). func PressKey(vk uint16) error { inputs := [2]inputUnion{ makeVKInput(vk, false), makeVKInput(vk, true), } r, _, e := procSendInput.Call( uintptr(len(inputs)), uintptr(unsafe.Pointer(&inputs[0])), unsafe.Sizeof(inputs[0]), ) if int(r) != len(inputs) { if errno, ok := e.(syscall.Errno); ok && errno != 0 { return fmt.Errorf("win32: SendInput (vk): %w", errno) } return fmt.Errorf("win32: SendInput (vk): short send") } return nil } // VK_RETURN is exposed for callers that want to press Enter after Auto-type. const VK_RETURN uint16 = 0x0D // ----------------------------------------------------------------------------- // raw syscalls + structs var ( procOpenClipboard = user32.NewProc("OpenClipboard") procCloseClipboard = user32.NewProc("CloseClipboard") procEmptyClipboard = user32.NewProc("EmptyClipboard") procSetClipboardData = user32.NewProc("SetClipboardData") procGetClipboardData = user32.NewProc("GetClipboardData") procGetForegroundWindow = user32.NewProc("GetForegroundWindow") procSetForegroundWindowProc = user32.NewProc("SetForegroundWindow") procShowWindowProc = user32.NewProc("ShowWindow") procBringWindowToTop = user32.NewProc("BringWindowToTop") procGetWindowThreadProcessId = user32.NewProc("GetWindowThreadProcessId") procAttachThreadInput = user32.NewProc("AttachThreadInput") procSendInput = user32.NewProc("SendInput") kernel32 = windows.NewLazySystemDLL("kernel32.dll") procGlobalAlloc = kernel32.NewProc("GlobalAlloc") procGlobalFree = kernel32.NewProc("GlobalFree") procGlobalLock = kernel32.NewProc("GlobalLock") procGlobalUnlock = kernel32.NewProc("GlobalUnlock") procGlobalSize = kernel32.NewProc("GlobalSize") procGetCurrentThreadId = kernel32.NewProc("GetCurrentThreadId") ) // inputUnion is the Win32 INPUT structure, sized for KEYBDINPUT (the // largest variant on x64 is MOUSEINPUT but KEYBDINPUT padded to 40 // works because INPUT_KEYBOARD never reads the extra trailing bytes). type inputUnion struct { typ uint32 _pad uint32 // alignment on 64-bit wVk uint16 wScan uint16 dwFlags uint32 time uint32 dwExtra uintptr // 8 bytes of MOUSEINPUT-sized padding so the layout is large enough // for the union on amd64. _padTail [8]byte } const ( inputKeyboard uint32 = 1 keyeventfKeyUp uint32 = 0x0002 keyeventfUnicode uint32 = 0x0004 ) func makeUnicodeInput(r uint16, up bool) inputUnion { flags := keyeventfUnicode if up { flags |= keyeventfKeyUp } return inputUnion{typ: inputKeyboard, wScan: r, dwFlags: flags} } func makeVKInput(vk uint16, up bool) inputUnion { var flags uint32 if up { flags = keyeventfKeyUp } return inputUnion{typ: inputKeyboard, wVk: vk, dwFlags: flags} }