From 76e43af9c190091f9fc16a945b8b2a374c428bc2 Mon Sep 17 00:00:00 2001 From: phantomreactor Date: Fri, 16 May 2025 21:45:19 +0530 Subject: [PATCH 1/2] paste images from clipboard --- go.mod | 5 +- go.sum | 3 + internal/tui/components/chat/editor.go | 25 ++++ internal/tui/image/clipboard_unix.go | 49 +++++++ internal/tui/image/clipboard_windows.go | 179 ++++++++++++++++++++++++ internal/tui/image/images.go | 12 ++ 6 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 internal/tui/image/clipboard_unix.go create mode 100644 internal/tui/image/clipboard_windows.go diff --git a/go.mod b/go.mod index 6bc9b150a97..798cd709bad 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/google/uuid v1.6.0 github.com/lithammer/fuzzysearch v1.1.8 github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231 + github.com/lxn/win v0.0.0-20210218163916-a377121e959e github.com/mark3labs/mcp-go v0.17.0 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 github.com/muesli/reflow v0.3.0 @@ -42,7 +43,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect - github.com/atotto/clipboard v0.1.4 // indirect + github.com/atotto/clipboard v0.1.4 github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect @@ -115,7 +116,7 @@ require ( go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.37.0 // indirect - golang.org/x/image v0.26.0 // indirect + golang.org/x/image v0.26.0 golang.org/x/net v0.39.0 // indirect golang.org/x/sync v0.13.0 // indirect golang.org/x/sys v0.32.0 // indirect diff --git a/go.sum b/go.sum index c6a79ab1617..8a78e83cb63 100644 --- a/go.sum +++ b/go.sum @@ -150,6 +150,8 @@ github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231 h1:9rjt7AfnrX github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231/go.mod h1:S5etECMx+sZnW0Gm100Ma9J1PgVCTgNyFaqGu2b08b4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc= +github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/mark3labs/mcp-go v0.17.0 h1:5Ps6T7qXr7De/2QTqs9h6BKeZ/qdeUeGrgM5lPzi930= github.com/mark3labs/mcp-go v0.17.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -295,6 +297,7 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/tui/components/chat/editor.go b/internal/tui/components/chat/editor.go index 4d5ba01282a..8ba42a8efd2 100644 --- a/internal/tui/components/chat/editor.go +++ b/internal/tui/components/chat/editor.go @@ -2,12 +2,14 @@ package chat import ( "fmt" + "log/slog" "os" "os/exec" "slices" "strings" "unicode" + "github.com/atotto/clipboard" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/textarea" tea "github.com/charmbracelet/bubbletea" @@ -16,6 +18,7 @@ import ( "github.com/sst/opencode/internal/message" "github.com/sst/opencode/internal/status" "github.com/sst/opencode/internal/tui/components/dialog" + "github.com/sst/opencode/internal/tui/image" "github.com/sst/opencode/internal/tui/layout" "github.com/sst/opencode/internal/tui/styles" "github.com/sst/opencode/internal/tui/theme" @@ -34,6 +37,7 @@ type editorCmp struct { type EditorKeyMaps struct { Send key.Binding OpenEditor key.Binding + Paste key.Binding } type bluredEditorKeyMaps struct { @@ -56,6 +60,11 @@ var editorMaps = EditorKeyMaps{ key.WithKeys("ctrl+e"), key.WithHelp("ctrl+e", "open editor"), ), + Paste: key.NewBinding( + key.WithKeys("ctrl+v"), + key.WithHelp("ctrl+v", "paste content"), + ), + } var DeleteKeyMaps = DeleteAttachmentKeyMaps{ @@ -200,6 +209,22 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.deleteMode = false return m, nil } + + if key.Matches(msg, editorMaps.Paste) { + imageBytes, text, err := image.GetImageFromClipboard() + if err != nil { + slog.Error(err.Error()) + return m, cmd + } + if len(imageBytes) != 0 { + attachmentName := fmt.Sprintf("clipboard-image-%d", len(m.attachments)) + attachment := message.Attachment{FileName: attachmentName, Content: imageBytes, MimeType: "image/png"} + m.attachments = append(m.attachments, attachment) + } else { + m.textarea.SetValue(m.textarea.Value() + text) + } + return m, cmd + } // Handle Enter key if m.textarea.Focused() && key.Matches(msg, editorMaps.Send) { value := m.textarea.Value() diff --git a/internal/tui/image/clipboard_unix.go b/internal/tui/image/clipboard_unix.go new file mode 100644 index 00000000000..3cb59020779 --- /dev/null +++ b/internal/tui/image/clipboard_unix.go @@ -0,0 +1,49 @@ +//go:build !windows + +package image + +import ( + "bytes" + "fmt" + "image" + "github.com/atotto/clipboard" +) + +func GetImageFromClipboard() ([]byte, string, error) { + text, err := clipboard.ReadAll() + if err != nil { + return nil, "", fmt.Errorf("Error reading clipboard") + } + + if text == "" { + return nil, "", nil + } + + binaryData := []byte(text) + imageBytes, err := binaryToImage(binaryData) + if err != nil { + return nil, text, nil + } + return imageBytes, "", nil + +} + + + +func binaryToImage(data []byte) ([]byte, error) { + reader := bytes.NewReader(data) + img, _, err := image.Decode(reader) + if err != nil { + return nil, fmt.Errorf("Unable to covert bytes to image") + } + + return ImageToBytes(img) +} + + +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/internal/tui/image/clipboard_windows.go b/internal/tui/image/clipboard_windows.go new file mode 100644 index 00000000000..4ce4f9a0b96 --- /dev/null +++ b/internal/tui/image/clipboard_windows.go @@ -0,0 +1,179 @@ +//go:build windows + +package image + +import ( + "bytes" + "fmt" + "image" + "image/color" + "syscall" + "unsafe" +) + +var ( + user32 = syscall.NewLazyDLL("user32.dll") + kernel32 = syscall.NewLazyDLL("kernel32.dll") + openClipboard = user32.NewProc("OpenClipboard") + closeClipboard = user32.NewProc("CloseClipboard") + getClipboardData = user32.NewProc("GetClipboardData") + isClipboardFormatAvailable = user32.NewProc("IsClipboardFormatAvailable") + globalLock = kernel32.NewProc("GlobalLock") + globalUnlock = kernel32.NewProc("GlobalUnlock") + globalSize = kernel32.NewProc("GlobalSize") +) + +const ( + CF_TEXT = 1 + CF_UNICODETEXT = 13 + CF_DIB = 8 +) + +type BITMAPINFOHEADER struct { + BiSize uint32 + BiWidth int32 + BiHeight int32 + BiPlanes uint16 + BiBitCount uint16 + BiCompression uint32 + BiSizeImage uint32 + BiXPelsPerMeter int32 + BiYPelsPerMeter int32 + BiClrUsed uint32 + BiClrImportant uint32 +} + +func GetImageFromClipboard() ([]byte, string, error) { + ret, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_DIB)) + if ret == 0 { + return nil, "", fmt.Errorf("no DIB image in clipboard") + } + + ret, _, _ = openClipboard.Call(0) + if ret == 0 { + return nil, "", fmt.Errorf("failed to open clipboard") + } + defer closeClipboard.Call() +isTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_TEXT)) + isUnicodeTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_UNICODETEXT)) + + if isTextAvailable != 0 || isUnicodeTextAvailable != 0 { + // Get text from clipboard + var formatToUse uintptr = CF_TEXT + if isUnicodeTextAvailable != 0 { + formatToUse = CF_UNICODETEXT + } + + hClipboardText, _, _ := getClipboardData.Call(formatToUse) + if hClipboardText != 0 { + textPtr, _, _ := globalLock.Call(hClipboardText) + if textPtr != 0 { + defer globalUnlock.Call(hClipboardText) + + // Get clipboard text + var clipboardText string + if formatToUse == CF_UNICODETEXT { + // Convert wide string to Go string + clipboardText = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(textPtr))[:]) + } else { + // Get size of ANSI text + size, _, _ := globalSize.Call(hClipboardText) + if size > 0 { + // Convert ANSI string to Go string + textBytes := make([]byte, size) + copy(textBytes, (*[1 << 20]byte)(unsafe.Pointer(textPtr))[:size:size]) + clipboardText = bytesToString(textBytes) + } + } + + // Check if the text is not empty + if clipboardText != "" { + return nil, clipboardText, nil + } + } + } + } + + hClipboardData, _, _ := getClipboardData.Call(uintptr(CF_DIB)) + if hClipboardData == 0 { + return nil, "", fmt.Errorf("failed to get clipboard data") + } + + dataPtr, _, _ := globalLock.Call(hClipboardData) + if dataPtr == 0 { + return nil, "", fmt.Errorf("failed to lock clipboard data") + } + defer globalUnlock.Call(hClipboardData) + + bmiHeader := (*BITMAPINFOHEADER)(unsafe.Pointer(dataPtr)) + + width := int(bmiHeader.BiWidth) + height := int(bmiHeader.BiHeight) + if height < 0 { + height = -height + } + bitsPerPixel := int(bmiHeader.BiBitCount) + + img := image.NewRGBA(image.Rect(0, 0, width, height)) + + var bitsOffset uintptr + if bitsPerPixel <= 8 { + numColors := uint32(1) << bitsPerPixel + if bmiHeader.BiClrUsed > 0 { + numColors = bmiHeader.BiClrUsed + } + bitsOffset = uintptr(unsafe.Sizeof(*bmiHeader)) + uintptr(numColors*4) + } else { + bitsOffset = uintptr(unsafe.Sizeof(*bmiHeader)) + } + + for y := range height { + for x := range width { + + srcY := height - y - 1 + if bmiHeader.BiHeight < 0 { + srcY = y + } + + var pixelPointer unsafe.Pointer + var r, g, b, a uint8 + + switch bitsPerPixel { + case 24: + stride := (width*3 + 3) &^ 3 + pixelPointer = unsafe.Pointer(dataPtr + bitsOffset + uintptr(srcY*stride+x*3)) + b = *(*byte)(pixelPointer) + g = *(*byte)(unsafe.Add(pixelPointer, 1)) + r = *(*byte)(unsafe.Add(pixelPointer, 2)) + a = 255 + case 32: + pixelPointer = unsafe.Pointer(dataPtr + bitsOffset + uintptr(srcY*width*4+x*4)) + b = *(*byte)(pixelPointer) + g = *(*byte)(unsafe.Add(pixelPointer, 1)) + r = *(*byte)(unsafe.Add(pixelPointer, 2)) + a = *(*byte)(unsafe.Add(pixelPointer, 3)) + if a == 0 { + a = 255 + } + default: + return nil, "", fmt.Errorf("unsupported bit count: %d", bitsPerPixel) + } + + img.Set(x, y, color.RGBA{r, g, b, a}) + } + } + + imageBytes, err := ImageToBytes(img) + if err != nil { + return nil, "", err + } + return imageBytes, "", nil +} + +func bytesToString(b []byte) string { + i := bytes.IndexByte(b, 0) + if i == -1 { + return string(b) + } + return string(b[:i]) +} diff --git a/internal/tui/image/images.go b/internal/tui/image/images.go index b55884d1180..f476b201ca0 100644 --- a/internal/tui/image/images.go +++ b/internal/tui/image/images.go @@ -1,8 +1,10 @@ package image import ( + "bytes" "fmt" "image" + "image/png" "os" "strings" @@ -71,3 +73,13 @@ func ImagePreview(width int, filename string) (string, error) { return imageString, nil } + +func ImageToBytes(image image.Image) ([]byte, error) { + buf := new(bytes.Buffer) + err := png.Encode(buf, image) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} From edb2d231585a1cae2c0338e1dc01f13b2556cacb Mon Sep 17 00:00:00 2001 From: phantomreactor Date: Sat, 17 May 2025 00:16:36 +0530 Subject: [PATCH 2/2] fix attachment names --- go.mod | 1 - go.sum | 3 - internal/tui/components/chat/editor.go | 8 +-- internal/tui/image/clipboard_windows.go | 79 ++++++++++++++----------- 4 files changed, 49 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index 798cd709bad..1881759006a 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( github.com/google/uuid v1.6.0 github.com/lithammer/fuzzysearch v1.1.8 github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231 - github.com/lxn/win v0.0.0-20210218163916-a377121e959e github.com/mark3labs/mcp-go v0.17.0 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 github.com/muesli/reflow v0.3.0 diff --git a/go.sum b/go.sum index 8a78e83cb63..c6a79ab1617 100644 --- a/go.sum +++ b/go.sum @@ -150,8 +150,6 @@ github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231 h1:9rjt7AfnrX github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231/go.mod h1:S5etECMx+sZnW0Gm100Ma9J1PgVCTgNyFaqGu2b08b4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc= -github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/mark3labs/mcp-go v0.17.0 h1:5Ps6T7qXr7De/2QTqs9h6BKeZ/qdeUeGrgM5lPzi930= github.com/mark3labs/mcp-go v0.17.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -297,7 +295,6 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/tui/components/chat/editor.go b/internal/tui/components/chat/editor.go index 8ba42a8efd2..607aaedf363 100644 --- a/internal/tui/components/chat/editor.go +++ b/internal/tui/components/chat/editor.go @@ -9,7 +9,6 @@ import ( "strings" "unicode" - "github.com/atotto/clipboard" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/textarea" tea "github.com/charmbracelet/bubbletea" @@ -64,7 +63,6 @@ var editorMaps = EditorKeyMaps{ key.WithKeys("ctrl+v"), key.WithHelp("ctrl+v", "paste content"), ), - } var DeleteKeyMaps = DeleteAttachmentKeyMaps{ @@ -209,16 +207,16 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.deleteMode = false return m, nil } - + if key.Matches(msg, editorMaps.Paste) { - imageBytes, text, err := image.GetImageFromClipboard() + imageBytes, text, err := image.GetImageFromClipboard() if err != nil { slog.Error(err.Error()) return m, cmd } if len(imageBytes) != 0 { attachmentName := fmt.Sprintf("clipboard-image-%d", len(m.attachments)) - attachment := message.Attachment{FileName: attachmentName, Content: imageBytes, MimeType: "image/png"} + attachment := message.Attachment{FilePath: attachmentName, FileName: attachmentName, Content: imageBytes, MimeType: "image/png"} m.attachments = append(m.attachments, attachment) } else { m.textarea.SetValue(m.textarea.Value() + text) diff --git a/internal/tui/image/clipboard_windows.go b/internal/tui/image/clipboard_windows.go index 4ce4f9a0b96..6431ce3d4ca 100644 --- a/internal/tui/image/clipboard_windows.go +++ b/internal/tui/image/clipboard_windows.go @@ -7,26 +7,27 @@ import ( "fmt" "image" "image/color" + "log/slog" "syscall" "unsafe" ) var ( - user32 = syscall.NewLazyDLL("user32.dll") - kernel32 = syscall.NewLazyDLL("kernel32.dll") - openClipboard = user32.NewProc("OpenClipboard") - closeClipboard = user32.NewProc("CloseClipboard") - getClipboardData = user32.NewProc("GetClipboardData") + user32 = syscall.NewLazyDLL("user32.dll") + kernel32 = syscall.NewLazyDLL("kernel32.dll") + openClipboard = user32.NewProc("OpenClipboard") + closeClipboard = user32.NewProc("CloseClipboard") + getClipboardData = user32.NewProc("GetClipboardData") isClipboardFormatAvailable = user32.NewProc("IsClipboardFormatAvailable") - globalLock = kernel32.NewProc("GlobalLock") - globalUnlock = kernel32.NewProc("GlobalUnlock") - globalSize = kernel32.NewProc("GlobalSize") + globalLock = kernel32.NewProc("GlobalLock") + globalUnlock = kernel32.NewProc("GlobalUnlock") + globalSize = kernel32.NewProc("GlobalSize") ) const ( - CF_TEXT = 1 + CF_TEXT = 1 CF_UNICODETEXT = 13 - CF_DIB = 8 + CF_DIB = 8 ) type BITMAPINFOHEADER struct { @@ -44,32 +45,39 @@ type BITMAPINFOHEADER struct { } func GetImageFromClipboard() ([]byte, string, error) { - ret, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_DIB)) - if ret == 0 { - return nil, "", fmt.Errorf("no DIB image in clipboard") - } - - ret, _, _ = openClipboard.Call(0) + ret, _, _ := openClipboard.Call(0) if ret == 0 { return nil, "", fmt.Errorf("failed to open clipboard") } - defer closeClipboard.Call() -isTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_TEXT)) + defer func(closeClipboard *syscall.LazyProc, a ...uintptr) { + _, _, err := closeClipboard.Call(a...) + if err != nil { + slog.Error("close clipboard failed") + return + } + }(closeClipboard) + isTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_TEXT)) isUnicodeTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_UNICODETEXT)) - + if isTextAvailable != 0 || isUnicodeTextAvailable != 0 { // Get text from clipboard var formatToUse uintptr = CF_TEXT if isUnicodeTextAvailable != 0 { formatToUse = CF_UNICODETEXT } - + hClipboardText, _, _ := getClipboardData.Call(formatToUse) if hClipboardText != 0 { textPtr, _, _ := globalLock.Call(hClipboardText) if textPtr != 0 { - defer globalUnlock.Call(hClipboardText) - + defer func(globalUnlock *syscall.LazyProc, a ...uintptr) { + _, _, err := globalUnlock.Call(a...) + if err != nil { + slog.Error("Global unlock failed") + return + } + }(globalUnlock, hClipboardText) + // Get clipboard text var clipboardText string if formatToUse == CF_UNICODETEXT { @@ -85,7 +93,7 @@ isTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_TEXT)) clipboardText = bytesToString(textBytes) } } - + // Check if the text is not empty if clipboardText != "" { return nil, clipboardText, nil @@ -93,7 +101,6 @@ isTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_TEXT)) } } } - hClipboardData, _, _ := getClipboardData.Call(uintptr(CF_DIB)) if hClipboardData == 0 { return nil, "", fmt.Errorf("failed to get clipboard data") @@ -103,14 +110,20 @@ isTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_TEXT)) if dataPtr == 0 { return nil, "", fmt.Errorf("failed to lock clipboard data") } - defer globalUnlock.Call(hClipboardData) + defer func(globalUnlock *syscall.LazyProc, a ...uintptr) { + _, _, err := globalUnlock.Call(a...) + if err != nil { + slog.Error("Global unlock failed") + return + } + }(globalUnlock, hClipboardData) bmiHeader := (*BITMAPINFOHEADER)(unsafe.Pointer(dataPtr)) width := int(bmiHeader.BiWidth) height := int(bmiHeader.BiHeight) if height < 0 { - height = -height + height = -height } bitsPerPixel := int(bmiHeader.BiBitCount) @@ -122,17 +135,17 @@ isTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_TEXT)) if bmiHeader.BiClrUsed > 0 { numColors = bmiHeader.BiClrUsed } - bitsOffset = uintptr(unsafe.Sizeof(*bmiHeader)) + uintptr(numColors*4) + bitsOffset = unsafe.Sizeof(*bmiHeader) + uintptr(numColors*4) } else { - bitsOffset = uintptr(unsafe.Sizeof(*bmiHeader)) + bitsOffset = unsafe.Sizeof(*bmiHeader) } for y := range height { for x := range width { - + srcY := height - y - 1 if bmiHeader.BiHeight < 0 { - srcY = y + srcY = y } var pixelPointer unsafe.Pointer @@ -140,7 +153,7 @@ isTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_TEXT)) switch bitsPerPixel { case 24: - stride := (width*3 + 3) &^ 3 + stride := (width*3 + 3) &^ 3 pixelPointer = unsafe.Pointer(dataPtr + bitsOffset + uintptr(srcY*stride+x*3)) b = *(*byte)(pixelPointer) g = *(*byte)(unsafe.Add(pixelPointer, 1)) @@ -153,13 +166,13 @@ isTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_TEXT)) r = *(*byte)(unsafe.Add(pixelPointer, 2)) a = *(*byte)(unsafe.Add(pixelPointer, 3)) if a == 0 { - a = 255 + a = 255 } default: return nil, "", fmt.Errorf("unsupported bit count: %d", bitsPerPixel) } - img.Set(x, y, color.RGBA{r, g, b, a}) + img.Set(x, y, color.RGBA{R: r, G: g, B: b, A: a}) } }