master
兔子 1 month ago
parent 568dd318c3
commit 0d790f2f68

@ -1,6 +1,7 @@
package clipboard
import (
"errors"
"strings"
"time"
)
@ -21,13 +22,33 @@ type Clipboard struct {
secondaryOriType string
secondaryType FileType
secondaryData []byte
secondarySize int
primaryOriType string
primaryType FileType
primaryData []byte
primarySize int
hash string
}
func Set(types FileType, data []byte) error {
switch types {
case Text:
return AutoSetter("CF_UNICODETEXT", data)
case File:
return AutoSetter("CF_HDROP", data)
case Image:
return AutoSetter("PNG", data)
case HTML:
return AutoSetter("HTML Format", data)
}
return errors.New("not support type:" + string(types))
}
func SetOrigin(types string, data []byte) error {
return AutoSetter(types, data)
}
func (c *Clipboard) PrimaryType() FileType {
return c.primaryType
}
@ -57,6 +78,16 @@ func (c *Clipboard) Text() string {
return ""
}
func (c *Clipboard) TextSize() int {
if c.primaryType == Text {
return c.primarySize
}
if c.secondaryType == Text {
return c.secondarySize
}
return 0
}
func (c *Clipboard) IsHTML() bool {
return (c.primaryType == HTML || c.secondaryType == HTML) || c.IsText()
}
@ -87,6 +118,10 @@ func (c *Clipboard) FilePaths() []string {
return nil
}
func (c *Clipboard) IsFile() bool {
return c.primaryType == File || c.secondaryType == File
}
func (c *Clipboard) FirstFilePath() string {
if c.primaryType == File {
return strings.Split(string(c.primaryData), "|")[0]
@ -107,6 +142,25 @@ func (c *Clipboard) Image() []byte {
return nil
}
func (c *Clipboard) ImageSize() int {
if c.primaryType == Image {
return c.primarySize
}
if c.secondaryType == Image {
return c.secondarySize
}
return 0
}
func (c *Clipboard) IsImage() bool {
return c.primaryType == Image || c.secondaryType == Image
}
func (c *Clipboard) PrimaryTypeSize() int {
return c.primarySize
}
func (c *Clipboard) SecondaryTypeSize() int {
return c.secondarySize
}

@ -1,13 +1,18 @@
package clipboard
import (
"b612.me/win32api"
"encoding/binary"
"fmt"
"os"
"syscall"
"testing"
"time"
)
func TestGet(t *testing.T) {
lsn, err := Listen()
fmt.Println(win32api.RegisterClipboardFormat("Preferred DropEffect"))
lsn, err := Listen(false)
if err != nil {
t.Fatal(err)
}
@ -15,9 +20,17 @@ func TestGet(t *testing.T) {
select {
case cb := <-lsn:
fmt.Println(cb.plateform)
fmt.Println(cb.winOriginTypes)
fmt.Println(cb.AvailableTypes())
fmt.Println(cb.Text())
fmt.Println(cb.HTML())
if cb.IsText() {
fmt.Println(cb.Text())
}
if cb.IsHTML() {
fmt.Println(cb.HTML())
}
if cb.IsFile() {
fmt.Println(cb.FilePaths())
}
case <-time.After(60 * time.Second):
fmt.Println("not get clipboard data in 60s")
StopListen()
@ -26,3 +39,60 @@ func TestGet(t *testing.T) {
}
}
}
func TestGetMeta(t *testing.T) {
fmt.Println(win32api.RegisterClipboardFormat("Preferred DropEffect"))
lsn, err := Listen(true)
if err != nil {
t.Fatal(err)
}
for {
select {
case cb := <-lsn:
fmt.Println(cb.plateform)
fmt.Println(cb.winOriginTypes)
fmt.Println(cb.AvailableTypes())
fmt.Println(cb.primarySize)
fmt.Println(cb.secondarySize)
case <-time.After(60 * time.Second):
fmt.Println("not get clipboard data in 60s")
StopListen()
time.Sleep(time.Second * 15)
return
}
}
}
func TestAutoSetter(t *testing.T) {
//samp := "天狼星、测试,123.hello.love.what??"
/*
err := AutoSetter("File", []string{"C:\\Users\\Starainrt\\Desktop\\haruhi.jpg"})
if err != nil {
t.Fatal(err)
}
*/
f, err := os.ReadFile("C:\\Users\\Starainrt\\Desktop\\60.png")
if err != nil {
t.Fatal(err)
}
err = AutoSetter("Image", f)
if err != nil {
t.Fatal(err)
}
}
func TestSetTextOrigin(t *testing.T) {
samp := "天狼星、测试,123.hello.love.what"
u, err := syscall.UTF16FromString(samp)
if err != nil {
t.Fatal(err)
}
b := make([]byte, 2*len(u))
for i, v := range u {
binary.LittleEndian.PutUint16(b[i*2:], v)
}
err = setClipboardData(win32api.CF_UNICODETEXT, b, nil)
if err != nil {
t.Fatal(err)
}
}

@ -42,12 +42,17 @@ var formatRank = map[string]int{
}
func Get() (Clipboard, error) {
return innerGetClipboard()
return innerGetClipboard(true)
}
func innerGetClipboard() (Clipboard, error) {
func GetMeta() (Clipboard, error) {
return innerGetClipboard(false)
}
func innerGetClipboard(withFetch bool) (Clipboard, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var tmpData interface{}
var c = Clipboard{
plateform: "windows",
}
@ -107,10 +112,20 @@ func innerGetClipboard() (Clipboard, error) {
case "CF_HDROP":
c.primaryType = File
}
c.primaryData, err = AutoFetcher(firstFormatName)
if err != nil {
return c, fmt.Errorf("AutoFetcher error: %v", err)
if withFetch {
tmpData, err = AutoFetcher(firstFormatName)
if err != nil {
return c, fmt.Errorf("AutoFetcher error: %v", err)
}
c.primaryData = tmpData.([]byte)
c.primarySize = len(c.primaryData)
} else {
c.primarySize, err = ClipSize(firstFormatName)
if err != nil {
return c, fmt.Errorf("ClipSize error: %v", err)
}
}
if secondFormatName != "" {
switch secondFormatName {
case "CF_UNICODETEXT":
@ -123,7 +138,19 @@ func innerGetClipboard() (Clipboard, error) {
c.secondaryType = File
}
c.secondaryOriType = secondFormatName
c.secondaryData, err = AutoFetcher(secondFormatName)
if withFetch {
tmpData, err = AutoFetcher(secondFormatName)
if err != nil {
return c, fmt.Errorf("AutoFetcher error: %v", err)
}
c.secondaryData = tmpData.([]byte)
c.secondarySize = len(c.secondaryData)
} else {
c.secondarySize, err = ClipSize(secondFormatName)
if err != nil {
return c, fmt.Errorf("ClipSize error: %v", err)
}
}
if err != nil {
return c, fmt.Errorf("AutoFetcher error: %v", err)
}

@ -3,7 +3,7 @@ module b612.me/clipboard
go 1.21.2
require (
b612.me/win32api v0.0.0-20240328010943-f10bafb4e804
b612.me/win32api v0.0.0-20240402021613-0959dfb96afa
golang.org/x/image v0.15.0
)

@ -1,7 +1,5 @@
b612.me/win32api v0.0.0-20240326080749-ad19f5cd4247 h1:fDTZ1HzVtVpEcXqlsQB9O2AbtrbqiAruaRX1Yd7M9Z8=
b612.me/win32api v0.0.0-20240326080749-ad19f5cd4247/go.mod h1:sj66sFJDKElEjOR+0YhdSW6b4kq4jsXu4T5/Hnpyot0=
b612.me/win32api v0.0.0-20240328010943-f10bafb4e804 h1:eLeVqlAmljdycU1cP7svO8cY7vklan6mAuSR/BcfHMs=
b612.me/win32api v0.0.0-20240328010943-f10bafb4e804/go.mod h1:sj66sFJDKElEjOR+0YhdSW6b4kq4jsXu4T5/Hnpyot0=
b612.me/win32api v0.0.0-20240402021613-0959dfb96afa h1:BsFIbLbjQqq9Yuh+eWs7JmmXcw2RKerP1NT7X8+GKR4=
b612.me/win32api v0.0.0-20240402021613-0959dfb96afa/go.mod h1:sj66sFJDKElEjOR+0YhdSW6b4kq4jsXu4T5/Hnpyot0=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=

@ -102,7 +102,7 @@ func StopListen() error {
return nil
}
func Listen() (<-chan Clipboard, error) {
func Listen(onlyMeta bool) (<-chan Clipboard, error) {
if atomic.LoadUint32(&isListening) != 0 {
return nil, errors.New("Already listening")
}
@ -127,13 +127,24 @@ func Listen() (<-chan Clipboard, error) {
storeSeq = seq
//fmt.Println("Clipboard updated", seq, storeSeq)
if atomic.LoadUint32(&isListening) == 1 {
cb, err := Get()
if err != nil {
continue
}
if atomic.LoadUint32(&isListening) == 1 {
res <- cb
continue
if !onlyMeta {
cb, err := Get()
if err != nil {
continue
}
if atomic.LoadUint32(&isListening) == 1 {
res <- cb
continue
}
} else {
cb, err := GetMeta()
if err != nil {
continue
}
if atomic.LoadUint32(&isListening) == 1 {
res <- cb
continue
}
}
}
}

@ -16,7 +16,7 @@ import (
"unsafe"
)
func fetchClipboardData(uFormat win32api.DWORD, fn func(p unsafe.Pointer, size uint32) ([]byte, error)) ([]byte, error) {
func fetchClipboardData(uFormat win32api.DWORD, fn func(p unsafe.Pointer, size uint32) (interface{}, error)) (interface{}, error) {
mem, err := win32api.GetClipboardData(uFormat)
if err != nil {
return nil, fmt.Errorf("GetClipboardData failed: %v", err)
@ -36,7 +36,7 @@ func fetchClipboardData(uFormat win32api.DWORD, fn func(p unsafe.Pointer, size u
return fn(p, uint32(size))
}
func defaultFetchFn(p unsafe.Pointer, size uint32) ([]byte, error) {
func defaultFetchFn(p unsafe.Pointer, size uint32) (interface{}, error) {
var buf []byte
for i := 0; i < int(size); i++ {
buf = append(buf, *(*byte)(unsafe.Pointer(uintptr(p) + uintptr(i))))
@ -44,7 +44,7 @@ func defaultFetchFn(p unsafe.Pointer, size uint32) ([]byte, error) {
return buf, nil
}
func AutoFetcher(uFormat string) ([]byte, error) {
func AutoFetcher(uFormat string) (interface{}, error) {
switch uFormat {
case "CF_TEXT", "CF_UNICODETEXT":
return fetchClipboardData(win32api.CF_UNICODETEXT, textFetcher)
@ -62,7 +62,17 @@ func AutoFetcher(uFormat string) ([]byte, error) {
return nil, errors.New("not support uFormat:" + uFormat)
}
func textFetcher(p unsafe.Pointer, size uint32) ([]byte, error) {
func ClipSize(uFormat string) (int, error) {
s, err := fetchClipboardData(getFormat(uFormat), func(p unsafe.Pointer, size uint32) (interface{}, error) {
return int(size), nil
})
if err != nil {
return 0, err
}
return s.(int), nil
}
func textFetcher(p unsafe.Pointer, size uint32) (interface{}, error) {
var buf []uint16
for ptr := unsafe.Pointer(p); *(*uint16)(ptr) != 0; ptr = unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(*((*uint16)(unsafe.Pointer(p))))) {
buf = append(buf, *(*uint16)(ptr))
@ -70,7 +80,7 @@ func textFetcher(p unsafe.Pointer, size uint32) ([]byte, error) {
return []byte(syscall.UTF16ToString(buf)), nil
}
func filedropFetcher(p unsafe.Pointer, size uint32) ([]byte, error) {
func filedropFetcher(p unsafe.Pointer, size uint32) (interface{}, error) {
var res []string
c, err := win32api.DragQueryFile(win32api.HDROP(p), 0xFFFFFFFF, nil, 0)
if err != nil {
@ -93,7 +103,7 @@ func filedropFetcher(p unsafe.Pointer, size uint32) ([]byte, error) {
return []byte(strings.Join(res, "|")), nil
}
func cfDIBv5Fetcher(p unsafe.Pointer, size uint32) ([]byte, error) {
func cfDIBv5Fetcher(p unsafe.Pointer, size uint32) (interface{}, error) {
// inspect header information
info := (*bitmapV5Header)(unsafe.Pointer(p))
// maybe deal with other formats?
@ -130,7 +140,7 @@ func cfDIBv5Fetcher(p unsafe.Pointer, size uint32) ([]byte, error) {
return buf.Bytes(), nil
}
func cfDIBFetcher(p unsafe.Pointer, size uint32) ([]byte, error) {
func cfDIBFetcher(p unsafe.Pointer, size uint32) (interface{}, error) {
// 函数意外报错,待修正
const (
fileHeaderLen = 14

@ -1 +1,304 @@
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
}

Loading…
Cancel
Save