import { app, BrowserWindow, shell, ipcMain, dialog, nativeTheme, globalShortcut, clipboard } from 'electron'; import path from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; import fs from 'fs'; import log from 'electron-log'; import { generatePdf } from './services/pdfGenerator'; import { selectHtmlFile } from './services/htmlImport'; import { electronState } from './state'; log.initialize(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); process.env.NOTEBOOK_ROOT = path.join(app.getPath('documents'), 'XCDesktop'); if (!fs.existsSync(process.env.NOTEBOOK_ROOT)) { try { fs.mkdirSync(process.env.NOTEBOOK_ROOT, { recursive: true }); } catch (err) { log.error('Failed to create notebook directory:', err); } } electronState.setDevelopment(!app.isPackaged); let lastClipboardText = ''; function startClipboardWatcher() { lastClipboardText = clipboard.readText(); setInterval(() => { try { const currentText = clipboard.readText(); if (currentText && currentText !== lastClipboardText) { lastClipboardText = currentText; log.info('Clipboard changed, syncing to remote'); const win = electronState.getMainWindow(); if (win) { win.webContents.send('remote-clipboard-auto-sync', currentText); } } } catch (e) { // ignore } }, 1000); } async function createWindow() { const initialSymbolColor = nativeTheme.shouldUseDarkColors ? '#ffffff' : '#000000'; const mainWindow = new BrowserWindow({ width: 1280, height: 800, minWidth: 1600, minHeight: 900, autoHideMenuBar: true, titleBarStyle: 'hidden', titleBarOverlay: { color: '#00000000', symbolColor: initialSymbolColor, height: 32, }, webPreferences: { nodeIntegration: false, contextIsolation: true, sandbox: false, webviewTag: true, preload: path.join(__dirname, 'preload.cjs'), }, }); electronState.setMainWindow(mainWindow); mainWindow.setMenu(null); mainWindow.webContents.setWindowOpenHandler(({ url }) => { if (url.startsWith('http:') || url.startsWith('https:')) { shell.openExternal(url); return { action: 'deny' }; } return { action: 'allow' }; }); if (electronState.isDevelopment()) { log.info('Loading development URL...'); try { await mainWindow.loadURL('http://localhost:5173'); } catch (e) { log.error('Failed to load dev URL. Make sure npm run electron:dev is used.', e); } mainWindow.webContents.openDevTools(); } else { log.info(`Loading production URL with port ${electronState.getServerPort()}...`); await mainWindow.loadURL(`http://localhost:${electronState.getServerPort()}`); } } ipcMain.handle('export-pdf', async (event, title, htmlContent) => { const win = BrowserWindow.fromWebContents(event.sender); if (!win) return { success: false, error: 'No window found' }; try { const { filePath } = await dialog.showSaveDialog(win, { title: '导出 PDF', defaultPath: `${title}.pdf`, filters: [{ name: 'PDF Files', extensions: ['pdf'] }] }); if (!filePath) return { success: false, canceled: true }; if (!htmlContent) { throw new Error('No HTML content provided for PDF export'); } const pdfData = await generatePdf(htmlContent); fs.writeFileSync(filePath, pdfData); return { success: true, filePath }; } catch (error: any) { log.error('Export PDF failed:', error); return { success: false, error: error.message }; } }); ipcMain.handle('select-html-file', async (event) => { const win = BrowserWindow.fromWebContents(event.sender); return selectHtmlFile(win); }); ipcMain.handle('update-titlebar-buttons', async (event, symbolColor: string) => { const win = BrowserWindow.fromWebContents(event.sender); if (win) { win.setTitleBarOverlay({ symbolColor }); return { success: true }; } return { success: false }; }); ipcMain.handle('clipboard-read-text', async () => { try { const text = clipboard.readText(); return { success: true, text }; } catch (error: any) { log.error('Clipboard read failed:', error); return { success: false, error: error.message }; } }); ipcMain.handle('clipboard-write-text', async (event, text: string) => { try { clipboard.writeText(text); return { success: true }; } catch (error: any) { log.error('Clipboard write failed:', error); return { success: false, error: error.message }; } }); async function startServer() { if (electronState.isDevelopment()) { log.info('In dev mode, assuming external servers are running.'); return; } const serverPath = path.join(__dirname, '../dist-api/server.js'); const serverUrl = pathToFileURL(serverPath).href; log.info(`Starting internal server from: ${serverPath}`); try { const serverModule = await import(serverUrl); if (serverModule.startServer) { const port = await serverModule.startServer(); electronState.setServerPort(port); log.info(`Internal server started successfully on port ${port}`); } else { log.warn('startServer function not found in server module, using default port 3001'); } } catch (e) { log.error('Failed to start internal server:', e); } } app.whenReady().then(async () => { await startServer(); await createWindow(); startClipboardWatcher(); globalShortcut.register('CommandOrControl+Shift+C', () => { log.info('Global shortcut: sync clipboard to remote'); const win = electronState.getMainWindow(); if (win) { win.webContents.send('remote-clipboard-sync-to-remote'); } }); globalShortcut.register('CommandOrControl+Shift+V', () => { log.info('Global shortcut: sync clipboard from remote'); const win = electronState.getMainWindow(); if (win) { win.webContents.send('remote-clipboard-sync-from-remote'); } }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); app.on('window-all-closed', () => { globalShortcut.unregisterAll(); if (process.platform !== 'darwin') { app.quit(); } });