init
commit
26e0bf5bb5
@ -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…
Reference in New Issue