clipboard/clipboard_windows.go

285 lines
5.8 KiB
Go
Raw Permalink Normal View History

2024-03-27 11:20:59 +08:00
package clipboard
import (
"b612.me/win32api"
2025-11-10 10:17:06 +08:00
"errors"
2024-03-30 15:07:20 +08:00
"fmt"
"runtime"
2025-11-10 10:17:06 +08:00
"sort"
"sync"
"time"
2024-03-27 11:20:59 +08:00
)
2025-11-10 10:17:06 +08:00
var (
clipboardWorker chan func()
workerReady sync.WaitGroup
)
func init() {
// 创建专门的工作线程来处理剪贴板操作
clipboardWorker = make(chan func(), 10)
go func() {
runtime.LockOSThread() // 永久锁定这个线程
for fn := range clipboardWorker {
fn()
}
}()
}
2024-03-27 11:20:59 +08:00
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,
2025-11-10 10:17:06 +08:00
"HTML format": 3,
2024-03-27 11:20:59 +08:00
"CF_HDROP": 4,
}
2025-11-10 10:17:06 +08:00
// 公开的Get和GetMeta函数
2024-03-27 11:20:59 +08:00
func Get() (Clipboard, error) {
2024-04-02 14:11:59 +08:00
return innerGetClipboard(true)
2024-03-27 11:20:59 +08:00
}
2024-04-02 14:11:59 +08:00
func GetMeta() (Clipboard, error) {
return innerGetClipboard(false)
}
func innerGetClipboard(withFetch bool) (Clipboard, error) {
2024-03-30 15:07:20 +08:00
runtime.LockOSThread()
defer runtime.UnlockOSThread()
2025-11-10 10:17:06 +08:00
c := Clipboard{
platform: "windows",
date: time.Now(),
2024-03-27 11:20:59 +08:00
}
2025-11-10 10:17:06 +08:00
2024-03-27 11:20:59 +08:00
err := win32api.OpenClipboard(0)
if err != nil {
2024-03-30 15:07:20 +08:00
return c, fmt.Errorf("OpenClipboard error: %v", err)
2024-03-27 11:20:59 +08:00
}
defer win32api.CloseClipboard()
2025-11-10 10:17:06 +08:00
2024-03-27 11:20:59 +08:00
formats, err := win32api.GetUpdatedClipboardFormatsAll()
if err != nil {
2024-03-30 15:07:20 +08:00
return c, fmt.Errorf("GetUpdatedClipboardFormatsAll error: %v", err)
2024-03-27 11:20:59 +08:00
}
2025-11-10 10:17:06 +08:00
type formatEntry struct {
name string
rank int
}
var rankedFormats []formatEntry
2024-03-27 11:20:59 +08:00
for _, format := range formats {
2025-11-10 10:17:06 +08:00
var name string
var ok bool
if name, ok = winformat[format]; !ok {
name, err = win32api.GetClipboardFormatName(format)
if err != nil {
continue
2024-03-27 11:20:59 +08:00
}
}
2025-11-10 10:17:06 +08:00
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
2024-03-27 11:20:59 +08:00
}
2025-11-10 10:17:06 +08:00
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
2024-03-27 11:20:59 +08:00
}
}
2025-11-10 10:17:06 +08:00
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)
2024-03-27 11:20:59 +08:00
}
2025-11-10 10:17:06 +08:00
return c, nil
}
func setClipTypeAndData(c *Clipboard, formatName string, isPrimary bool, withFetch bool) error {
var typ FileType
switch formatName {
2024-03-27 11:20:59 +08:00
case "CF_UNICODETEXT":
2025-11-10 10:17:06 +08:00
typ = Text
case "HTML format":
typ = HTML
2024-03-27 11:20:59 +08:00
case "PNG", "CF_DIBV5", "CF_DIB":
2025-11-10 10:17:06 +08:00
typ = Image
2024-03-27 11:20:59 +08:00
case "CF_HDROP":
2025-11-10 10:17:06 +08:00
typ = File
default:
return fmt.Errorf("unsupported format: %s", formatName)
2024-03-27 11:20:59 +08:00
}
2025-11-10 10:17:06 +08:00
if isPrimary {
c.primaryOriType = formatName
c.primaryType = typ
2024-04-02 14:11:59 +08:00
} else {
2025-11-10 10:17:06 +08:00
c.secondaryOriType = formatName
c.secondaryType = typ
2024-03-27 11:20:59 +08:00
}
2024-04-02 14:11:59 +08:00
2025-11-10 10:17:06 +08:00
if withFetch {
tmpData, err := AutoFetcher(formatName)
if err != nil {
return fmt.Errorf("AutoFetcher error: %v", err)
2024-03-27 11:20:59 +08:00
}
2025-11-10 10:17:06 +08:00
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)
2024-04-02 14:11:59 +08:00
} else {
2025-11-10 10:17:06 +08:00
c.secondaryData = data
c.secondarySize = len(data)
2024-04-02 14:11:59 +08:00
}
2025-11-10 10:17:06 +08:00
} else {
size, err := ClipSize(formatName)
2024-03-27 11:20:59 +08:00
if err != nil {
2025-11-10 10:17:06 +08:00
return fmt.Errorf("ClipSize error: %v", err)
}
if isPrimary {
c.primarySize = size
} else {
c.secondarySize = size
2024-03-27 11:20:59 +08:00
}
}
2025-11-10 10:17:06 +08:00
return nil
2024-03-27 11:20:59 +08:00
}