github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/windows/win32/clipboard.go (about) 1 //go:build windows 2 3 /* 4 * Based on code originally from https://github.com/atotto/clipboard. Copyright (c) 2013 Ato Araki. All rights reserved. 5 */ 6 7 package win32 8 9 import ( 10 "runtime" 11 "syscall" 12 "time" 13 "unsafe" 14 ) 15 16 const ( 17 cfUnicodetext = 13 18 gmemMoveable = 0x0002 19 ) 20 21 // waitOpenClipboard opens the clipboard, waiting for up to a second to do so. 22 func waitOpenClipboard() error { 23 started := time.Now() 24 limit := started.Add(time.Second) 25 var r uintptr 26 var err error 27 for time.Now().Before(limit) { 28 r, _, err = procOpenClipboard.Call(0) 29 if r != 0 { 30 return nil 31 } 32 time.Sleep(time.Millisecond) 33 } 34 return err 35 } 36 37 func GetClipboardText() (string, error) { 38 // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution). 39 // Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock. 40 runtime.LockOSThread() 41 defer runtime.UnlockOSThread() 42 if formatAvailable, _, err := procIsClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 { 43 return "", err 44 } 45 err := waitOpenClipboard() 46 if err != nil { 47 return "", err 48 } 49 50 h, _, err := procGetClipboardData.Call(cfUnicodetext) 51 if h == 0 { 52 _, _, _ = procCloseClipboard.Call() 53 return "", err 54 } 55 56 l, _, err := kernelGlobalLock.Call(h) 57 if l == 0 { 58 _, _, _ = procCloseClipboard.Call() 59 return "", err 60 } 61 62 text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:]) 63 64 r, _, err := kernelGlobalUnlock.Call(h) 65 if r == 0 { 66 _, _, _ = procCloseClipboard.Call() 67 return "", err 68 } 69 70 closed, _, err := procCloseClipboard.Call() 71 if closed == 0 { 72 return "", err 73 } 74 return text, nil 75 } 76 77 func SetClipboardText(text string) error { 78 // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution). 79 // Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock. 80 runtime.LockOSThread() 81 defer runtime.UnlockOSThread() 82 83 err := waitOpenClipboard() 84 if err != nil { 85 return err 86 } 87 88 r, _, err := procEmptyClipboard.Call(0) 89 if r == 0 { 90 _, _, _ = procCloseClipboard.Call() 91 return err 92 } 93 94 data, err := syscall.UTF16FromString(text) 95 if err != nil { 96 return err 97 } 98 99 // "If the hMem parameter identifies a memory object, the object must have 100 // been allocated using the function with the GMEM_MOVEABLE flag." 101 h, _, err := kernelGlobalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0])))) 102 if h == 0 { 103 _, _, _ = procCloseClipboard.Call() 104 return err 105 } 106 defer func() { 107 if h != 0 { 108 kernelGlobalFree.Call(h) 109 } 110 }() 111 112 l, _, err := kernelGlobalLock.Call(h) 113 if l == 0 { 114 _, _, _ = procCloseClipboard.Call() 115 return err 116 } 117 118 r, _, err = kernelLstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0]))) 119 if r == 0 { 120 _, _, _ = procCloseClipboard.Call() 121 return err 122 } 123 124 r, _, err = kernelGlobalUnlock.Call(h) 125 if r == 0 { 126 if err.(syscall.Errno) != 0 { 127 _, _, _ = procCloseClipboard.Call() 128 return err 129 } 130 } 131 132 r, _, err = procSetClipboardData.Call(cfUnicodetext, h) 133 if r == 0 { 134 _, _, _ = procCloseClipboard.Call() 135 return err 136 } 137 h = 0 // suppress deferred cleanup 138 closed, _, err := procCloseClipboard.Call() 139 if closed == 0 { 140 return err 141 } 142 return nil 143 }