mirror of
https://github.com/rubickCenter/rubick
synced 2025-12-17 16:24:16 +08:00
简化复制代码
This commit is contained in:
@@ -28,9 +28,6 @@ import getWinPosition from './getWinPosition';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import commonConst from '@/common/utils/commonConst';
|
import commonConst from '@/common/utils/commonConst';
|
||||||
|
|
||||||
const logCopyFile = (...messages: unknown[]) =>
|
|
||||||
console.log('[copyFile]', ...messages);
|
|
||||||
|
|
||||||
const DROPFILES_HEADER_SIZE = 20;
|
const DROPFILES_HEADER_SIZE = 20;
|
||||||
|
|
||||||
type ClipboardExModule = typeof import('electron-clipboard-ex');
|
type ClipboardExModule = typeof import('electron-clipboard-ex');
|
||||||
@@ -43,24 +40,14 @@ const ensureClipboardEx = (): ClipboardExModule | null => {
|
|||||||
try {
|
try {
|
||||||
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
|
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
|
||||||
clipboardExModule = require('electron-clipboard-ex');
|
clipboardExModule = require('electron-clipboard-ex');
|
||||||
logCopyFile('Loaded electron-clipboard-ex successfully');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logCopyFile('Failed to load electron-clipboard-ex', error);
|
|
||||||
clipboardExModule = null;
|
clipboardExModule = null;
|
||||||
}
|
}
|
||||||
return clipboardExModule;
|
return clipboardExModule;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildWindowsFileListPayload = (files: string[]): Buffer => {
|
const buildWindowsFileListPayload = (files: string[]): Buffer =>
|
||||||
return Buffer.from(`${files.join('\0')}\0\0`, 'utf16le');
|
Buffer.from(`${files.join('\0')}\0\0`, 'utf16le');
|
||||||
};
|
|
||||||
|
|
||||||
const parseWindowsFileListPayload = (payload: Buffer): string[] => {
|
|
||||||
if (!payload?.length) return [];
|
|
||||||
const trimmed = payload.toString('utf16le').replace(/\0+$/, '');
|
|
||||||
if (!trimmed) return [];
|
|
||||||
return trimmed.split('\0').filter(Boolean);
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildWindowsFileDropBuffer = (files: string[]): Buffer => {
|
const buildWindowsFileDropBuffer = (files: string[]): Buffer => {
|
||||||
const payload = buildWindowsFileListPayload(files);
|
const payload = buildWindowsFileListPayload(files);
|
||||||
@@ -81,18 +68,6 @@ const buildWindowsFileDropBuffer = (files: string[]): Buffer => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseWindowsDropBuffer = (buffer: Buffer): string[] => {
|
|
||||||
if (!buffer?.length || buffer.length < DROPFILES_HEADER_SIZE) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const offset = buffer.readUInt32LE(0);
|
|
||||||
if (!offset || offset >= buffer.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const payload = buffer.subarray(offset);
|
|
||||||
return parseWindowsFileListPayload(payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildDropEffectBuffer = (effect: 'copy' | 'move' | 'link' = 'copy') => {
|
const buildDropEffectBuffer = (effect: 'copy' | 'move' | 'link' = 'copy') => {
|
||||||
const effectMap = {
|
const effectMap = {
|
||||||
copy: 1,
|
copy: 1,
|
||||||
@@ -104,41 +79,61 @@ const buildDropEffectBuffer = (effect: 'copy' | 'move' | 'link' = 'copy') => {
|
|||||||
return buffer;
|
return buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
const logAvailableFormats = (type: 'clipboard' | 'selection') => {
|
const sanitizeInputFiles = (input: unknown): string[] => {
|
||||||
|
const candidates = Array.isArray(input)
|
||||||
|
? input
|
||||||
|
: typeof input === 'string'
|
||||||
|
? [input]
|
||||||
|
: [];
|
||||||
|
return candidates
|
||||||
|
.map((filePath) =>
|
||||||
|
typeof filePath === 'string' ? filePath.trim() : ''
|
||||||
|
)
|
||||||
|
.filter((filePath) => {
|
||||||
|
if (!filePath) return false;
|
||||||
try {
|
try {
|
||||||
logCopyFile(`availableFormats(${type})`, clipboard.availableFormats(type));
|
return fs.existsSync(filePath);
|
||||||
} catch (error) {
|
} catch {
|
||||||
logCopyFile(`availableFormats(${type}) read failed`, error);
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeMacClipboardFiles = (files: string[]): boolean => {
|
||||||
|
try {
|
||||||
|
clipboard.writeBuffer(
|
||||||
|
'NSFilenamesPboardType',
|
||||||
|
Buffer.from(plist.build(files))
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const debugWindowsClipboard = (phase = 'afterWrite') => {
|
const writeWindowsClipboardFiles = (files: string[]): boolean => {
|
||||||
logCopyFile('---- clipboard debug phase ----', phase);
|
|
||||||
logAvailableFormats('clipboard');
|
|
||||||
logAvailableFormats('selection');
|
|
||||||
try {
|
try {
|
||||||
const drop = clipboard.readBuffer('CF_HDROP');
|
clipboard.writeBuffer('CF_HDROP', buildWindowsFileDropBuffer(files));
|
||||||
logCopyFile('CF_HDROP length', drop.length);
|
clipboard.writeBuffer('FileNameW', buildWindowsFileListPayload(files));
|
||||||
logCopyFile('CF_HDROP parsed', parseWindowsDropBuffer(drop));
|
clipboard.writeBuffer('Preferred DropEffect', buildDropEffectBuffer('copy'));
|
||||||
} catch (error) {
|
return clipboard.readBuffer('CF_HDROP').length > 0;
|
||||||
logCopyFile('read CF_HDROP failed', error);
|
} catch {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeWithClipboardEx = (files: string[]): boolean => {
|
||||||
|
const clipboardEx = ensureClipboardEx();
|
||||||
|
if (!clipboardEx) return false;
|
||||||
try {
|
try {
|
||||||
const fileNameW = clipboard.readBuffer('FileNameW');
|
clipboardEx.writeFilePaths(files);
|
||||||
logCopyFile('FileNameW length', fileNameW.length);
|
if (typeof clipboardEx.readFilePaths === 'function') {
|
||||||
logCopyFile('FileNameW parsed', parseWindowsFileListPayload(fileNameW));
|
const result = clipboardEx.readFilePaths();
|
||||||
} catch (error) {
|
return Array.isArray(result) && result.length === files.length;
|
||||||
logCopyFile('read FileNameW failed', error);
|
|
||||||
}
|
}
|
||||||
try {
|
return true;
|
||||||
const dropEffect = clipboard.readBuffer('Preferred DropEffect');
|
} catch {
|
||||||
logCopyFile('DropEffect length', dropEffect.length);
|
return false;
|
||||||
logCopyFile(
|
|
||||||
'DropEffect value',
|
|
||||||
dropEffect.length ? dropEffect.readUInt32LE(0) : undefined
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
logCopyFile('read Preferred DropEffect failed', error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -344,111 +339,25 @@ class API extends DBInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public copyFile({ data }) {
|
public copyFile({ data }) {
|
||||||
const input = data?.file;
|
const targetFiles = sanitizeInputFiles(data?.file);
|
||||||
logCopyFile('Input received', { input, platform: process.platform });
|
|
||||||
const candidateFiles = Array.isArray(input)
|
|
||||||
? input
|
|
||||||
: typeof input === 'string'
|
|
||||||
? [input]
|
|
||||||
: [];
|
|
||||||
logCopyFile('Candidate files', candidateFiles);
|
|
||||||
|
|
||||||
const targetFiles = candidateFiles
|
|
||||||
.map((filePath) =>
|
|
||||||
typeof filePath === 'string' ? filePath.trim() : ''
|
|
||||||
)
|
|
||||||
.filter((filePath) => {
|
|
||||||
if (!filePath) return false;
|
|
||||||
try {
|
|
||||||
return fs.existsSync(filePath);
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!targetFiles.length) {
|
if (!targetFiles.length) {
|
||||||
logCopyFile('No valid files detected, abort copy');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
try {
|
return writeMacClipboardFiles(targetFiles);
|
||||||
clipboard.writeBuffer(
|
|
||||||
'NSFilenamesPboardType',
|
|
||||||
Buffer.from(plist.build(targetFiles))
|
|
||||||
);
|
|
||||||
logCopyFile('macOS clipboard write succeeded', targetFiles);
|
|
||||||
} catch (error) {
|
|
||||||
logCopyFile('macOS clipboard write failed', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
const normalizedFiles = targetFiles.map((filePath) => {
|
const normalizedFiles = targetFiles.map((filePath) =>
|
||||||
const normalized = path.normalize(filePath);
|
path.normalize(filePath)
|
||||||
logCopyFile('Normalized path', { original: filePath, normalized });
|
);
|
||||||
return normalized;
|
if (writeWithClipboardEx(normalizedFiles)) {
|
||||||
});
|
|
||||||
|
|
||||||
const clipboardExInstance = ensureClipboardEx();
|
|
||||||
if (clipboardExInstance) {
|
|
||||||
try {
|
|
||||||
const writeResult = clipboardExInstance.writeFilePaths(normalizedFiles);
|
|
||||||
logCopyFile('clipboard-ex write result', writeResult);
|
|
||||||
const readBack = clipboardExInstance.readFilePaths();
|
|
||||||
logCopyFile('clipboard-ex readback', readBack);
|
|
||||||
if (writeResult.length === normalizedFiles.length) {
|
|
||||||
logCopyFile('clipboard-ex copied all files, skipping Electron fallback');
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
logCopyFile(
|
return writeWindowsClipboardFiles(normalizedFiles);
|
||||||
'clipboard-ex copied fewer files than requested, continue with Electron fallback'
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
logCopyFile('clipboard-ex write failed, continue with Electron fallback', error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logCopyFile('clipboard-ex not available, continue with Electron fallback');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
clipboard.writeBuffer(
|
|
||||||
'CF_HDROP',
|
|
||||||
buildWindowsFileDropBuffer(normalizedFiles)
|
|
||||||
);
|
|
||||||
clipboard.writeBuffer(
|
|
||||||
'FileNameW',
|
|
||||||
buildWindowsFileListPayload(normalizedFiles)
|
|
||||||
);
|
|
||||||
clipboard.writeBuffer(
|
|
||||||
'Preferred DropEffect',
|
|
||||||
buildDropEffectBuffer('copy')
|
|
||||||
);
|
|
||||||
logCopyFile('Electron clipboard write finished', normalizedFiles);
|
|
||||||
} catch (error) {
|
|
||||||
logCopyFile('Electron clipboard write failed', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
debugWindowsClipboard('afterElectronWrite');
|
|
||||||
|
|
||||||
let dropBufferLength = 0;
|
|
||||||
try {
|
|
||||||
dropBufferLength = clipboard.readBuffer('CF_HDROP').length;
|
|
||||||
} catch (error) {
|
|
||||||
logCopyFile('Failed to read CF_HDROP after Electron write', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!dropBufferLength) {
|
|
||||||
logCopyFile('CF_HDROP buffer is empty after Electron write, copy failed');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
logCopyFile('CF_HDROP buffer detected after Electron write, copy succeeded');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
logCopyFile('Current platform is not supported yet');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user