You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
clipboard/writehandle_windows.go

305 lines
8.1 KiB
Go

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
}