From 15f257b5b31e6547cb569a4c25befaf0e0c2e27d Mon Sep 17 00:00:00 2001 From: allan716 <525223688@qq.com> Date: Sat, 19 Oct 2024 11:21:36 +0800 Subject: [PATCH 1/4] support 24 bit image read from clipboard Signed-off-by: allan716 <525223688@qq.com> --- clipboard_windows.go | 73 ++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/clipboard_windows.go b/clipboard_windows.go index bd042cd..33a01a9 100644 --- a/clipboard_windows.go +++ b/clipboard_windows.go @@ -123,33 +123,60 @@ func readImage() ([]byte, error) { info := (*bitmapV5Header)(unsafe.Pointer(p)) // maybe deal with other formats? - if info.BitCount != 32 { - return nil, errUnsupported - } - - var data []byte - sh := (*reflect.SliceHeader)(unsafe.Pointer(&data)) - sh.Data = uintptr(p) - sh.Cap = int(info.Size + 4*uint32(info.Width)*uint32(info.Height)) - sh.Len = int(info.Size + 4*uint32(info.Width)*uint32(info.Height)) - img := image.NewRGBA(image.Rect(0, 0, int(info.Width), int(info.Height))) - offset := int(info.Size) - stride := int(info.Width) - for y := 0; y < int(info.Height); y++ { - for x := 0; x < int(info.Width); x++ { - idx := offset + 4*(y*stride+x) - xhat := (x + int(info.Width)) % int(info.Width) - yhat := int(info.Height) - 1 - y - r := data[idx+2] - g := data[idx+1] - b := data[idx+0] - a := data[idx+3] - img.SetRGBA(xhat, yhat, color.RGBA{r, g, b, a}) + var img *image.RGBA + switch info.BitCount { + case 32: + // existing 32-bit handling code + var data []byte + sh := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + sh.Data = uintptr(p) + sh.Cap = int(info.Size + 4*uint32(info.Width)*uint32(info.Height)) + sh.Len = int(info.Size + 4*uint32(info.Width)*uint32(info.Height)) + img = image.NewRGBA(image.Rect(0, 0, int(info.Width), int(info.Height))) + offset := int(info.Size) + stride := int(info.Width) + for y := 0; y < int(info.Height); y++ { + for x := 0; x < int(info.Width); x++ { + idx := offset + 4*(y*stride+x) + xhat := (x + int(info.Width)) % int(info.Width) + yhat := int(info.Height) - 1 - y + r := data[idx+2] + g := data[idx+1] + b := data[idx+0] + a := data[idx+3] + img.SetRGBA(xhat, yhat, color.RGBA{r, g, b, a}) + } + } + case 24: + // new 24-bit handling code + var data []byte + sh := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + sh.Data = uintptr(p) + sh.Cap = int(info.Size + 3*uint32(info.Width)*uint32(info.Height)) + sh.Len = int(info.Size + 3*uint32(info.Width)*uint32(info.Height)) + img = image.NewRGBA(image.Rect(0, 0, int(info.Width), int(info.Height))) + offset := int(info.Size) + stride := (int(info.Width)*3 + 3) &^ 3 // ensure stride is a multiple of 4 + for y := 0; y < int(info.Height); y++ { + for x := 0; x < int(info.Width); x++ { + idx := offset + y*stride + x*3 + xhat := (x + int(info.Width)) % int(info.Width) + yhat := int(info.Height) - 1 - y + r := data[idx+2] + g := data[idx+1] + b := data[idx+0] + img.SetRGBA(xhat, yhat, color.RGBA{r, g, b, 255}) + } } + default: + return nil, errUnsupported } // always use PNG encoding. var buf bytes.Buffer - png.Encode(&buf, img) + err = png.Encode(&buf, img) + if err != nil { + return nil, err + } return buf.Bytes(), nil } From c0e99c8bc318ac6844f5cfa3960ab81ca562bb97 Mon Sep 17 00:00:00 2001 From: allan716 <525223688@qq.com> Date: Sat, 19 Oct 2024 12:39:42 +0800 Subject: [PATCH 2/4] fixes out-of-bounds and white space issues Signed-off-by: allan716 <525223688@qq.com> --- clipboard_windows.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/clipboard_windows.go b/clipboard_windows.go index 33a01a9..81dc1f9 100644 --- a/clipboard_windows.go +++ b/clipboard_windows.go @@ -148,24 +148,25 @@ func readImage() ([]byte, error) { } } case 24: - // new 24-bit handling code + // updated 24-bit handling code var data []byte sh := (*reflect.SliceHeader)(unsafe.Pointer(&data)) sh.Data = uintptr(p) - sh.Cap = int(info.Size + 3*uint32(info.Width)*uint32(info.Height)) - sh.Len = int(info.Size + 3*uint32(info.Width)*uint32(info.Height)) + stride := (int(info.Width)*3 + 3) &^ 3 // ensure stride is a multiple of 4 + dataSize := int(info.Size) + stride*int(info.Height) + sh.Cap = dataSize + sh.Len = dataSize img = image.NewRGBA(image.Rect(0, 0, int(info.Width), int(info.Height))) offset := int(info.Size) - stride := (int(info.Width)*3 + 3) &^ 3 // ensure stride is a multiple of 4 for y := 0; y < int(info.Height); y++ { for x := 0; x < int(info.Width); x++ { idx := offset + y*stride + x*3 - xhat := (x + int(info.Width)) % int(info.Width) - yhat := int(info.Height) - 1 - y - r := data[idx+2] - g := data[idx+1] - b := data[idx+0] - img.SetRGBA(xhat, yhat, color.RGBA{r, g, b, 255}) + if idx+2 < len(data) { + r := data[idx+2] + g := data[idx+1] + b := data[idx+0] + img.SetRGBA(x, int(info.Height)-1-y, color.RGBA{r, g, b, 255}) + } } } default: From 11adc7e093d0d05f17f1785bc612596250050cc4 Mon Sep 17 00:00:00 2001 From: allan716 <525223688@qq.com> Date: Sat, 19 Oct 2024 13:36:40 +0800 Subject: [PATCH 3/4] Implement the ability to read absolute paths of files and folders via Read and Watch when copying files or folders on the windows side. Signed-off-by: allan716 <525223688@qq.com> --- clipboard.go | 14 +++++++------ clipboard_test.go | 31 ++++++++++++++++++++++++++++ clipboard_windows.go | 48 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/clipboard.go b/clipboard.go index b00f415..5d19869 100644 --- a/clipboard.go +++ b/clipboard.go @@ -79,14 +79,16 @@ const ( FmtText Format = iota // FmtImage indicates image/png clipboard format FmtImage + // FmtHDrop indicates system object clipboard format,can be a file or a directory + FmtHDrop ) var ( // Due to the limitation on operating systems (such as darwin), // concurrent read can even cause panic, use a global lock to // guarantee one read at a time. - lock = sync.Mutex{} - initOnce sync.Once + lock = sync.Mutex{} + initOnce sync.Once initError error ) @@ -95,10 +97,10 @@ var ( // target system lacks required dependency, such as libx11-dev in X11 // environment. For example, // -// err := clipboard.Init() -// if err != nil { -// panic(err) -// } +// err := clipboard.Init() +// if err != nil { +// panic(err) +// } // // If Init returns an error, any subsequent Read/Write/Watch call // may result in an unrecoverable panic. diff --git a/clipboard_test.go b/clipboard_test.go index 8d47568..34ae258 100644 --- a/clipboard_test.go +++ b/clipboard_test.go @@ -10,6 +10,7 @@ import ( "bytes" "context" "errors" + "fmt" "image/color" "image/png" "os" @@ -339,3 +340,33 @@ func TestClipboardNoCgo(t *testing.T) { clipboard.Watch(context.TODO(), clipboard.FmtText) }) } + +func TestReadCopyFilePath(t *testing.T) { + err := clipboard.Init() + if err != nil { + t.Fatal(err) + } + + go func() { + chText := clipboard.Watch(context.Background(), clipboard.FmtText) + for data := range chText { + fmt.Println("Text:", string(data)) + } + }() + + go func() { + chText := clipboard.Watch(context.Background(), clipboard.FmtImage) + for data := range chText { + fmt.Println("Image:", len(data)) + } + }() + + go func() { + chText := clipboard.Watch(context.Background(), clipboard.FmtHDrop) + for data := range chText { + fmt.Println("File or Dir Path:", string(data)) + } + }() + + select {} +} diff --git a/clipboard_windows.go b/clipboard_windows.go index 81dc1f9..7001aea 100644 --- a/clipboard_windows.go +++ b/clipboard_windows.go @@ -323,6 +323,34 @@ func writeImage(buf []byte) error { return nil } +// readFilePath reads the clipboard and returns the file path if a file or folder is copied. +// The caller is responsible for opening/closing the clipboard before calling this function. +func readFilePath() ([]byte, error) { + hMem, _, err := getClipboardData.Call(uintptr(cFmtHDrop)) + if hMem == 0 { + return nil, err + } + + pMem, _, err := gLock.Call(hMem) + if pMem == 0 { + return nil, err + } + defer gLock.Call(hMem) + + // Get the number of files + nFiles, _, _ := dragQueryFile.Call(pMem, 0xFFFFFFFF, 0, 0) + if nFiles == 0 { + return nil, syscall.EINVAL + } + + // We're only interested in the first file + bufLen, _, _ := dragQueryFile.Call(pMem, 0, 0, 0) + buf := make([]uint16, bufLen+1) + dragQueryFile.Call(pMem, 0, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf))) + + return []byte(syscall.UTF16ToString(buf)), nil +} + func read(t Format) (buf []byte, err error) { // On Windows, OpenClipboard and CloseClipboard must be executed on // the same thread. Thus, lock the OS thread for further execution. @@ -332,10 +360,16 @@ func read(t Format) (buf []byte, err error) { var format uintptr switch t { case FmtImage: + format = cFmtDIBV5 case FmtText: - fallthrough + + format = cFmtUnicodeText + case FmtHDrop: + + format = cFmtHDrop default: + format = cFmtUnicodeText } @@ -357,10 +391,16 @@ func read(t Format) (buf []byte, err error) { switch format { case cFmtDIBV5: + return readImage() case cFmtUnicodeText: - fallthrough + + return readText() + case cFmtHDrop: + + return readFilePath() default: + return readText() } } @@ -459,6 +499,7 @@ func watch(ctx context.Context, t Format) <-chan []byte { const ( cFmtBitmap = 2 // Win+PrintScreen cFmtUnicodeText = 13 + cFmtHDrop = 15 // CF_HDROP cFmtDIBV5 = 17 // Screenshot taken from special shortcut is in different format (why??), see: // https://jpsoft.com/forums/threads/detecting-clipboard-format.5225/ @@ -576,4 +617,7 @@ var ( // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalfree gFree = kernel32.NewProc("GlobalFree") memMove = kernel32.NewProc("RtlMoveMemory") + + shell32 = syscall.NewLazyDLL("shell32.dll") + dragQueryFile = shell32.NewProc("DragQueryFileW") ) From 89fa0059f436601185d0d5c0c6379ca49dfaffe4 Mon Sep 17 00:00:00 2001 From: allan716 <525223688@qq.com> Date: Sat, 19 Oct 2024 13:39:05 +0800 Subject: [PATCH 4/4] del test code Signed-off-by: allan716 <525223688@qq.com> --- clipboard_test.go | 55 +++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/clipboard_test.go b/clipboard_test.go index 34ae258..f735b25 100644 --- a/clipboard_test.go +++ b/clipboard_test.go @@ -10,7 +10,6 @@ import ( "bytes" "context" "errors" - "fmt" "image/color" "image/png" "os" @@ -342,31 +341,31 @@ func TestClipboardNoCgo(t *testing.T) { } func TestReadCopyFilePath(t *testing.T) { - err := clipboard.Init() - if err != nil { - t.Fatal(err) - } - - go func() { - chText := clipboard.Watch(context.Background(), clipboard.FmtText) - for data := range chText { - fmt.Println("Text:", string(data)) - } - }() - - go func() { - chText := clipboard.Watch(context.Background(), clipboard.FmtImage) - for data := range chText { - fmt.Println("Image:", len(data)) - } - }() - - go func() { - chText := clipboard.Watch(context.Background(), clipboard.FmtHDrop) - for data := range chText { - fmt.Println("File or Dir Path:", string(data)) - } - }() - - select {} + //err := clipboard.Init() + //if err != nil { + // t.Fatal(err) + //} + // + //go func() { + // chText := clipboard.Watch(context.Background(), clipboard.FmtText) + // for data := range chText { + // fmt.Println("Text:", string(data)) + // } + //}() + // + //go func() { + // chText := clipboard.Watch(context.Background(), clipboard.FmtImage) + // for data := range chText { + // fmt.Println("Image:", len(data)) + // } + //}() + // + //go func() { + // chText := clipboard.Watch(context.Background(), clipboard.FmtHDrop) + // for data := range chText { + // fmt.Println("File or Dir Path:", string(data)) + // } + //}() + // + //select {} }