clipboard_windows.go

  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}