package clipboard import ( "b612.me/win32api" "bytes" "fmt" "image/png" "runtime" "syscall" "unsafe" ) func allocNewMem(size uintptr) (win32api.HGLOBAL, unsafe.Pointer, error) { mem, err := win32api.GlobalAlloc(win32api.GMEM_MOVEABLE, size) if err != nil { return 0, nil, fmt.Errorf("GlobalAlloc failed: %v", err) } p, err := win32api.GlobalLock(mem) if err != nil { return 0, nil, fmt.Errorf("GlobalLock failed: %v", err) } return 0, p, nil } var winformatrev = map[string]win32api.DWORD{ "CF_TEXT": 1, "CF_BITMAP": 2, "CF_METAFILEPICT": 3, "CF_SYLK": 4, "CF_DIF": 5, "CF_TIFF": 6, "CF_OEMTEXT": 7, "CF_DIB": 8, "CF_PALETTE": 9, "CF_PENDATA": 10, "CF_RIFF": 11, "CF_WAVE": 12, "CF_UNICODETEXT": 13, "CF_ENHMETAFILE": 14, "CF_HDROP": 15, "CF_LOCALE": 16, "CF_DIBV5": 17, "CF_DSPBITMAP": 130, "CF_DSPTEXT": 129, "CF_DSPMETAFILEPICT": 131, "CF_DSPENHMETAFILE": 142, "CF_GDIOBJLAST": 0x03FF, "CF_PRIVATEFIRST": 0x0200, } func getFormat(uFormat string) win32api.DWORD { if v, ok := winformatrev[uFormat]; ok { return v } return win32api.RegisterClipboardFormat(uFormat) } func AutoSetter(uFormat string, data interface{}) error { switch uFormat { case "CF_TEXT", "CF_UNICODETEXT", "TEXT": return setClipboardData(win32api.CF_UNICODETEXT, data, textSetFn) case "File": return setClipboardData(win32api.CF_HDROP, data, fileSetFn) case "Image", "PNG": return setClipboardData(0, data, imageSetFn) default: } return setClipboardData(getFormat(uFormat), data, nil) } func setClipboardData(uFormat win32api.DWORD, data interface{}, fn func(uFormat win32api.DWORD, data interface{}) error) error { runtime.LockOSThread() defer runtime.UnlockOSThread() err := win32api.OpenClipboard(0) if err != nil { return fmt.Errorf("OpenClipboard failed: %v", err) } defer win32api.CloseClipboard() err = win32api.EmptyClipboard() if err != nil { return fmt.Errorf("EmptyClipboard failed: %v", err) } if fn == nil { return defaultSetFn(uFormat, data) } return fn(uFormat, data) } func defaultSetFn(uFormat win32api.DWORD, idata interface{}) error { data, ok := idata.([]byte) if !ok { return fmt.Errorf("data is not a byte slice") } mem, p, err := allocNewMem(uintptr(uint32(len(data)))) if err != nil { return err } defer win32api.GlobalUnlock(mem) err = win32api.RtlMoveMemory(p, unsafe.Pointer(&data[0]), uintptr(len(data))) if err != nil { return fmt.Errorf("RtlMoveMemory failed: %v", err) } _, err = win32api.SetClipboardData(uFormat, win32api.HGLOBAL(p)) if err != nil { win32api.GlobalFree(win32api.HGLOBAL(p)) return fmt.Errorf("SetClipboardData failed: %v", err) } return nil } func textSetFn(uFormat win32api.DWORD, idata interface{}) error { data, ok := idata.(string) if !ok { return fmt.Errorf("data is not a byte slice") } str, err := syscall.UTF16FromString(data) if err != nil { return fmt.Errorf("UTF16FromString failed: %v", err) } size := uintptr(len(str) * int(unsafe.Sizeof(str[0]))) mem, p, err := allocNewMem(size) if err != nil { return err } defer win32api.GlobalUnlock(mem) err = win32api.RtlMoveMemory(p, unsafe.Pointer(&str[0]), size) if err != nil { return fmt.Errorf("RtlMoveMemory failed: %v", err) } _, err = win32api.SetClipboardData(uFormat, win32api.HGLOBAL(p)) if err != nil { win32api.GlobalFree(win32api.HGLOBAL(p)) return fmt.Errorf("SetClipboardData failed: %v", err) } return nil } type dropfiles struct { pFiles uint32 // 4 bytes pt point // 2 * 4 = 8 bytes fNC uint32 // 2 byte fWide uint32 // 2 byte } type point struct { X int32 // 4 bytes Y int32 // 4 bytes } func fileSetFn(uFormat win32api.DWORD, idata interface{}) error { data, ok := idata.([]string) if !ok { return fmt.Errorf("data is not a filepath string slice") } var utf16s = make([][]uint16, 0, len(data)) var size uintptr = 20 + 2 for _, d := range data { str, err := syscall.UTF16FromString(d) if err != nil { return fmt.Errorf("UTF16FromString failed: %v", err) } utf16s = append(utf16s, str) size += uintptr((len(str)) * int(unsafe.Sizeof(str[0]))) } { mem, p, err := allocNewMem(size) if err != nil { return err } defer win32api.GlobalUnlock(mem) var fdrop = dropfiles{ pFiles: 20, pt: point{0, 0}, fNC: 0, fWide: 1, } offset := unsafe.Sizeof(fdrop) err = win32api.RtlMoveMemory(unsafe.Pointer(p), unsafe.Pointer(&fdrop), unsafe.Sizeof(fdrop)) if err != nil { return fmt.Errorf("RtlMoveMemory failed: %v", err) } for _, v := range utf16s { size = uintptr(len(v) * int(unsafe.Sizeof(v[0]))) err = win32api.RtlMoveMemory(unsafe.Pointer(uintptr(p)+offset), unsafe.Pointer(&v[0]), size) if err != nil { return fmt.Errorf("RtlMoveMemory failed: %v", err) } offset += size } *(*uint16)(unsafe.Pointer(uintptr(p) + offset)) = 0 _, err = win32api.SetClipboardData(win32api.CF_HDROP, win32api.HGLOBAL(p)) if err != nil { win32api.GlobalFree(win32api.HGLOBAL(p)) return fmt.Errorf("SetClipboardData failed: %v", err) } } { mem, p, err := allocNewMem(4) if err != nil { return err } defer win32api.GlobalUnlock(mem) for idx, v := range []byte{5, 0, 0, 0} { *(*byte)(unsafe.Pointer(uintptr(p) + uintptr(idx))) = v } format := win32api.RegisterClipboardFormat("Preferred DropEffect") _, err = win32api.SetClipboardData(format, win32api.HGLOBAL(p)) if err != nil { win32api.GlobalFree(win32api.HGLOBAL(p)) return fmt.Errorf("SetClipboardData failed: %v", err) } } return nil } func imageSetFn(uFormat win32api.DWORD, idata interface{}) error { data, ok := idata.([]byte) if !ok { return fmt.Errorf("data is not a byte slice") } if len(data) == 0 { return nil } { img, err := png.Decode(bytes.NewReader(data)) if err != nil { return fmt.Errorf("input bytes is not PNG encoded: %w", err) } offset := unsafe.Sizeof(bitmapV5Header{}) width := img.Bounds().Dx() height := img.Bounds().Dy() imageSize := 4 * width * height datas := make([]byte, int(offset)+imageSize) for y := 0; y < height; y++ { for x := 0; x < width; x++ { idx := int(offset) + 4*(y*width+x) r, g, b, a := img.At(x, height-1-y).RGBA() datas[idx+2] = uint8(r) datas[idx+1] = uint8(g) datas[idx+0] = uint8(b) datas[idx+3] = uint8(a) } } info := bitmapV5Header{} info.Size = uint32(offset) info.Width = int32(width) info.Height = int32(height) info.Planes = 1 info.Compression = 0 // BI_RGB info.SizeImage = uint32(4 * info.Width * info.Height) info.RedMask = 0xff0000 // default mask info.GreenMask = 0xff00 info.BlueMask = 0xff info.AlphaMask = 0xff000000 info.BitCount = 32 // we only deal with 32 bpp at the moment. info.CSType = 0x73524742 info.Intent = 4 // LCS_GM_IMAGES infob := make([]byte, int(unsafe.Sizeof(info))) for i, v := range *(*[unsafe.Sizeof(info)]byte)(unsafe.Pointer(&info)) { infob[i] = v } copy(datas[:], infob[:]) size := uintptr(len(datas) * int(unsafe.Sizeof(datas[0]))) mem, p, err := allocNewMem(size) if err != nil { return err } defer win32api.GlobalUnlock(mem) err = win32api.RtlMoveMemory(p, unsafe.Pointer(&datas[0]), size) if err != nil { return fmt.Errorf("RtlMoveMemory failed: %v", err) } _, err = win32api.SetClipboardData(win32api.CF_DIBV5, win32api.HGLOBAL(p)) if err != nil { win32api.GlobalFree(win32api.HGLOBAL(p)) return fmt.Errorf("SetClipboardData failed: %v", err) } } { mem, p, err := allocNewMem(uintptr(len(data) * int(unsafe.Sizeof(data[0])))) if err != nil { return err } defer win32api.GlobalUnlock(mem) err = win32api.RtlMoveMemory(p, unsafe.Pointer(&data[0]), uintptr(len(data)*int(unsafe.Sizeof(data[0])))) if err != nil { return fmt.Errorf("RtlMoveMemory failed: %v", err) } format := win32api.RegisterClipboardFormat("PNG") _, err = win32api.SetClipboardData(format, win32api.HGLOBAL(p)) if err != nil { win32api.GlobalFree(win32api.HGLOBAL(p)) return fmt.Errorf("SetClipboardData failed: %v", err) } } return nil }