mirror of
https://github.com/rubickCenter/rubick
synced 2025-12-17 16:24:16 +08:00
windows复制文件到剪切板
This commit is contained in:
@@ -27,57 +27,7 @@ import DBInstance from './db';
|
|||||||
import getWinPosition from './getWinPosition';
|
import getWinPosition from './getWinPosition';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import commonConst from '@/common/utils/commonConst';
|
import commonConst from '@/common/utils/commonConst';
|
||||||
|
import { copyFilesToWindowsClipboard } from './windowsClipboard';
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
const sanitizeInputFiles = (input: unknown): string[] => {
|
const sanitizeInputFiles = (input: unknown): string[] => {
|
||||||
const candidates = Array.isArray(input)
|
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 runnerInstance = runner();
|
||||||
const detachInstance = detach();
|
const detachInstance = detach();
|
||||||
|
|
||||||
@@ -349,13 +273,7 @@ class API extends DBInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
const normalizedFiles = targetFiles.map((filePath) =>
|
return copyFilesToWindowsClipboard(targetFiles);
|
||||||
path.normalize(filePath)
|
|
||||||
);
|
|
||||||
if (writeWithClipboardEx(normalizedFiles)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return writeWindowsClipboardFiles(normalizedFiles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
91
src/main/common/windowsClipboard.ts
Normal file
91
src/main/common/windowsClipboard.ts
Normal file
@@ -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);
|
||||||
|
};
|
||||||
|
|
||||||
Reference in New Issue
Block a user