clipboard_windows.go 4.1 KB

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