const { BrowserWindow, Menu, MenuItem, ipcMain, app, clipboard, screen, globalShortcut, dialog, Tray, nativeImage } = require('electron') const path = require('path') const fs = require('fs'); const Store = require('electron-store'); const store = new Store(); //store.clear() let width = 400; let height = 300; let isLocked = false; let tray = null; let helpWindow = null; function createWindow() { const win = new BrowserWindow({ backgroundColor: "#202020", width: width, height: height, frame: false, transparent: true, webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: true, } }) win.setAlwaysOnTop(true); win.loadFile('index.html') return win; } function createHelpWindow(parentWindow) { // 如果帮助窗口已存在,则聚焦它 if (helpWindow && !helpWindow.isDestroyed()) { helpWindow.focus() return } helpWindow = new BrowserWindow({ width: 700, height: 600, backgroundColor: "#2a2a2a", title: "快捷键帮助 - RefVroid", autoHideMenuBar: true, parent: parentWindow, modal: true, alwaysOnTop: true, webPreferences: { nodeIntegration: false, contextIsolation: true } }) helpWindow.loadFile('help.html') // 窗口关闭时清理引用 helpWindow.on('closed', () => { helpWindow = null }) } app.commandLine.appendSwitch('disable-site-isolation-trials'); const contextMenu = new Menu() const recentSubmenu = new Menu() const windowSubmenu = new Menu() const opacitySubmenu = new Menu() app.whenReady().then(() => { const mainWin = createWindow() // 创建系统托盘图标 let trayIcon const iconPath = path.join(__dirname, 'images', 'tray-icon.png') if (fs.existsSync(iconPath)) { trayIcon = nativeImage.createFromPath(iconPath) } else { // 使用 base64 图标 const iconData = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAA5SURBVDiN7dMxDQAgEMCwA/+fGQZgABZgARZgARZgARZgARZgARZgARZgARZgARZgARZgARbwsgMvpQYH8VLsJwAAAABJRU5ErkJggg==' trayIcon = nativeImage.createFromDataURL(iconData) } if (trayIcon.isEmpty()) { trayIcon = nativeImage.createFromBuffer( Buffer.from('iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAA5SURBVDiN7dMxDQAgEMCwA/+fGQZgABZgARZgARZgARZgARZgARZgARZgARZgARZgARZgARbwsgMvpQYH8VLsJwAAAABJRU5ErkJggg==', 'base64') ) } tray = new Tray(trayIcon) // 创建更新托盘菜单的函数 function updateTrayMenu() { const menuTemplate = [ { label: 'RefVroid', enabled: false }, { type: 'separator' } ] // 只在锁定时显示解锁选项 if (isLocked) { menuTemplate.push({ label: '解锁窗口', click: () => { isLocked = false mainWin.setIgnoreMouseEvents(false) mainWin.webContents.send('unlock-window') updateTrayMenu() } }) } menuTemplate.push( { label: '显示窗口', click: () => { mainWin.show() mainWin.focus() } }, { type: 'separator' }, { label: '退出', click: () => { app.quit() } } ) const trayMenu = Menu.buildFromTemplate(menuTemplate) tray.setContextMenu(trayMenu) } updateTrayMenu() tray.setToolTip('RefVroid - 动画参考工具') tray.on('click', () => { mainWin.isVisible() ? mainWin.hide() : mainWin.show() }) mainWin.on('ready-to-show', () => { console.log('Window ready to be presented'); mainWin.show(); }); mainWin.webContents.on('did-finish-load', () => { console.log('Page fully loaded'); //setTimeout(loadMostRecent, 1000) loadMostRecent() }); contextMenu.append(new MenuItem({ label: '粘贴', accelerator: process.platform === 'darwin' ? 'Cmd+V' : 'Ctrl+V', click: (menuItem, browserWindow, event) => { handlePaste(); } })); contextMenu.append(new MenuItem({ type: 'separator' })) contextMenu.append(new MenuItem({ label: '总是置顶', type: 'checkbox', checked: true, click: (menuItem, browserWindow, event) => { mainWin.setAlwaysOnTop(menuItem.checked); } })); // 只注册开发者工具的全局快捷键 globalShortcut.register('Control+Shift+I', () => { mainWin.webContents.openDevTools() }); function handlePaste() { console.log('========== handlePaste ==========') let payload = {} var formats = clipboard.availableFormats(); console.log('Available clipboard formats:', formats) // 尝试读取所有可能的格式 console.log('Trying FileNameW...') var rawFilePath = clipboard.read('FileNameW'); console.log('FileNameW result:', rawFilePath ? 'found' : 'not found') // 尝试读取 HTML 格式(可能包含文件路径) if (!rawFilePath && formats.indexOf('text/html') > -1) { try { const html = clipboard.readHTML(); console.log('HTML content:', html.substring(0, 200)); // 尝试从 HTML 中提取文件路径 const srcMatch = html.match(/src=["']file:\/\/\/([^"']+)["']/i); if (srcMatch) { rawFilePath = srcMatch[1].replace(/\//g, '\\'); console.log('Extracted file path from HTML:', rawFilePath); } } catch (err) { console.log('Failed to read HTML:', err.message); } } if (rawFilePath) { var filePath = rawFilePath.replace(new RegExp(String.fromCharCode(0), 'g'), ''); // 检查是否是图片文件,如果是则转换为 dataURL const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp']; const isImage = imageExtensions.some(ext => filePath.toLowerCase().endsWith(ext)); if (isImage && fs.existsSync(filePath)) { try { // 读取文件并转换为 base64 const imageBuffer = fs.readFileSync(filePath); const base64 = imageBuffer.toString('base64'); // 根据文件扩展名确定 MIME 类型 let mimeType = 'image/png'; if (filePath.toLowerCase().endsWith('.jpg') || filePath.toLowerCase().endsWith('.jpeg')) { mimeType = 'image/jpeg'; } else if (filePath.toLowerCase().endsWith('.gif')) { mimeType = 'image/gif'; } else if (filePath.toLowerCase().endsWith('.bmp')) { mimeType = 'image/bmp'; } else if (filePath.toLowerCase().endsWith('.webp')) { mimeType = 'image/webp'; } payload.type = 'dataURL'; payload.dataURL = `data:${mimeType};base64,${base64}`; console.log('Converted file to dataURL:', filePath, mimeType); } catch (err) { console.error('Error converting file to dataURL:', err); payload.type = 'filePath'; payload.filePath = filePath; } } else { payload.type = 'filePath'; payload.filePath = filePath; } console.log(filePath) } else { // 尝试读取原始图片数据(支持 GIF 动画) let imageBuffer = null; let mimeType = null; console.log('No file path found, trying to read image data...') // 尝试所有可能的图片格式 const imageFormats = [ 'image/gif', 'image/png', 'image/jpeg', 'image/jpg', 'image/bmp', 'image/webp', 'CF_DIB', // Windows DIB format 'CF_DIBV5' // Windows DIB v5 format ]; for (const format of imageFormats) { if (formats.indexOf(format) > -1) { try { console.log(`Trying to read format: ${format}`) const buffer = clipboard.readBuffer(format); if (buffer && buffer.length > 0) { imageBuffer = buffer; // 检测实际的图片类型(通过文件头) const header = buffer.slice(0, 6).toString('hex'); console.log(`Buffer header: ${header}, size: ${buffer.length}`) if (header.startsWith('474946')) { mimeType = 'image/gif'; console.log('Detected GIF by header'); } else if (header.startsWith('89504e')) { mimeType = 'image/png'; console.log('Detected PNG by header'); } else if (header.startsWith('ffd8ff')) { mimeType = 'image/jpeg'; console.log('Detected JPEG by header'); } else { // 使用格式名称作为后备 mimeType = format.startsWith('image/') ? format : 'image/png'; console.log('Using format name as mime type:', mimeType); } break; } } catch (err) { console.log(`Failed to read ${format}:`, err.message); } } } if (imageBuffer && imageBuffer.length > 0) { // 成功读取到原始图片数据 const base64 = imageBuffer.toString('base64'); payload.type = 'dataURL'; payload.dataURL = `data:${mimeType};base64,${base64}`; console.log('✓ Successfully converted clipboard image to dataURL'); console.log(' Type:', mimeType); console.log(' Size:', imageBuffer.length, 'bytes'); console.log(' Base64 length:', base64.length); } else if (formats.indexOf('image/png') > -1 || formats.some(f => f.startsWith('image/'))) { // 降级方案:使用 readImage(会丢失 GIF 动画) console.log('⚠ Using fallback readImage (will lose GIF animation)'); img = clipboard.readImage(); payload.type = 'dataURL'; payload.dataURL = img.toDataURL(); console.log(' Fallback image size:', img.getSize()); } else if (formats.indexOf('text/plain') > -1) { // 文本内容 var potentialUrl = clipboard.readText(); console.log("Checking if text is URL:", potentialUrl); if (validateUrl(potentialUrl)) { payload.type = 'filePath'; payload.filePath = potentialUrl; console.log('✓ Detected URL'); } else { // paste as text element payload.type = 'text'; payload.text = clipboard.readText(); console.log('✓ Pasting as text'); } } else { console.log('✗ No supported format found in clipboard'); } } console.log('========== Final payload type:', payload.type, '==========') mainWin.webContents.send('clipboard', JSON.stringify(payload)) // send to web page } function addToRecent(filePath) { var recent = JSON.parse(store.get('recent') || "[]") console.log("addToRecent", store.get('recent'), filePath) let index = recent.indexOf(filePath) let exists = index != -1 if (exists) { recent.splice(index, 1) } recent.push(filePath) store.set('recent', JSON.stringify(recent)); console.log("addedToRecent", store.get('recent'), filePath) if (!exists) { recentSubmenu.append(new MenuItem({ label: filePath, click: (menuItem, browserWindow, event) => { readAndLoadFilePath(menuItem.label) } })) } } function populateRecent() { var recent = JSON.parse(store.get('recent') || "[]") console.log("populateRecent", store.get('recent')) for (recentfile of recent) { recentSubmenu.append(new MenuItem({ label: recentfile, click: (menuItem, browserWindow, event) => { readAndLoadFilePath(menuItem.label) } })) } } function loadMostRecent() { var recent = JSON.parse(store.get('recent') || "[]") if (recent.length > 0) readAndLoadFilePath(recent[recent.length - 1]) } contextMenu.append(new MenuItem({ type: 'separator' })) function readAndLoadFilePath(filePath) { fs.readFile(filePath, (err, data) => { if (err) throw err; let newState = JSON.parse(data); mainWin.webContents.send('load-scene', newState, filePath) }); } contextMenu.append(new MenuItem({ label: "最近使用", type: 'submenu', submenu: recentSubmenu })); populateRecent() // 添加窗口菜单 contextMenu.append(new MenuItem({ label: "窗口", type: 'submenu', submenu: windowSubmenu })); // 添加透明度菜单 for (let opacity = 100; opacity >= 10; opacity -= 10) { opacitySubmenu.append(new MenuItem({ label: `${opacity}%`, click: (menuItem, browserWindow, event) => { mainWin.setOpacity(opacity / 100) } })) } contextMenu.append(new MenuItem({ label: "透明度", type: 'submenu', submenu: opacitySubmenu })); // 添加锁定窗口菜单 contextMenu.append(new MenuItem({ id: "lock-window", label: '锁定窗口', click: (menuItem, browserWindow, event) => { isLocked = true mainWin.setIgnoreMouseEvents(true, { forward: true }) mainWin.webContents.send('lock-window') updateTrayMenu() } })); contextMenu.append(new MenuItem({ label: '加载', accelerator: process.platform === 'darwin' ? 'Cmd+L' : 'Ctrl+L', click: (menuItem, browserWindow, event) => { dialog.showOpenDialog({ properties: ['openFile'], filters: [ { name: 'RefVroid 场景', extensions: ['purgif'] } ] }).then(result => { if (!result.canceled) { readAndLoadFilePath(result.filePaths[0]) } }).catch(err => { console.log(err) }) } })); contextMenu.append(new MenuItem({ label: '保存', accelerator: process.platform === 'darwin' ? 'Cmd+S' : 'Ctrl+S', click: (menuItem, browserWindow, event) => { dialog.showSaveDialog({ defaultPath: 'scene.purgif', filters: [ { name: 'RefVroid 场景', extensions: ['purgif'] } ] }).then(result => { if (!result.canceled) { mainWin.webContents.send('save-scene', result.filePath) addToRecent(result.filePath) } }).catch(err => { console.log(err) }) } })); contextMenu.append(new MenuItem({ label: '新建场景', accelerator: process.platform === 'darwin' ? 'Cmd+N' : 'Ctrl+N', click: (menuItem, browserWindow, event) => { mainWin.webContents.send('new-scene'); } })); contextMenu.append(new MenuItem({ label: '关闭', accelerator: process.platform === 'darwin' ? 'Cmd+W' : 'Ctrl+W', click: (menuItem, browserWindow, event) => { browserWindow.close(); } })); Menu.setApplicationMenu(contextMenu) if (process.argv.indexOf("debug") > -1) mainWin.webContents.openDevTools() app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow() } }) app.on('browser-window-created', (event, win) => { win.webContents.on('context-menu', (e, params) => { //menu.popup(win, params.x, params.y) }) }) ipcMain.on('ready', (event, menuType) => { //loadMostRecent() windowIsReady = true; }); ipcMain.on('show-context-menu', (event, menuType) => { const win = BrowserWindow.fromWebContents(event.sender) // 根据窗口状态动态更新窗口菜单 const isMaximized = win.isMaximized() windowSubmenu.clear() windowSubmenu.append(new MenuItem({ label: isMaximized ? "恢复默认" : "最大化", accelerator: process.platform === 'darwin' ? 'Cmd+F' : 'Ctrl+F', click: (menuItem, browserWindow, event) => { if(!browserWindow.isMaximized()) { browserWindow.maximize(); } else { browserWindow.unmaximize(); } } })) windowSubmenu.append(new MenuItem({ label: "最小化", accelerator: process.platform === 'darwin' ? 'Cmd+M' : 'Ctrl+M', click: (menuItem, browserWindow, event) => { browserWindow.minimize(); } })) contextMenu.popup(win) }) ipcMain.on('save-scene', (event, filePath, stateCopy) => { console.log('save', filePath, stateCopy) let data = JSON.stringify(stateCopy); fs.writeFileSync(filePath, data); //const win = BrowserWindow.fromWebContents(event.sender) //menu.popup(win) }) let dragState = { dragging: false } ipcMain.on('handle-paste', (event, w, h) => { handlePaste() }) ipcMain.on('trigger-load', (event) => { dialog.showOpenDialog({ properties: ['openFile'], filters: [ { name: 'RefVroid 场景', extensions: ['purgif'] } ] }).then(result => { if (!result.canceled) { readAndLoadFilePath(result.filePaths[0]) } }).catch(err => { console.log(err) }) }) ipcMain.on('trigger-save', (event) => { dialog.showSaveDialog({ defaultPath: 'scene.purgif', filters: [ { name: 'RefVroid 场景', extensions: ['purgif'] } ] }).then(result => { if (!result.canceled) { mainWin.webContents.send('save-scene', result.filePath) addToRecent(result.filePath) } }).catch(err => { console.log(err) }) }) ipcMain.on('trigger-close', (event) => { mainWin.close() }) ipcMain.on('trigger-maximize', (event) => { if(!mainWin.isMaximized()) { mainWin.maximize() } else { mainWin.unmaximize() } }) ipcMain.on('trigger-minimize', (event) => { mainWin.minimize() }) ipcMain.on('show-help', (event) => { createHelpWindow(mainWin) }) ipcMain.on('convert-file-to-dataurl', (event, filePath) => { try { console.log('Converting file to dataURL:', filePath) // 读取文件 const imageBuffer = fs.readFileSync(filePath) const base64 = imageBuffer.toString('base64') // 根据文件扩展名确定 MIME 类型 let mimeType = 'image/png' const lowerPath = filePath.toLowerCase() if (lowerPath.endsWith('.jpg') || lowerPath.endsWith('.jpeg')) { mimeType = 'image/jpeg' } else if (lowerPath.endsWith('.gif')) { mimeType = 'image/gif' } else if (lowerPath.endsWith('.bmp')) { mimeType = 'image/bmp' } else if (lowerPath.endsWith('.webp')) { mimeType = 'image/webp' } const dataURL = `data:${mimeType};base64,${base64}` console.log('Converted to dataURL, type:', mimeType) // 发送回渲染进程 event.sender.send('file-converted-to-dataurl', dataURL) } catch (err) { console.error('Error converting file to dataURL:', err) } }) ipcMain.on('copy-text', (event, text) => { clipboard.writeText(text) console.log('Copied text to clipboard:', text) }) ipcMain.on('copy-multiple-images', (event, imageDataURLs) => { try { console.log('Received', imageDataURLs.length, 'images to copy') if (imageDataURLs.length === 1) { // 只有一张图片,使用 copy-image 的逻辑(支持 GIF) console.log('Single image, using copy-image handler') // 触发 copy-image 事件 mainWin.webContents.send('trigger-copy-single-image') // 直接调用 copy-image 的逻辑 const imagePath = imageDataURLs[0] // 检测是否是 GIF if (imagePath.startsWith('data:')) { const base64Data = imagePath.split(',')[1] const buffer = Buffer.from(base64Data, 'base64') const header = buffer.slice(0, 6).toString('hex') if (header.startsWith('474946')) { // 是 GIF,保存为临时文件 console.log('Detected GIF in multiple selection') const tempDir = path.join(app.getPath('temp'), 'RefVroid-clipboard') if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }) } const tempFilePath = path.join(tempDir, `animated_${Date.now()}.gif`) fs.writeFileSync(tempFilePath, buffer) clipboard.clear() const filePathBuffer = Buffer.from(tempFilePath + '\0', 'ucs2') clipboard.writeBuffer('FileNameW', filePathBuffer) console.log('✓ Copied GIF from multiple selection') return } } // 不是 GIF,使用普通方法 const img = nativeImage.createFromDataURL(imageDataURLs[0]) if (!img.isEmpty()) { clipboard.clear() clipboard.writeImage(img) console.log('Copied single image') } return } // 创建临时文件夹 const tempDir = path.join(app.getPath('temp'), 'RefVroid-copy') if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }) } // 清理旧文件 try { const oldFiles = fs.readdirSync(tempDir) oldFiles.forEach(file => { try { fs.unlinkSync(path.join(tempDir, file)) } catch (err) { // 忽略删除错误 } }) } catch (err) { // 忽略读取目录错误 } // 保存所有图片为临时文件 const filePaths = [] const timestamp = Date.now() imageDataURLs.forEach((dataURL, index) => { try { // 检测图片类型 const base64Data = dataURL.split(',')[1] const buffer = Buffer.from(base64Data, 'base64') const header = buffer.slice(0, 6).toString('hex') let extension = 'png' let fileBuffer = null if (header.startsWith('474946')) { // GIF - 保存原始数据 extension = 'gif' fileBuffer = buffer console.log(' Image', index + 1, 'is GIF, preserving animation') } else { // 其他格式 - 转换为 PNG const img = nativeImage.createFromDataURL(dataURL) if (!img.isEmpty()) { fileBuffer = img.toPNG() console.log(' Image', index + 1, 'converted to PNG') } } if (fileBuffer) { const filePath = path.join(tempDir, `image_${timestamp}_${index + 1}.${extension}`) fs.writeFileSync(filePath, fileBuffer) filePaths.push(filePath) console.log(' Saved:', filePath) } } catch (err) { console.error('Error saving image', index, ':', err) } }) if (filePaths.length === 0) { console.log('No valid images to copy') return } console.log('Copying', filePaths.length, 'files') // 清空剪贴板 clipboard.clear() // 方法1: 使用 Windows 文件格式(FileNameW) // 这是 Windows 文件复制的标准格式,大多数应用都支持 try { // 构建文件列表(每个路径以 null 结尾,最后再加一个 null) let fileListBuffer = Buffer.alloc(0) filePaths.forEach(fp => { const pathBuffer = Buffer.from(fp + '\0', 'ucs2') fileListBuffer = Buffer.concat([fileListBuffer, pathBuffer]) }) // 添加最后的 null 终止符 fileListBuffer = Buffer.concat([fileListBuffer, Buffer.from('\0', 'ucs2')]) clipboard.writeBuffer('FileNameW', fileListBuffer) console.log(' Wrote FileNameW format') } catch (err) { console.error('Failed to write FileNameW:', err) } // 方法2: 使用 HTML 格式(某些应用支持) try { const htmlImages = filePaths.map(fp => `` ).join('') const html = `${htmlImages}` clipboard.writeHTML(html) console.log(' Wrote HTML format') } catch (err) { console.error('Failed to write HTML:', err) } // 方法3: 如果只有一张图片,同时写入图片数据(提高兼容性) if (filePaths.length === 1) { try { const buffer = fs.readFileSync(filePaths[0]) const img = nativeImage.createFromBuffer(buffer) if (!img.isEmpty()) { clipboard.writeImage(img) console.log(' Also wrote image data for single image') } } catch (err) { console.error('Failed to write image data:', err) } } console.log('✓ Copied', filePaths.length, 'file(s) in multiple formats') console.log(' Note: Some apps (like Feishu) may show file paths instead of images') } catch (err) { console.error('Error copying multiple images:', err) } }) ipcMain.on('copy-image', (event, imagePath) => { try { console.log('========== copy-image ==========') console.log('Image path/dataURL:', imagePath.substring(0, 100)) // 如果是 dataURL,检测真实的图片类型 if (imagePath.startsWith('data:')) { try { // 从 dataURL 提取 base64 数据 const base64Data = imagePath.split(',')[1] const buffer = Buffer.from(base64Data, 'base64') // 检测文件头来确定真实类型 const header = buffer.slice(0, 6).toString('hex') console.log('Image buffer header:', header, 'size:', buffer.length, 'bytes') let actualMimeType = null let isGif = false if (header.startsWith('474946')) { // GIF 文件头: 47 49 46 = "GIF" actualMimeType = 'image/gif' isGif = true console.log('✓ Detected GIF by file header') } else if (header.startsWith('89504e')) { // PNG 文件头: 89 50 4E 47 actualMimeType = 'image/png' console.log('✓ Detected PNG by file header') } else if (header.startsWith('ffd8ff')) { // JPEG 文件头: FF D8 FF actualMimeType = 'image/jpeg' console.log('✓ Detected JPEG by file header') } else { console.log('⚠ Unknown image format, using declared MIME type') actualMimeType = imagePath.split(';')[0].split(':')[1] } if (isGif) { // GIF - 保存为临时文件并复制文件 console.log('Copying GIF with animation...') try { // 创建临时文件 const tempDir = path.join(app.getPath('temp'), 'RefVroid-clipboard') if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }) } // 清理旧的临时文件 try { const files = fs.readdirSync(tempDir) const now = Date.now() files.forEach(file => { const filePath = path.join(tempDir, file) const stats = fs.statSync(filePath) // 删除超过1小时的文件 if (now - stats.mtimeMs > 3600000) { fs.unlinkSync(filePath) } }) } catch (err) { // 忽略清理错误 } const tempFilePath = path.join(tempDir, `animated_${Date.now()}.gif`) fs.writeFileSync(tempFilePath, buffer) console.log(' Created temp GIF file:', tempFilePath) // 使用 Electron 的 clipboard.writeBuffer 写入文件路径 // 这是 Windows 文件复制的标准格式 clipboard.clear() // 写入文件路径(CF_HDROP 格式) const filePathBuffer = Buffer.from(tempFilePath + '\0', 'ucs2') clipboard.writeBuffer('FileNameW', filePathBuffer) console.log('✓ Successfully copied GIF file to clipboard') console.log(' Temp file:', tempFilePath) console.log(' You can now paste it to QQ or other apps') return } catch (err) { console.error('Failed to copy GIF with temp file:', err) // 降级方案:只写入静态图片 clipboard.clear() const img = nativeImage.createFromBuffer(buffer) if (!img.isEmpty()) { clipboard.writeImage(img) console.log('⚠ Copied as static image (animation lost)') return } } } else { // 其他格式 - 使用 nativeImage console.log('Copying', actualMimeType, 'image...') const correctedDataURL = `data:${actualMimeType};base64,${base64Data}` const img = nativeImage.createFromDataURL(correctedDataURL) if (!img.isEmpty()) { clipboard.clear() clipboard.writeImage(img) console.log('✓ Successfully copied', actualMimeType, 'to clipboard') console.log(' Image size:', img.getSize()) return } else { console.error('✗ Created image is empty') } } } catch (err) { console.error('Failed to process dataURL:', err) clipboard.writeText('图片复制失败: ' + err.message) return } } // 处理网络图片 if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) { // 网络图片暂时只能复制 URL clipboard.writeText(imagePath) console.log('Copied image URL to clipboard:', imagePath) return } else { // 本地文件路径 // 处理可能的 file:// 协议 let filePath = imagePath if (filePath.startsWith('file://')) { filePath = filePath.substring(7) } // Windows 路径处理 if (process.platform === 'win32' && filePath.startsWith('/')) { filePath = filePath.substring(1) } console.log('Reading image from path:', filePath) // 检查文件是否存在 if (!fs.existsSync(filePath)) { console.error('File does not exist:', filePath) clipboard.writeText('文件不存在: ' + filePath) return } // 检查是否是 GIF 文件 const isGifFile = filePath.toLowerCase().endsWith('.gif') if (isGifFile) { // GIF 文件 - 直接复制文件路径 try { console.log('Detected GIF file, copying file path') const buffer = fs.readFileSync(filePath) console.log('GIF buffer size:', buffer.length, 'bytes') clipboard.clear() // 写入文件路径(Windows 格式) clipboard.write({ text: filePath, bookmark: filePath }) // 同时写入 GIF buffer clipboard.writeBuffer('image/gif', buffer) // 写入静态图片作为降级 const img = nativeImage.createFromBuffer(buffer) if (!img.isEmpty()) { clipboard.writeImage(img) console.log(' Also wrote static image as fallback') } console.log('✓ Successfully copied GIF file') return } catch (err) { console.error('Failed to copy GIF file:', err) // 降级到普通方法 } } // 其他格式的文件 const img = nativeImage.createFromPath(filePath) console.log('Image loaded, size:', img.getSize()) if (!img.isEmpty()) { clipboard.clear() clipboard.writeImage(img) console.log('✓ Successfully copied image to clipboard') console.log(' Image size:', img.getSize()) return } else { console.error('✗ Image is empty') clipboard.writeText('图片为空') return } } } catch (err) { console.error('Error copying image:', err) clipboard.writeText('复制图片时出错: ' + err.message) } }) ipcMain.on('loaded-state', (event, filePath) => { addToRecent(filePath) }) ipcMain.on('record-window-size', (event, w, h) => { width = mainWin.getSize()[0] height = mainWin.getSize()[1] }) ipcMain.on('move-electron-window', (event, x, y, initPos) => { //var display = screen.getDisplayNearestPoint({x: x, y: y}) //var dpiRespected = screen.dipToScreenPoint({x: x, y: y}) //mainWin.setPosition(x,y) mainWin.setBounds({ width: width, height: height, x: x - initPos.x, y: y - initPos.y }); //win.setSize(width, height) //mainWin.setPosition(Math.round(x / 1.25) - Math.round(initPos.x / 1.25), Math.round(y / 1.25) - Math.round(initPos.y / 1.25)) }) let loopToLoad = function loopToLoad(){ if(windowIsReady){ console.log("loadMostRecent") //setTimeout(loadMostRecent, 700) //loadMostRecent() } else{ console.log("not ready") setTimeout(loopToLoad, 100) } }; loopToLoad(); }) let windowIsReady = false; app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } }) app.on('will-quit', () => { globalShortcut.unregisterAll() }) function validateUrl(value) { return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(value); }