Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add windows 24 bit image support #65

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions clipboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand All @@ -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.
Expand Down
30 changes: 30 additions & 0 deletions clipboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,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 {}
}
122 changes: 97 additions & 25 deletions clipboard_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,33 +123,61 @@ 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:
// updated 24-bit handling code
var data []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&data))
sh.Data = uintptr(p)
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)
for y := 0; y < int(info.Height); y++ {
for x := 0; x < int(info.Width); x++ {
idx := offset + y*stride + x*3
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:
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
}

Expand Down Expand Up @@ -295,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.
Expand All @@ -304,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
}

Expand All @@ -329,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()
}
}
Expand Down Expand Up @@ -431,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/
Expand Down Expand Up @@ -548,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")
)