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 }