Command line client for Write.as https://write.as/apps/cli
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

129 lines
2.6 KiB

  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. "syscall"
  8. "time"
  9. "unsafe"
  10. )
  11. const (
  12. cfUnicodetext = 13
  13. gmemMoveable = 0x0002
  14. )
  15. var (
  16. user32 = syscall.MustLoadDLL("user32")
  17. openClipboard = user32.MustFindProc("OpenClipboard")
  18. closeClipboard = user32.MustFindProc("CloseClipboard")
  19. emptyClipboard = user32.MustFindProc("EmptyClipboard")
  20. getClipboardData = user32.MustFindProc("GetClipboardData")
  21. setClipboardData = user32.MustFindProc("SetClipboardData")
  22. kernel32 = syscall.NewLazyDLL("kernel32")
  23. globalAlloc = kernel32.NewProc("GlobalAlloc")
  24. globalFree = kernel32.NewProc("GlobalFree")
  25. globalLock = kernel32.NewProc("GlobalLock")
  26. globalUnlock = kernel32.NewProc("GlobalUnlock")
  27. lstrcpy = kernel32.NewProc("lstrcpyW")
  28. )
  29. // waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
  30. func waitOpenClipboard() error {
  31. started := time.Now()
  32. limit := started.Add(time.Second)
  33. var r uintptr
  34. var err error
  35. for time.Now().Before(limit) {
  36. r, _, err = openClipboard.Call(0)
  37. if r != 0 {
  38. return nil
  39. }
  40. time.Sleep(time.Millisecond)
  41. }
  42. return err
  43. }
  44. func readAll() (string, error) {
  45. err := waitOpenClipboard()
  46. if err != nil {
  47. return "", err
  48. }
  49. defer closeClipboard.Call()
  50. h, _, err := getClipboardData.Call(cfUnicodetext)
  51. if h == 0 {
  52. return "", err
  53. }
  54. l, _, err := globalLock.Call(h)
  55. if l == 0 {
  56. return "", err
  57. }
  58. text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
  59. r, _, err := globalUnlock.Call(h)
  60. if r == 0 {
  61. return "", err
  62. }
  63. return text, nil
  64. }
  65. func writeAll(text string) error {
  66. err := waitOpenClipboard()
  67. if err != nil {
  68. return err
  69. }
  70. defer closeClipboard.Call()
  71. r, _, err := emptyClipboard.Call(0)
  72. if r == 0 {
  73. return err
  74. }
  75. data := syscall.StringToUTF16(text)
  76. // "If the hMem parameter identifies a memory object, the object must have
  77. // been allocated using the function with the GMEM_MOVEABLE flag."
  78. h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
  79. if h == 0 {
  80. return err
  81. }
  82. defer func() {
  83. if h != 0 {
  84. globalFree.Call(h)
  85. }
  86. }()
  87. l, _, err := globalLock.Call(h)
  88. if l == 0 {
  89. return err
  90. }
  91. r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
  92. if r == 0 {
  93. return err
  94. }
  95. r, _, err = globalUnlock.Call(h)
  96. if r == 0 {
  97. if err.(syscall.Errno) != 0 {
  98. return err
  99. }
  100. }
  101. r, _, err = setClipboardData.Call(cfUnicodetext, h)
  102. if r == 0 {
  103. return err
  104. }
  105. h = 0 // suppress deferred cleanup
  106. return nil
  107. }