285 lines
5.8 KiB
Go
285 lines
5.8 KiB
Go
package clipboard
|
|
|
|
import (
|
|
"b612.me/win32api"
|
|
"errors"
|
|
"fmt"
|
|
"runtime"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
clipboardWorker chan func()
|
|
workerReady sync.WaitGroup
|
|
)
|
|
|
|
func init() {
|
|
// 创建专门的工作线程来处理剪贴板操作
|
|
clipboardWorker = make(chan func(), 10)
|
|
go func() {
|
|
runtime.LockOSThread() // 永久锁定这个线程
|
|
for fn := range clipboardWorker {
|
|
fn()
|
|
}
|
|
}()
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
// 公开的Get和GetMeta函数
|
|
func Get() (Clipboard, error) {
|
|
return innerGetClipboard(true)
|
|
}
|
|
|
|
func GetMeta() (Clipboard, error) {
|
|
return innerGetClipboard(false)
|
|
}
|
|
|
|
func innerGetClipboard(withFetch bool) (Clipboard, error) {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
|
|
c := Clipboard{
|
|
platform: "windows",
|
|
date: time.Now(),
|
|
}
|
|
|
|
err := win32api.OpenClipboard(0)
|
|
if err != nil {
|
|
return c, fmt.Errorf("OpenClipboard error: %v", err)
|
|
}
|
|
defer win32api.CloseClipboard()
|
|
|
|
formats, err := win32api.GetUpdatedClipboardFormatsAll()
|
|
if err != nil {
|
|
return c, fmt.Errorf("GetUpdatedClipboardFormatsAll error: %v", err)
|
|
}
|
|
|
|
type formatEntry struct {
|
|
name string
|
|
rank int
|
|
}
|
|
|
|
var rankedFormats []formatEntry
|
|
for _, format := range formats {
|
|
var name string
|
|
var ok bool
|
|
if name, ok = winformat[format]; !ok {
|
|
name, err = win32api.GetClipboardFormatName(format)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
}
|
|
c.winOriginTypes = append(c.winOriginTypes, name)
|
|
|
|
rank := formatRank[name]
|
|
if rank > 0 {
|
|
rankedFormats = append(rankedFormats, formatEntry{name: name, rank: rank})
|
|
}
|
|
}
|
|
|
|
sort.Slice(rankedFormats, func(i, j int) bool {
|
|
return rankedFormats[i].rank < rankedFormats[j].rank
|
|
})
|
|
|
|
var primaryName, secondaryName string
|
|
if len(rankedFormats) > 0 {
|
|
primaryName = rankedFormats[0].name
|
|
}
|
|
if len(rankedFormats) > 1 {
|
|
secondaryName = rankedFormats[1].name
|
|
}
|
|
|
|
if primaryName == "" {
|
|
return c, errors.New("no supported primary format found in clipboard")
|
|
}
|
|
|
|
err = setClipTypeAndData(&c, primaryName, true, withFetch)
|
|
if err != nil {
|
|
return c, err
|
|
}
|
|
|
|
if secondaryName != "" {
|
|
err = setClipTypeAndData(&c, secondaryName, false, withFetch)
|
|
if err != nil {
|
|
return c, err
|
|
}
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func getClipboardWithRetry() (Clipboard, error) {
|
|
return innerGetClipboardSafe(true)
|
|
}
|
|
|
|
func getMetaWithRetry() (Clipboard, error) {
|
|
return innerGetClipboardSafe(false)
|
|
}
|
|
|
|
// 安全的剪贴板读取(已在工作线程中)
|
|
func innerGetClipboardSafe(withFetch bool) (Clipboard, error) {
|
|
c := Clipboard{
|
|
platform: "windows",
|
|
date: time.Now(),
|
|
}
|
|
|
|
// 尝试打开剪贴板,带超时
|
|
var err error
|
|
for i := 0; i < 5; i++ {
|
|
err = win32api.OpenClipboard(0)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(5 * time.Millisecond)
|
|
}
|
|
if err != nil {
|
|
return c, fmt.Errorf("OpenClipboard error after retries: %v", err)
|
|
}
|
|
defer win32api.CloseClipboard()
|
|
|
|
// 快速获取格式
|
|
formats, err := win32api.GetUpdatedClipboardFormatsAll()
|
|
if err != nil {
|
|
return c, fmt.Errorf("GetUpdatedClipboardFormatsAll error: %v", err)
|
|
}
|
|
|
|
type formatEntry struct {
|
|
name string
|
|
rank int
|
|
}
|
|
|
|
var rankedFormats []formatEntry
|
|
for _, format := range formats {
|
|
var name string
|
|
var ok bool
|
|
if name, ok = winformat[format]; !ok {
|
|
name, _ = win32api.GetClipboardFormatName(format)
|
|
if name == "" {
|
|
continue
|
|
}
|
|
}
|
|
c.winOriginTypes = append(c.winOriginTypes, name)
|
|
|
|
rank := formatRank[name]
|
|
if rank > 0 {
|
|
rankedFormats = append(rankedFormats, formatEntry{name: name, rank: rank})
|
|
}
|
|
}
|
|
|
|
sort.Slice(rankedFormats, func(i, j int) bool {
|
|
return rankedFormats[i].rank < rankedFormats[j].rank
|
|
})
|
|
|
|
var primaryName, secondaryName string
|
|
if len(rankedFormats) > 0 {
|
|
primaryName = rankedFormats[0].name
|
|
}
|
|
if len(rankedFormats) > 1 {
|
|
secondaryName = rankedFormats[1].name
|
|
}
|
|
|
|
if primaryName == "" {
|
|
return c, errors.New("no supported primary format found")
|
|
}
|
|
|
|
err = setClipTypeAndData(&c, primaryName, true, withFetch)
|
|
if err != nil {
|
|
return c, err
|
|
}
|
|
|
|
if secondaryName != "" {
|
|
setClipTypeAndData(&c, secondaryName, false, withFetch)
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func setClipTypeAndData(c *Clipboard, formatName string, isPrimary bool, withFetch bool) error {
|
|
var typ FileType
|
|
switch formatName {
|
|
case "CF_UNICODETEXT":
|
|
typ = Text
|
|
case "HTML format":
|
|
typ = HTML
|
|
case "PNG", "CF_DIBV5", "CF_DIB":
|
|
typ = Image
|
|
case "CF_HDROP":
|
|
typ = File
|
|
default:
|
|
return fmt.Errorf("unsupported format: %s", formatName)
|
|
}
|
|
|
|
if isPrimary {
|
|
c.primaryOriType = formatName
|
|
c.primaryType = typ
|
|
} else {
|
|
c.secondaryOriType = formatName
|
|
c.secondaryType = typ
|
|
}
|
|
|
|
if withFetch {
|
|
tmpData, err := AutoFetcher(formatName)
|
|
if err != nil {
|
|
return fmt.Errorf("AutoFetcher error: %v", err)
|
|
}
|
|
data, ok := tmpData.([]byte)
|
|
if !ok {
|
|
return fmt.Errorf("unexpected data type from AutoFetcher: %T", tmpData)
|
|
}
|
|
if isPrimary {
|
|
c.primaryData = data
|
|
c.primarySize = len(data)
|
|
} else {
|
|
c.secondaryData = data
|
|
c.secondarySize = len(data)
|
|
}
|
|
} else {
|
|
size, err := ClipSize(formatName)
|
|
if err != nil {
|
|
return fmt.Errorf("ClipSize error: %v", err)
|
|
}
|
|
if isPrimary {
|
|
c.primarySize = size
|
|
} else {
|
|
c.secondarySize = size
|
|
}
|
|
}
|
|
return nil
|
|
}
|