From f210651b08a313beee1254843964121059b54051 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 17 May 2020 18:09:23 +0200 Subject: [PATCH] app/internal/window,app/internal/windows: implement Windows clipboard access Update gio#31 Signed-off-by: Elias Naur --- app/internal/window/os_windows.go | 80 ++++++++++++++++++++++++++ app/internal/windows/windows.go | 93 ++++++++++++++++++++++++++++--- 2 files changed, 165 insertions(+), 8 deletions(-) diff --git a/app/internal/window/os_windows.go b/app/internal/window/os_windows.go index b314de67..70db5de8 100644 --- a/app/internal/window/os_windows.go +++ b/app/internal/window/os_windows.go @@ -6,12 +6,14 @@ import ( "errors" "fmt" "image" + "reflect" "runtime" "sort" "strings" "sync" "time" "unicode" + "unicode/utf16" "unsafe" syscall "golang.org/x/sys/windows" @@ -378,6 +380,84 @@ func (w *window) NewContext() (Context, error) { return nil, errors.New("NewContext: no available backends") } +func (w *window) ReadClipboard() { + w.readClipboard() +} + +func (w *window) readClipboard() error { + if err := windows.OpenClipboard(w.hwnd); err != nil { + return err + } + defer windows.CloseClipboard() + mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT) + if err != nil { + return err + } + ptr, err := windows.GlobalLock(mem) + if err != nil { + return err + } + defer windows.GlobalUnlock(mem) + // Look for terminating null character. + n := 0 + for { + ch := *(*uint16)(unsafe.Pointer(ptr + uintptr(n)*2)) + if ch == 0 { + break + } + n++ + } + var u16 []uint16 + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u16)) + hdr.Data = ptr + hdr.Cap = n + hdr.Len = n + content := string(utf16.Decode(u16)) + go func() { + w.w.Event(system.ClipboardEvent{Text: content}) + }() + return nil +} + +func (w *window) WriteClipboard(s string) { + w.writeClipboard(s) +} + +func (w *window) writeClipboard(s string) error { + u16 := utf16.Encode([]rune(s)) + // Data must be null terminated. + u16 = append(u16, 0) + if err := windows.OpenClipboard(w.hwnd); err != nil { + return err + } + defer windows.CloseClipboard() + if err := windows.EmptyClipboard(); err != nil { + return err + } + n := len(u16) * int(unsafe.Sizeof(u16[0])) + mem, err := windows.GlobalAlloc(n) + if err != nil { + return err + } + ptr, err := windows.GlobalLock(mem) + if err != nil { + windows.GlobalFree(mem) + return err + } + var u16v []uint16 + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u16v)) + hdr.Data = ptr + hdr.Cap = len(u16) + hdr.Len = len(u16) + copy(u16v, u16) + windows.GlobalUnlock(mem) + if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil { + windows.GlobalFree(mem) + return err + } + return nil +} + func (w *window) ShowTextInput(show bool) {} func (w *window) HDC() syscall.Handle { diff --git a/app/internal/windows/windows.go b/app/internal/windows/windows.go index 4fb87dce..49321fba 100644 --- a/app/internal/windows/windows.go +++ b/app/internal/windows/windows.go @@ -171,20 +171,31 @@ const ( PM_REMOVE = 0x0001 PM_NOREMOVE = 0x0000 + + GHND = 0x0042 + + CF_UNICODETEXT = 13 ) var ( kernel32 = syscall.NewLazySystemDLL("kernel32.dll") _GetModuleHandleW = kernel32.NewProc("GetModuleHandleW") + _GlobalAlloc = kernel32.NewProc("GlobalAlloc") + _GlobalFree = kernel32.NewProc("GlobalFree") + _GlobalLock = kernel32.NewProc("GlobalLock") + _GlobalUnlock = kernel32.NewProc("GlobalUnlock") user32 = syscall.NewLazySystemDLL("user32.dll") _AdjustWindowRectEx = user32.NewProc("AdjustWindowRectEx") _CallMsgFilter = user32.NewProc("CallMsgFilterW") + _CloseClipboard = user32.NewProc("CloseClipboard") _CreateWindowEx = user32.NewProc("CreateWindowExW") _DefWindowProc = user32.NewProc("DefWindowProcW") _DestroyWindow = user32.NewProc("DestroyWindow") _DispatchMessage = user32.NewProc("DispatchMessageW") + _EmptyClipboard = user32.NewProc("EmptyClipboard") _GetClientRect = user32.NewProc("GetClientRect") + _GetClipboardData = user32.NewProc("GetClipboardData") _GetDC = user32.NewProc("GetDC") _GetKeyState = user32.NewProc("GetKeyState") _GetMessage = user32.NewProc("GetMessageW") @@ -193,6 +204,7 @@ var ( _LoadCursor = user32.NewProc("LoadCursorW") _MonitorFromPoint = user32.NewProc("MonitorFromPoint") _MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx") + _OpenClipboard = user32.NewProc("OpenClipboard") _PeekMessage = user32.NewProc("PeekMessageW") _PostMessage = user32.NewProc("PostMessageW") _PostQuitMessage = user32.NewProc("PostQuitMessage") @@ -202,6 +214,7 @@ var ( _ScreenToClient = user32.NewProc("ScreenToClient") _ShowWindow = user32.NewProc("ShowWindow") _SetCapture = user32.NewProc("SetCapture") + _SetClipboardData = user32.NewProc("SetClipboardData") _SetForegroundWindow = user32.NewProc("SetForegroundWindow") _SetFocus = user32.NewProc("SetFocus") _SetProcessDPIAware = user32.NewProc("SetProcessDPIAware") @@ -217,14 +230,6 @@ var ( _GetDeviceCaps = gdi32.NewProc("GetDeviceCaps") ) -func GetModuleHandle() (syscall.Handle, error) { - h, _, err := _GetModuleHandleW.Call(uintptr(0)) - if h == 0 { - return 0, fmt.Errorf("GetModuleHandleW failed: %v", err) - } - return syscall.Handle(h), nil -} - func AdjustWindowRectEx(r *Rect, dwStyle uint32, bMenu int, dwExStyle uint32) { _AdjustWindowRectEx.Call(uintptr(unsafe.Pointer(r)), uintptr(dwStyle), uintptr(bMenu), uintptr(dwExStyle)) issue34474KeepAlive(r) @@ -236,6 +241,14 @@ func CallMsgFilter(m *Msg, nCode uintptr) bool { return r != 0 } +func CloseClipboard() error { + r, _, err := _CloseClipboard.Call() + if r == 0 { + return fmt.Errorf("CloseClipboard: %v", err) + } + return nil +} + func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, dwStyle uint32, x, y, w, h int32, hWndParent, hMenu, hInstance syscall.Handle, lpParam uintptr) (syscall.Handle, error) { wname := syscall.StringToUTF16Ptr(lpWindowName) hwnd, _, err := _CreateWindowEx.Call( @@ -270,11 +283,27 @@ func DispatchMessage(m *Msg) { issue34474KeepAlive(m) } +func EmptyClipboard() error { + r, _, err := _EmptyClipboard.Call() + if r == 0 { + return fmt.Errorf("EmptyClipboard: %v", err) + } + return nil +} + func GetClientRect(hwnd syscall.Handle, r *Rect) { _GetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(r))) issue34474KeepAlive(r) } +func GetClipboardData(format uint32) (syscall.Handle, error) { + r, _, err := _GetClipboardData.Call(uintptr(format)) + if r == 0 { + return 0, fmt.Errorf("GetClipboardData: %v", err) + } + return syscall.Handle(r), nil +} + func GetDC(hwnd syscall.Handle) (syscall.Handle, error) { hdc, _, err := _GetDC.Call(uintptr(hwnd)) if hdc == 0 { @@ -283,6 +312,14 @@ func GetDC(hwnd syscall.Handle) (syscall.Handle, error) { return syscall.Handle(hdc), nil } +func GetModuleHandle() (syscall.Handle, error) { + h, _, err := _GetModuleHandleW.Call(uintptr(0)) + if h == 0 { + return 0, fmt.Errorf("GetModuleHandleW failed: %v", err) + } + return syscall.Handle(h), nil +} + func getDeviceCaps(hdc syscall.Handle, index int32) int { c, _, _ := _GetDeviceCaps.Call(uintptr(hdc), uintptr(index)) return int(c) @@ -330,6 +367,30 @@ func GetMessageTime() time.Duration { return time.Duration(r) * time.Millisecond } +func GlobalAlloc(size int) (syscall.Handle, error) { + r, _, err := _GlobalAlloc.Call(GHND, uintptr(size)) + if r == 0 { + return 0, fmt.Errorf("GlobalAlloc: %v", err) + } + return syscall.Handle(r), nil +} + +func GlobalFree(h syscall.Handle) { + _GlobalFree.Call(uintptr(h)) +} + +func GlobalLock(h syscall.Handle) (uintptr, error) { + r, _, err := _GlobalLock.Call(uintptr(h)) + if r == 0 { + return 0, fmt.Errorf("GlobalLock: %v", err) + } + return r, nil +} + +func GlobalUnlock(h syscall.Handle) { + _GlobalUnlock.Call(uintptr(h)) +} + func KillTimer(hwnd syscall.Handle, nIDEvent uintptr) error { r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), 0, 0) if r == 0 { @@ -360,6 +421,14 @@ func MsgWaitForMultipleObjectsEx(nCount uint32, pHandles uintptr, millis, mask, return res, nil } +func OpenClipboard(hwnd syscall.Handle) error { + r, _, err := _OpenClipboard.Call(uintptr(hwnd)) + if r == 0 { + return fmt.Errorf("OpenClipboard: %v", err) + } + return nil +} + func PeekMessage(m *Msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax, wRemoveMsg uint32) bool { r, _, _ := _PeekMessage.Call(uintptr(unsafe.Pointer(m)), uintptr(hwnd), uintptr(wMsgFilterMin), uintptr(wMsgFilterMax), uintptr(wRemoveMsg)) issue34474KeepAlive(m) @@ -413,6 +482,14 @@ func SetCapture(hwnd syscall.Handle) syscall.Handle { return syscall.Handle(r) } +func SetClipboardData(format uint32, mem syscall.Handle) error { + r, _, err := _SetClipboardData.Call(uintptr(format), uintptr(mem)) + if r == 0 { + return fmt.Errorf("SetClipboardData: %v", err) + } + return nil +} + func SetTimer(hwnd syscall.Handle, nIDEvent uintptr, uElapse uint32, timerProc uintptr) error { r, _, err := _SetTimer.Call(uintptr(hwnd), uintptr(nIDEvent), uintptr(uElapse), timerProc) if r == 0 {