master
兔子 1 month ago
commit 26e0bf5bb5

10
.idea/.gitignore vendored

@ -0,0 +1,10 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# GitHub Copilot persisted chat sessions
/copilot/chatSessions

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.idea/copilot/chatSessions" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/clipboard.iml" filepath="$PROJECT_DIR$/.idea/clipboard.iml" />
</modules>
</component>
</project>

@ -0,0 +1,112 @@
package clipboard
import (
"strings"
"time"
)
type FileType string
const (
Text FileType = "text"
File FileType = "file"
Image FileType = "image"
HTML FileType = "html"
)
type Clipboard struct {
winOriginTypes []string
plateform string
date time.Time
secondaryOriType string
secondaryType FileType
secondaryData []byte
primaryOriType string
primaryType FileType
primaryData []byte
hash string
}
func (c *Clipboard) PrimaryType() FileType {
return c.primaryType
}
func (c *Clipboard) AvailableTypes() []FileType {
var res = make([]FileType, 0, 2)
if c.primaryType != "" {
res = append(res, c.primaryType)
}
if c.secondaryType != "" {
res = append(res, c.secondaryType)
}
return res
}
func (c *Clipboard) IsText() bool {
return c.primaryType == Text || c.secondaryType == Text
}
func (c *Clipboard) Text() string {
if c.primaryType == Text {
return string(c.primaryData)
}
if c.secondaryType == Text {
return string(c.secondaryData)
}
return ""
}
func (c *Clipboard) IsHTML() bool {
return (c.primaryType == HTML || c.secondaryType == HTML) || c.IsText()
}
func (c *Clipboard) HTML() string {
var htmlBytes []byte
if c.primaryType == HTML {
htmlBytes = c.primaryData
} else if c.secondaryType == HTML {
htmlBytes = c.secondaryData
} else {
return c.Text()
}
formats := strings.SplitN(string(htmlBytes), "\n", 7)
if len(formats) < 7 {
return string(htmlBytes)
}
return formats[6]
}
func (c *Clipboard) FilePaths() []string {
if c.primaryType == File {
return strings.Split(string(c.primaryData), "|")
}
if c.secondaryType == File {
return strings.Split(string(c.secondaryData), "|")
}
return nil
}
func (c *Clipboard) FirstFilePath() string {
if c.primaryType == File {
return strings.Split(string(c.primaryData), "|")[0]
}
if c.secondaryType == File {
return strings.Split(string(c.secondaryData), "|")[0]
}
return ""
}
func (c *Clipboard) Image() []byte {
if c.primaryType == Image {
return c.primaryData
}
if c.secondaryType == Image {
return c.secondaryData
}
return nil
}
func (c *Clipboard) IsImage() bool {
return c.primaryType == Image || c.secondaryType == Image
}

@ -0,0 +1,20 @@
package clipboard
import (
"fmt"
"testing"
)
func TestGet(t *testing.T) {
c, err := Get()
if err != nil {
t.Error(err)
}
fmt.Println(c.plateform)
fmt.Println(c.winOriginTypes)
fmt.Println(c.PrimaryType())
fmt.Println(c.AvailableTypes())
fmt.Println(c.Text())
fmt.Println(c.HTML())
fmt.Println(c.FilePaths())
}

@ -0,0 +1,132 @@
package clipboard
import (
"b612.me/win32api"
)
var winformat = map[win32api.DWORD]string{
1: "CF_TEXT",
2: "CF_BITMAP",
3: "CF_METAFILEPICT",
4: "CF_SYLK",
5: "CF_DIF",
6: "CF_TIFF",
7: "CF_OEMTEXT",
8: "CF_DIB",
9: "CF_PALETTE",
10: "CF_PENDATA",
11: "CF_RIFF",
12: "CF_WAVE",
13: "CF_UNICODETEXT",
14: "CF_ENHMETAFILE",
15: "CF_HDROP",
16: "CF_LOCALE",
17: "CF_DIBV5",
130: "CF_DSPBITMAP",
129: "CF_DSPTEXT",
131: "CF_DSPMETAFILEPICT",
142: "CF_DSPENHMETAFILE",
0x03FF: "CF_GDIOBJLAST",
0x0200: "CF_PRIVATEFIRST",
}
var formatRank = map[string]int{
"CF_UNICODETEXT": 1,
"CF_DIBV5": 2,
//"CF_DIB": 2,
"PNG": 2,
"HTML Format": 3,
"CF_HDROP": 4,
}
func Get() (Clipboard, error) {
return innerGetClipboard()
}
func innerGetClipboard() (Clipboard, error) {
var c = Clipboard{
plateform: "windows",
}
err := win32api.OpenClipboard(0)
if err != nil {
return c, err
}
defer win32api.CloseClipboard()
formats, err := win32api.GetUpdatedClipboardFormatsAll()
if err != nil {
return c, err
}
var firstFormatName, secondFormatName string
var firstFormat, secondFormat int = 65535, 65535
for _, format := range formats {
if d, ok := winformat[format]; ok {
if formatRank[d] > 0 {
if formatRank[d] < firstFormat {
secondFormat = firstFormat
secondFormatName = firstFormatName
firstFormat = formatRank[d]
firstFormatName = d
} else if formatRank[d] != firstFormat && formatRank[d] < secondFormat {
secondFormat = formatRank[d]
secondFormatName = d
}
}
c.winOriginTypes = append(c.winOriginTypes, d)
continue
}
d, e := win32api.GetClipboardFormatName(format)
if e != nil {
continue
}
if formatRank[d] > 0 {
if formatRank[d] < firstFormat {
secondFormat = firstFormat
secondFormatName = firstFormatName
firstFormat = formatRank[d]
firstFormatName = d
} else if formatRank[d] != firstFormat && formatRank[d] < secondFormat {
secondFormat = formatRank[d]
secondFormatName = d
}
}
c.winOriginTypes = append(c.winOriginTypes, d)
}
c.primaryOriType = firstFormatName
switch c.primaryOriType {
case "CF_UNICODETEXT":
c.primaryType = Text
case "HTML Format":
c.primaryType = HTML
case "PNG", "CF_DIBV5", "CF_DIB":
c.primaryType = Image
case "CF_HDROP":
c.primaryType = File
}
c.primaryData, err = AutoFetcher(firstFormatName)
if err != nil {
return c, err
}
if secondFormatName != "" {
switch secondFormatName {
case "CF_UNICODETEXT":
c.secondaryType = Text
case "HTML Format":
c.secondaryType = HTML
case "PNG", "CF_DIBV5", "CF_DIB":
c.secondaryType = Image
case "CF_HDROP":
c.secondaryType = File
}
c.secondaryOriType = secondFormatName
c.secondaryData, err = AutoFetcher(secondFormatName)
if err != nil {
return c, err
}
}
return c, nil
}
func GetTopMatch() {
}

@ -0,0 +1,10 @@
module b612.me/clipboard
go 1.21.2
require (
b612.me/win32api v0.0.0-20240326080749-ad19f5cd4247
golang.org/x/image v0.15.0
)
require golang.org/x/sys v0.18.0 // indirect

@ -0,0 +1,6 @@
b612.me/win32api v0.0.0-20240326080749-ad19f5cd4247 h1:fDTZ1HzVtVpEcXqlsQB9O2AbtrbqiAruaRX1Yd7M9Z8=
b612.me/win32api v0.0.0-20240326080749-ad19f5cd4247/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=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

@ -0,0 +1,215 @@
package clipboard
import (
"b612.me/win32api"
"bytes"
"encoding/binary"
"errors"
"golang.org/x/image/bmp"
"image"
"image/color"
"image/png"
"reflect"
"strings"
"syscall"
"unsafe"
)
func fetchClipboardData(uFormat win32api.DWORD, fn func(p unsafe.Pointer, size uint32) ([]byte, error)) ([]byte, error) {
mem, err := win32api.GetClipboardData(uFormat)
if err != nil {
return nil, err
}
p, err := win32api.GlobalLock(mem)
if err != nil {
return nil, err
}
defer win32api.GlobalUnlock(mem)
size, err := win32api.GlobalSize(mem)
if err != nil {
return nil, err
}
if fn == nil {
return defaultFetchFn(p, uint32(size))
}
return fn(p, uint32(size))
}
func defaultFetchFn(p unsafe.Pointer, size uint32) ([]byte, error) {
var buf []byte
for i := 0; i < int(size); i++ {
buf = append(buf, *(*byte)(unsafe.Pointer(uintptr(p) + uintptr(i))))
}
return buf, nil
}
func AutoFetcher(uFormat string) ([]byte, error) {
switch uFormat {
case "CF_TEXT", "CF_UNICODETEXT":
return fetchClipboardData(win32api.CF_UNICODETEXT, textFetcher)
case "HTML Format":
return fetchClipboardData(win32api.RegisterClipboardFormat("HTML Format"), nil)
case "CF_HDROP":
return fetchClipboardData(win32api.CF_HDROP, filedropFetcher)
case "CF_DIBV5":
return fetchClipboardData(win32api.CF_DIBV5, cfDIBv5Fetcher)
case "CF_DIB":
return fetchClipboardData(win32api.CF_DIB, cfDIBFetcher)
case "PNG":
return fetchClipboardData(win32api.RegisterClipboardFormat("PNG"), nil)
}
return nil, errors.New("not support uFormat:" + uFormat)
}
func textFetcher(p unsafe.Pointer, size uint32) ([]byte, 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))
}
return []byte(syscall.UTF16ToString(buf)), nil
}
func filedropFetcher(p unsafe.Pointer, size uint32) ([]byte, error) {
var res []string
c, err := win32api.DragQueryFile(win32api.HDROP(p), 0xFFFFFFFF, nil, 0)
if err != nil {
return nil, err
}
count := int(c)
for i := 0; i < count; i++ {
c, err = win32api.DragQueryFile(win32api.HDROP(p), win32api.DWORD(i), nil, 0)
if err != nil {
return nil, err
}
length := int(c)
buffer := make([]uint16, length+1)
_, err = win32api.DragQueryFile(win32api.HDROP(p), win32api.DWORD(i),
&buffer[0], win32api.DWORD(len(buffer)))
res = append(res, syscall.UTF16ToString(buffer))
}
return []byte(strings.Join(res, "|")), nil
}
func cfDIBv5Fetcher(p unsafe.Pointer, size uint32) ([]byte, error) {
// inspect header information
info := (*bitmapV5Header)(unsafe.Pointer(p))
// maybe deal with other formats?
if info.BitCount != 32 {
return nil, errors.New("not support image format")
}
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})
}
}
// always use PNG encoding.
var buf bytes.Buffer
err := png.Encode(&buf, img)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func cfDIBFetcher(p unsafe.Pointer, size uint32) ([]byte, error) {
// 函数意外报错,待修正
const (
fileHeaderLen = 14
infoHeaderLen = 40
)
bmpHeader := (*bitmapHeader)(p)
dataSize := bmpHeader.SizeImage + fileHeaderLen + infoHeaderLen
if bmpHeader.SizeImage == 0 && bmpHeader.Compression == 0 {
iSizeImage := bmpHeader.Height * ((bmpHeader.Width*uint32(bmpHeader.BitCount)/8 + 3) &^ 3)
dataSize += iSizeImage
}
buf := new(bytes.Buffer)
binary.Write(buf, binary.LittleEndian, uint16('B')|(uint16('M')<<8))
binary.Write(buf, binary.LittleEndian, uint32(dataSize))
binary.Write(buf, binary.LittleEndian, uint32(0))
const sizeof_colorbar = 0
binary.Write(buf, binary.LittleEndian, uint32(fileHeaderLen+infoHeaderLen+sizeof_colorbar))
j := 0
for i := fileHeaderLen; i < int(dataSize); i++ {
binary.Write(buf, binary.BigEndian, *(*byte)(unsafe.Pointer(uintptr(p) + uintptr(j))))
j++
}
return bmpToPng(buf)
}
func bmpToPng(bmpBuf *bytes.Buffer) (buf []byte, err error) {
var f bytes.Buffer
original_image, err := bmp.Decode(bmpBuf)
if err != nil {
return nil, err
}
err = png.Encode(&f, original_image)
if err != nil {
return nil, err
}
return f.Bytes(), nil
}
type bitmapV5Header struct {
Size uint32
Width int32
Height int32
Planes uint16
BitCount uint16
Compression uint32
SizeImage uint32
XPelsPerMeter int32
YPelsPerMeter int32
ClrUsed uint32
ClrImportant uint32
RedMask uint32
GreenMask uint32
BlueMask uint32
AlphaMask uint32
CSType uint32
Endpoints struct {
CiexyzRed, CiexyzGreen, CiexyzBlue struct {
CiexyzX, CiexyzY, CiexyzZ int32 // FXPT2DOT30
}
}
GammaRed uint32
GammaGreen uint32
GammaBlue uint32
Intent uint32
ProfileData uint32
ProfileSize uint32
Reserved uint32
}
type bitmapHeader struct {
Size uint32
Width uint32
Height uint32
PLanes uint16
BitCount uint16
Compression uint32
SizeImage uint32
XPelsPerMeter uint32
YPelsPerMeter uint32
ClrUsed uint32
ClrImportant uint32
}

@ -0,0 +1,5 @@
package clipboard
func Listen() (<-chan Clipboard, error)
res := make(chan Clipboard)
}
Loading…
Cancel
Save