CI / Lint (push) Failing after 14m46s
CI / Test (push) Failing after 4m23s
CI / Build (darwin/amd64) (push) Skipped
CI / Build (linux/amd64) (push) Skipped
CI / Build (windows/amd64) (push) Skipped
CI / Build (darwin/arm64) (push) Skipped
CI / Build (linux/arm64) (push) Skipped
CI / Build (windows/arm64) (push) Skipped
CI / Release (push) Skipped
107 lines
2.7 KiB
Go
107 lines
2.7 KiB
Go
//go:build windows
|
|
|
|
package clipboard
|
|
|
|
import (
|
|
"fmt"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
const cfUnicodeText = 13
|
|
|
|
var (
|
|
user32 = syscall.NewLazyDLL("user32.dll")
|
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
|
|
procOpenClipboard = user32.NewProc("OpenClipboard")
|
|
procCloseClipboard = user32.NewProc("CloseClipboard")
|
|
procEmptyClipboard = user32.NewProc("EmptyClipboard")
|
|
procGetClipboardData = user32.NewProc("GetClipboardData")
|
|
procSetClipboardData = user32.NewProc("SetClipboardData")
|
|
|
|
procGlobalAlloc = kernel32.NewProc("GlobalAlloc")
|
|
procGlobalLock = kernel32.NewProc("GlobalLock")
|
|
procGlobalUnlock = kernel32.NewProc("GlobalUnlock")
|
|
procGlobalFree = kernel32.NewProc("GlobalFree")
|
|
)
|
|
|
|
type windowsClipboard struct{}
|
|
|
|
// New creates a Windows clipboard using Win32 API (syscall, no CGo).
|
|
func New() (Clipboard, error) {
|
|
return newWindowsClipboard()
|
|
}
|
|
|
|
func newWindowsClipboard() (*windowsClipboard, error) {
|
|
return &windowsClipboard{}, nil
|
|
}
|
|
|
|
func (c *windowsClipboard) Read() (string, error) {
|
|
r1, _, err := procOpenClipboard.Call(0)
|
|
if r1 == 0 {
|
|
return "", fmt.Errorf("clipboard read: OpenClipboard: %w", err)
|
|
}
|
|
defer procCloseClipboard.Call()
|
|
|
|
h, _, err := procGetClipboardData.Call(uintptr(cfUnicodeText))
|
|
if h == 0 {
|
|
// No text in clipboard — not an error, just empty.
|
|
return "", nil
|
|
}
|
|
|
|
p, _, err := procGlobalLock.Call(h)
|
|
if p == 0 {
|
|
return "", fmt.Errorf("clipboard read: GlobalLock: %w", err)
|
|
}
|
|
defer procGlobalUnlock.Call(h)
|
|
|
|
return syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(p))[:]), nil
|
|
}
|
|
|
|
func (c *windowsClipboard) Write(text string) error {
|
|
r1, _, err := procOpenClipboard.Call(0)
|
|
if r1 == 0 {
|
|
return fmt.Errorf("clipboard write: OpenClipboard: %w", err)
|
|
}
|
|
defer procCloseClipboard.Call()
|
|
|
|
r1, _, err = procEmptyClipboard.Call()
|
|
if r1 == 0 {
|
|
return fmt.Errorf("clipboard write: EmptyClipboard: %w", err)
|
|
}
|
|
|
|
utf16, err := syscall.UTF16FromString(text)
|
|
if err != nil {
|
|
return fmt.Errorf("clipboard write: UTF16FromString: %w", err)
|
|
}
|
|
|
|
// Allocate global memory: size in bytes (2 bytes per UTF-16 unit, including null terminator)
|
|
byteSize := uintptr(len(utf16) * 2)
|
|
h, _, err := procGlobalAlloc.Call(0, byteSize)
|
|
if h == 0 {
|
|
return fmt.Errorf("clipboard write: GlobalAlloc: %w", err)
|
|
}
|
|
|
|
p, _, err := procGlobalLock.Call(h)
|
|
if p == 0 {
|
|
procGlobalFree.Call(h)
|
|
return fmt.Errorf("clipboard write: GlobalLock: %w", err)
|
|
}
|
|
|
|
// Copy UTF-16 data into the locked memory.
|
|
dst := unsafe.Slice((*uint16)(unsafe.Pointer(p)), len(utf16))
|
|
copy(dst, utf16)
|
|
|
|
procGlobalUnlock.Call(h)
|
|
|
|
r1, _, err = procSetClipboardData.Call(uintptr(cfUnicodeText), h)
|
|
if r1 == 0 {
|
|
procGlobalFree.Call(h)
|
|
return fmt.Errorf("clipboard write: SetClipboardData: %w", err)
|
|
}
|
|
|
|
// Windows now owns the handle — do not free it.
|
|
return nil
|
|
}
|