//go:build windows package main import ( "runtime" "syscall" "time" "unsafe" "golang.org/x/sys/windows" ) var ( procIsClipboardFormatAvailable = windows.NewLazySystemDLL("user32.dll").NewProc("IsClipboardFormatAvailable") procOpenClipboard = windows.NewLazySystemDLL("user32.dll").NewProc("OpenClipboard") procCloseClipboard = windows.NewLazySystemDLL("user32.dll").NewProc("CloseClipboard") procGetClipboardData = windows.NewLazySystemDLL("user32.dll").NewProc("GetClipboardData") procDragQueryFileW = windows.NewLazySystemDLL("shell32.dll").NewProc("DragQueryFileW") ) // cfHDrop 剪贴板文件列表格式(资源管理器复制文件/文件夹),Win32 稳定 ABI 值 const cfHDrop = 15 // ReadClipboardFiles 读取系统剪贴板中的文件/文件夹路径列表。 // 剪贴板为文本/截图等非文件内容时返回空列表,前端据此回落图片粘贴等原有链路。 func (a *App) ReadClipboardFiles() ([]string, error) { // OpenClipboard 与 CloseClipboard 必须同线程,否则剪贴板死锁 runtime.LockOSThread() defer runtime.UnlockOSThread() if r, _, _ := procIsClipboardFormatAvailable.Call(cfHDrop); r == 0 { return nil, nil } // 剪贴板是全局互斥资源,短重试避开其他进程的瞬时占用 opened := false for i := 0; i < 20; i++ { if r, _, _ := procOpenClipboard.Call(0); r != 0 { opened = true break } time.Sleep(10 * time.Millisecond) } if !opened { return nil, syscall.EBUSY } defer procCloseClipboard.Call() h, _, _ := procGetClipboardData.Call(cfHDrop) if h == 0 { return nil, nil } count, _, _ := procDragQueryFileW.Call(h, 0xFFFFFFFF, 0, 0) if count == 0 { return nil, nil } paths := make([]string, 0, count) for i := uintptr(0); i < count; i++ { n, _, _ := procDragQueryFileW.Call(h, i, 0, 0) if n == 0 { continue } buf := make([]uint16, n+1) procDragQueryFileW.Call(h, i, uintptr(unsafe.Pointer(&buf[0])), n+1) paths = append(paths, syscall.UTF16ToString(buf)) } return paths, nil }