1// Copyright 2013 @atotto. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build windows
6
7package clipboard
8
9import (
10 "runtime"
11 "syscall"
12 "time"
13 "unsafe"
14)
15
16const (
17 cfUnicodetext = 13
18 gmemMoveable = 0x0002
19)
20
21var (
22 user32 = syscall.MustLoadDLL("user32")
23 isClipboardFormatAvailable = user32.MustFindProc("IsClipboardFormatAvailable")
24 openClipboard = user32.MustFindProc("OpenClipboard")
25 closeClipboard = user32.MustFindProc("CloseClipboard")
26 emptyClipboard = user32.MustFindProc("EmptyClipboard")
27 getClipboardData = user32.MustFindProc("GetClipboardData")
28 setClipboardData = user32.MustFindProc("SetClipboardData")
29
30 kernel32 = syscall.NewLazyDLL("kernel32")
31 globalAlloc = kernel32.NewProc("GlobalAlloc")
32 globalFree = kernel32.NewProc("GlobalFree")
33 globalLock = kernel32.NewProc("GlobalLock")
34 globalUnlock = kernel32.NewProc("GlobalUnlock")
35 lstrcpy = kernel32.NewProc("lstrcpyW")
36)
37
38// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
39func waitOpenClipboard() error {
40 started := time.Now()
41 limit := started.Add(time.Second)
42 var r uintptr
43 var err error
44 for time.Now().Before(limit) {
45 r, _, err = openClipboard.Call(0)
46 if r != 0 {
47 return nil
48 }
49 time.Sleep(time.Millisecond)
50 }
51 return err
52}
53
54func readAll() (string, error) {
55 // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
56 // 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.
57 runtime.LockOSThread()
58 defer runtime.UnlockOSThread()
59 if formatAvailable, _, err := isClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
60 return "", err
61 }
62 err := waitOpenClipboard()
63 if err != nil {
64 return "", err
65 }
66
67 h, _, err := getClipboardData.Call(cfUnicodetext)
68 if h == 0 {
69 _, _, _ = closeClipboard.Call()
70 return "", err
71 }
72
73 l, _, err := globalLock.Call(h)
74 if l == 0 {
75 _, _, _ = closeClipboard.Call()
76 return "", err
77 }
78
79 text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
80
81 r, _, err := globalUnlock.Call(h)
82 if r == 0 {
83 _, _, _ = closeClipboard.Call()
84 return "", err
85 }
86
87 closed, _, err := closeClipboard.Call()
88 if closed == 0 {
89 return "", err
90 }
91 return text, nil
92}
93
94func writeAll(text string) error {
95 // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
96 // 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.
97 runtime.LockOSThread()
98 defer runtime.UnlockOSThread()
99
100 err := waitOpenClipboard()
101 if err != nil {
102 return err
103 }
104
105 r, _, err := emptyClipboard.Call(0)
106 if r == 0 {
107 _, _, _ = closeClipboard.Call()
108 return err
109 }
110
111 data := syscall.StringToUTF16(text)
112
113 // "If the hMem parameter identifies a memory object, the object must have
114 // been allocated using the function with the GMEM_MOVEABLE flag."
115 h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
116 if h == 0 {
117 _, _, _ = closeClipboard.Call()
118 return err
119 }
120 defer func() {
121 if h != 0 {
122 globalFree.Call(h)
123 }
124 }()
125
126 l, _, err := globalLock.Call(h)
127 if l == 0 {
128 _, _, _ = closeClipboard.Call()
129 return err
130 }
131
132 r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
133 if r == 0 {
134 _, _, _ = closeClipboard.Call()
135 return err
136 }
137
138 r, _, err = globalUnlock.Call(h)
139 if r == 0 {
140 if err.(syscall.Errno) != 0 {
141 _, _, _ = closeClipboard.Call()
142 return err
143 }
144 }
145
146 r, _, err = setClipboardData.Call(cfUnicodetext, h)
147 if r == 0 {
148 _, _, _ = closeClipboard.Call()
149 return err
150 }
151 h = 0 // suppress deferred cleanup
152 closed, _, err := closeClipboard.Call()
153 if closed == 0 {
154 return err
155 }
156 return nil
157}