clipboard_windows.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. //go:build windows
  2. package image
  3. import (
  4. "bytes"
  5. "fmt"
  6. "image"
  7. "image/color"
  8. "log/slog"
  9. "syscall"
  10. "unsafe"
  11. )
  12. var (
  13. user32 = syscall.NewLazyDLL("user32.dll")
  14. kernel32 = syscall.NewLazyDLL("kernel32.dll")
  15. openClipboard = user32.NewProc("OpenClipboard")
  16. closeClipboard = user32.NewProc("CloseClipboard")
  17. getClipboardData = user32.NewProc("GetClipboardData")
  18. isClipboardFormatAvailable = user32.NewProc("IsClipboardFormatAvailable")
  19. globalLock = kernel32.NewProc("GlobalLock")
  20. globalUnlock = kernel32.NewProc("GlobalUnlock")
  21. globalSize = kernel32.NewProc("GlobalSize")
  22. )
  23. const (
  24. CF_TEXT = 1
  25. CF_UNICODETEXT = 13
  26. CF_DIB = 8
  27. )
  28. type BITMAPINFOHEADER struct {
  29. BiSize uint32
  30. BiWidth int32
  31. BiHeight int32
  32. BiPlanes uint16
  33. BiBitCount uint16
  34. BiCompression uint32
  35. BiSizeImage uint32
  36. BiXPelsPerMeter int32
  37. BiYPelsPerMeter int32
  38. BiClrUsed uint32
  39. BiClrImportant uint32
  40. }
  41. func GetImageFromClipboard() ([]byte, string, error) {
  42. ret, _, _ := openClipboard.Call(0)
  43. if ret == 0 {
  44. return nil, "", fmt.Errorf("failed to open clipboard")
  45. }
  46. defer func(closeClipboard *syscall.LazyProc, a ...uintptr) {
  47. _, _, err := closeClipboard.Call(a...)
  48. if err != nil {
  49. slog.Error("close clipboard failed")
  50. return
  51. }
  52. }(closeClipboard)
  53. isTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_TEXT))
  54. isUnicodeTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_UNICODETEXT))
  55. if isTextAvailable != 0 || isUnicodeTextAvailable != 0 {
  56. // Get text from clipboard
  57. var formatToUse uintptr = CF_TEXT
  58. if isUnicodeTextAvailable != 0 {
  59. formatToUse = CF_UNICODETEXT
  60. }
  61. hClipboardText, _, _ := getClipboardData.Call(formatToUse)
  62. if hClipboardText != 0 {
  63. textPtr, _, _ := globalLock.Call(hClipboardText)
  64. if textPtr != 0 {
  65. defer func(globalUnlock *syscall.LazyProc, a ...uintptr) {
  66. _, _, err := globalUnlock.Call(a...)
  67. if err != nil {
  68. slog.Error("Global unlock failed")
  69. return
  70. }
  71. }(globalUnlock, hClipboardText)
  72. // Get clipboard text
  73. var clipboardText string
  74. if formatToUse == CF_UNICODETEXT {
  75. // Convert wide string to Go string
  76. clipboardText = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(textPtr))[:])
  77. } else {
  78. // Get size of ANSI text
  79. size, _, _ := globalSize.Call(hClipboardText)
  80. if size > 0 {
  81. // Convert ANSI string to Go string
  82. textBytes := make([]byte, size)
  83. copy(textBytes, (*[1 << 20]byte)(unsafe.Pointer(textPtr))[:size:size])
  84. clipboardText = bytesToString(textBytes)
  85. }
  86. }
  87. // Check if the text is not empty
  88. if clipboardText != "" {
  89. return nil, clipboardText, nil
  90. }
  91. }
  92. }
  93. }
  94. hClipboardData, _, _ := getClipboardData.Call(uintptr(CF_DIB))
  95. if hClipboardData == 0 {
  96. return nil, "", fmt.Errorf("failed to get clipboard data")
  97. }
  98. dataPtr, _, _ := globalLock.Call(hClipboardData)
  99. if dataPtr == 0 {
  100. return nil, "", fmt.Errorf("failed to lock clipboard data")
  101. }
  102. defer func(globalUnlock *syscall.LazyProc, a ...uintptr) {
  103. _, _, err := globalUnlock.Call(a...)
  104. if err != nil {
  105. slog.Error("Global unlock failed")
  106. return
  107. }
  108. }(globalUnlock, hClipboardData)
  109. bmiHeader := (*BITMAPINFOHEADER)(unsafe.Pointer(dataPtr))
  110. width := int(bmiHeader.BiWidth)
  111. height := int(bmiHeader.BiHeight)
  112. if height < 0 {
  113. height = -height
  114. }
  115. bitsPerPixel := int(bmiHeader.BiBitCount)
  116. img := image.NewRGBA(image.Rect(0, 0, width, height))
  117. var bitsOffset uintptr
  118. if bitsPerPixel <= 8 {
  119. numColors := uint32(1) << bitsPerPixel
  120. if bmiHeader.BiClrUsed > 0 {
  121. numColors = bmiHeader.BiClrUsed
  122. }
  123. bitsOffset = unsafe.Sizeof(*bmiHeader) + uintptr(numColors*4)
  124. } else {
  125. bitsOffset = unsafe.Sizeof(*bmiHeader)
  126. }
  127. for y := range height {
  128. for x := range width {
  129. srcY := height - y - 1
  130. if bmiHeader.BiHeight < 0 {
  131. srcY = y
  132. }
  133. var pixelPointer unsafe.Pointer
  134. var r, g, b, a uint8
  135. switch bitsPerPixel {
  136. case 24:
  137. stride := (width*3 + 3) &^ 3
  138. pixelPointer = unsafe.Pointer(dataPtr + bitsOffset + uintptr(srcY*stride+x*3))
  139. b = *(*byte)(pixelPointer)
  140. g = *(*byte)(unsafe.Add(pixelPointer, 1))
  141. r = *(*byte)(unsafe.Add(pixelPointer, 2))
  142. a = 255
  143. case 32:
  144. pixelPointer = unsafe.Pointer(dataPtr + bitsOffset + uintptr(srcY*width*4+x*4))
  145. b = *(*byte)(pixelPointer)
  146. g = *(*byte)(unsafe.Add(pixelPointer, 1))
  147. r = *(*byte)(unsafe.Add(pixelPointer, 2))
  148. a = *(*byte)(unsafe.Add(pixelPointer, 3))
  149. if a == 0 {
  150. a = 255
  151. }
  152. default:
  153. return nil, "", fmt.Errorf("unsupported bit count: %d", bitsPerPixel)
  154. }
  155. img.Set(x, y, color.RGBA{R: r, G: g, B: b, A: a})
  156. }
  157. }
  158. imageBytes, err := ImageToBytes(img)
  159. if err != nil {
  160. return nil, "", err
  161. }
  162. return imageBytes, "", nil
  163. }
  164. func bytesToString(b []byte) string {
  165. i := bytes.IndexByte(b, 0)
  166. if i == -1 {
  167. return string(b)
  168. }
  169. return string(b[:i])
  170. }