From 9005d070aae4c2e5890b30d8df13ef61cd53624f Mon Sep 17 00:00:00 2001 From: lanxiuyun Date: Thu, 27 Nov 2025 16:01:58 +0800 Subject: [PATCH] =?UTF-8?q?windows=E5=A4=8D=E5=88=B6=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=88=B0=E5=89=AA=E5=88=87=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/common/api.ts | 86 +-------------------------- src/main/common/windowsClipboard.ts | 91 +++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 84 deletions(-) create mode 100644 src/main/common/windowsClipboard.ts diff --git a/src/main/common/api.ts b/src/main/common/api.ts index 6478d33..819fb5b 100644 --- a/src/main/common/api.ts +++ b/src/main/common/api.ts @@ -27,57 +27,7 @@ import DBInstance from './db'; import getWinPosition from './getWinPosition'; import path from 'path'; import commonConst from '@/common/utils/commonConst'; - -const DROPFILES_HEADER_SIZE = 20; - -type ClipboardExModule = typeof import('electron-clipboard-ex'); - -let clipboardExModule: ClipboardExModule | null = null; - -const ensureClipboardEx = (): ClipboardExModule | null => { - if (process.platform !== 'win32') return null; - if (clipboardExModule) return clipboardExModule; - try { - // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires - clipboardExModule = require('electron-clipboard-ex'); - } catch (error) { - clipboardExModule = null; - } - return clipboardExModule; -}; - -const buildWindowsFileListPayload = (files: string[]): Buffer => - Buffer.from(`${files.join('\0')}\0\0`, 'utf16le'); - -const buildWindowsFileDropBuffer = (files: string[]): Buffer => { - const payload = buildWindowsFileListPayload(files); - const header = Buffer.alloc(DROPFILES_HEADER_SIZE); - header.writeUInt32LE(DROPFILES_HEADER_SIZE, 0); // pFiles points to data offset - header.writeInt32LE(0, 4); // pt.x - header.writeInt32LE(0, 8); // pt.y - header.writeUInt32LE(0, 12); // fNC - header.writeUInt32LE(1, 16); // fWide => Unicode paths - - const result = Buffer.alloc(header.length + payload.length); - for (let i = 0; i < header.length; i += 1) { - result[i] = header[i]; - } - for (let i = 0; i < payload.length; i += 1) { - result[header.length + i] = payload[i]; - } - return result; -}; - -const buildDropEffectBuffer = (effect: 'copy' | 'move' | 'link' = 'copy') => { - const effectMap = { - copy: 1, - move: 2, - link: 4, - } as const; - const buffer = Buffer.alloc(4); - buffer.writeUInt32LE(effectMap[effect], 0); - return buffer; -}; +import { copyFilesToWindowsClipboard } from './windowsClipboard'; const sanitizeInputFiles = (input: unknown): string[] => { const candidates = Array.isArray(input) @@ -111,32 +61,6 @@ const writeMacClipboardFiles = (files: string[]): boolean => { } }; -const writeWindowsClipboardFiles = (files: string[]): boolean => { - try { - clipboard.writeBuffer('CF_HDROP', buildWindowsFileDropBuffer(files)); - clipboard.writeBuffer('FileNameW', buildWindowsFileListPayload(files)); - clipboard.writeBuffer('Preferred DropEffect', buildDropEffectBuffer('copy')); - return clipboard.readBuffer('CF_HDROP').length > 0; - } catch { - return false; - } -}; - -const writeWithClipboardEx = (files: string[]): boolean => { - const clipboardEx = ensureClipboardEx(); - if (!clipboardEx) return false; - try { - clipboardEx.writeFilePaths(files); - if (typeof clipboardEx.readFilePaths === 'function') { - const result = clipboardEx.readFilePaths(); - return Array.isArray(result) && result.length === files.length; - } - return true; - } catch { - return false; - } -}; - const runnerInstance = runner(); const detachInstance = detach(); @@ -349,13 +273,7 @@ class API extends DBInstance { } if (process.platform === 'win32') { - const normalizedFiles = targetFiles.map((filePath) => - path.normalize(filePath) - ); - if (writeWithClipboardEx(normalizedFiles)) { - return true; - } - return writeWindowsClipboardFiles(normalizedFiles); + return copyFilesToWindowsClipboard(targetFiles); } return false; diff --git a/src/main/common/windowsClipboard.ts b/src/main/common/windowsClipboard.ts new file mode 100644 index 0000000..c10aac8 --- /dev/null +++ b/src/main/common/windowsClipboard.ts @@ -0,0 +1,91 @@ +import { clipboard } from 'electron'; +import path from 'path'; + +type ClipboardExModule = typeof import('electron-clipboard-ex'); + +const DROPFILES_HEADER_SIZE = 20; + +let clipboardExModule: ClipboardExModule | null = null; + +const ensureClipboardEx = (): ClipboardExModule | null => { + if (process.platform !== 'win32') return null; + if (clipboardExModule) return clipboardExModule; + try { + // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires + clipboardExModule = require('electron-clipboard-ex'); + } catch { + clipboardExModule = null; + } + return clipboardExModule; +}; + +const buildWindowsFileListPayload = (files: string[]): Buffer => + Buffer.from(`${files.join('\0')}\0\0`, 'utf16le'); + +const buildWindowsFileDropBuffer = (files: string[]): Buffer => { + const payload = buildWindowsFileListPayload(files); + const header = Buffer.alloc(DROPFILES_HEADER_SIZE); + header.writeUInt32LE(DROPFILES_HEADER_SIZE, 0); + header.writeInt32LE(0, 4); + header.writeInt32LE(0, 8); + header.writeUInt32LE(0, 12); + header.writeUInt32LE(1, 16); + + const result = Buffer.alloc(header.length + payload.length); + for (let i = 0; i < header.length; i += 1) { + result[i] = header[i]; + } + for (let i = 0; i < payload.length; i += 1) { + result[header.length + i] = payload[i]; + } + return result; +}; + +const buildDropEffectBuffer = (effect: 'copy' | 'move' | 'link' = 'copy') => { + const effectMap = { + copy: 1, + move: 2, + link: 4, + } as const; + const buffer = Buffer.alloc(4); + buffer.writeUInt32LE(effectMap[effect], 0); + return buffer; +}; + +const writeWindowsBuffers = (files: string[]): boolean => { + try { + clipboard.writeBuffer('CF_HDROP', buildWindowsFileDropBuffer(files)); + clipboard.writeBuffer('FileNameW', buildWindowsFileListPayload(files)); + clipboard.writeBuffer('Preferred DropEffect', buildDropEffectBuffer('copy')); + return clipboard.readBuffer('CF_HDROP').length > 0; + } catch { + return false; + } +}; + +const writeWithClipboardEx = (files: string[]): boolean => { + const clipboardEx = ensureClipboardEx(); + if (!clipboardEx) return false; + try { + clipboardEx.writeFilePaths(files); + if (typeof clipboardEx.readFilePaths === 'function') { + const result = clipboardEx.readFilePaths(); + return Array.isArray(result) && result.length === files.length; + } + return true; + } catch { + return false; + } +}; + +export const copyFilesToWindowsClipboard = (files: string[]): boolean => { + const normalizedFiles = files + .map((filePath) => path.normalize(filePath)) + .filter(Boolean); + if (!normalizedFiles.length) return false; + if (writeWithClipboardEx(normalizedFiles)) { + return true; + } + return writeWindowsBuffers(normalizedFiles); +}; +