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  }