2020-08-23 22:04:24 +10:00
// Copyright 2013 @atotto. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package clipboard
import (
2023-04-24 13:32:10 +10:00
"runtime"
2020-08-23 22:04:24 +10:00
"syscall"
"time"
"unsafe"
)
const (
cfUnicodetext = 13
gmemMoveable = 0x0002
)
var (
2023-04-24 13:32:10 +10:00
user32 = syscall . MustLoadDLL ( "user32" )
isClipboardFormatAvailable = user32 . MustFindProc ( "IsClipboardFormatAvailable" )
openClipboard = user32 . MustFindProc ( "OpenClipboard" )
closeClipboard = user32 . MustFindProc ( "CloseClipboard" )
emptyClipboard = user32 . MustFindProc ( "EmptyClipboard" )
getClipboardData = user32 . MustFindProc ( "GetClipboardData" )
setClipboardData = user32 . MustFindProc ( "SetClipboardData" )
2020-08-23 22:04:24 +10:00
kernel32 = syscall . NewLazyDLL ( "kernel32" )
globalAlloc = kernel32 . NewProc ( "GlobalAlloc" )
globalFree = kernel32 . NewProc ( "GlobalFree" )
globalLock = kernel32 . NewProc ( "GlobalLock" )
globalUnlock = kernel32 . NewProc ( "GlobalUnlock" )
lstrcpy = kernel32 . NewProc ( "lstrcpyW" )
)
// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
func waitOpenClipboard ( ) error {
started := time . Now ( )
limit := started . Add ( time . Second )
var r uintptr
var err error
for time . Now ( ) . Before ( limit ) {
r , _ , err = openClipboard . Call ( 0 )
if r != 0 {
return nil
}
time . Sleep ( time . Millisecond )
}
return err
}
func readAll ( ) ( string , error ) {
2023-04-24 13:32:10 +10:00
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
// 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.
runtime . LockOSThread ( )
defer runtime . UnlockOSThread ( )
if formatAvailable , _ , err := isClipboardFormatAvailable . Call ( cfUnicodetext ) ; formatAvailable == 0 {
return "" , err
}
2020-08-23 22:04:24 +10:00
err := waitOpenClipboard ( )
if err != nil {
return "" , err
}
h , _ , err := getClipboardData . Call ( cfUnicodetext )
if h == 0 {
2023-04-24 13:32:10 +10:00
_ , _ , _ = closeClipboard . Call ( )
2020-08-23 22:04:24 +10:00
return "" , err
}
l , _ , err := globalLock . Call ( h )
if l == 0 {
2023-04-24 13:32:10 +10:00
_ , _ , _ = closeClipboard . Call ( )
2020-08-23 22:04:24 +10:00
return "" , err
}
text := syscall . UTF16ToString ( ( * [ 1 << 20 ] uint16 ) ( unsafe . Pointer ( l ) ) [ : ] )
r , _ , err := globalUnlock . Call ( h )
if r == 0 {
2023-04-24 13:32:10 +10:00
_ , _ , _ = closeClipboard . Call ( )
2020-08-23 22:04:24 +10:00
return "" , err
}
2023-04-24 13:32:10 +10:00
closed , _ , err := closeClipboard . Call ( )
if closed == 0 {
return "" , err
}
2020-08-23 22:04:24 +10:00
return text , nil
}
func writeAll ( text string ) error {
2023-04-24 13:32:10 +10:00
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
// 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.
runtime . LockOSThread ( )
defer runtime . UnlockOSThread ( )
2020-08-23 22:04:24 +10:00
err := waitOpenClipboard ( )
if err != nil {
return err
}
r , _ , err := emptyClipboard . Call ( 0 )
if r == 0 {
2023-04-24 13:32:10 +10:00
_ , _ , _ = closeClipboard . Call ( )
2020-08-23 22:04:24 +10:00
return err
}
data := syscall . StringToUTF16 ( text )
// "If the hMem parameter identifies a memory object, the object must have
// been allocated using the function with the GMEM_MOVEABLE flag."
h , _ , err := globalAlloc . Call ( gmemMoveable , uintptr ( len ( data ) * int ( unsafe . Sizeof ( data [ 0 ] ) ) ) )
if h == 0 {
2023-04-24 13:32:10 +10:00
_ , _ , _ = closeClipboard . Call ( )
2020-08-23 22:04:24 +10:00
return err
}
defer func ( ) {
if h != 0 {
globalFree . Call ( h )
}
} ( )
l , _ , err := globalLock . Call ( h )
if l == 0 {
2023-04-24 13:32:10 +10:00
_ , _ , _ = closeClipboard . Call ( )
2020-08-23 22:04:24 +10:00
return err
}
r , _ , err = lstrcpy . Call ( l , uintptr ( unsafe . Pointer ( & data [ 0 ] ) ) )
if r == 0 {
2023-04-24 13:32:10 +10:00
_ , _ , _ = closeClipboard . Call ( )
2020-08-23 22:04:24 +10:00
return err
}
r , _ , err = globalUnlock . Call ( h )
if r == 0 {
if err . ( syscall . Errno ) != 0 {
2023-04-24 13:32:10 +10:00
_ , _ , _ = closeClipboard . Call ( )
2020-08-23 22:04:24 +10:00
return err
}
}
r , _ , err = setClipboardData . Call ( cfUnicodetext , h )
if r == 0 {
2023-04-24 13:32:10 +10:00
_ , _ , _ = closeClipboard . Call ( )
2020-08-23 22:04:24 +10:00
return err
}
h = 0 // suppress deferred cleanup
2023-04-24 13:32:10 +10:00
closed , _ , err := closeClipboard . Call ( )
if closed == 0 {
return err
}
2020-08-23 22:04:24 +10:00
return nil
}