if (require('electron-squirrel-startup')) return; const { app, BrowserWindow, globalShortcut, Tray, Menu, nativeImage, screen, clipboard, desktopCapturer, ipcMain } = require('electron'); const path = require('path'); const settingsManager = require('./src/settingsManager'); const { handleSquirrelEvent } = require('./src/squirrelEvents'); const { createWindow, getMainWindow, toggleWindowVisibility, positionWindowAtBottomCenter, createFirstRunWindow } = require('./src/windowManager'); const fs = require('fs'); // Ensure fs is required // Handle Squirrel events for Windows installers if (handleSquirrelEvent()) { // Squirrel event handled (e.g., app installed, updated, or uninstalled), quit the app return; } let tray = null; let isQuitting = false; // Flag to differentiate between closing the window and quitting the app let settingsWindow = null; async function captureAndPaste() { const mainWindow = getMainWindow(); try { const cursorPoint = screen.getCursorScreenPoint(); const display = screen.getDisplayNearestPoint(cursorPoint); const sources = await desktopCapturer.getSources({ types: ['screen'], thumbnailSize: display.size }); const primaryDisplaySource = sources.find(source => source.display_id === display.id.toString()); if (primaryDisplaySource) { const screenshot = primaryDisplaySource.thumbnail; clipboard.writeImage(screenshot); } else { // TODO: Handle case where primary display source is not found } } catch (error) { // TODO: Handle screenshot error } toggleWindowVisibility(); // Show the window after capturing if (mainWindow && mainWindow.isVisible()) { // Optional: Reload the page before pasting if the setting is enabled if (settingsManager.getSetting('reloadOnPasteEnabled')) { await mainWindow.webContents.reload(); await mainWindow.webContents.executeJavaScript('document.readyState === "complete"'); await new Promise(resolve => setTimeout(resolve, 250)); } else { await mainWindow.webContents.executeJavaScript('document.readyState === "complete"'); await new Promise(resolve => setTimeout(resolve, 100)); } } if (mainWindow) { // Simulate a paste action (Ctrl+V or Cmd+V) mainWindow.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'v', modifiers: process.platform === 'darwin' ? ['meta'] : ['control'] }); mainWindow.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'v', modifiers: process.platform === 'darwin' ? ['meta'] : ['control'] }); } } function createAppWindow() { const mainWindow = createWindow( (event) => { // on-close handler if (!isQuitting) { event.preventDefault(); // Prevent the window from actually closing getMainWindow().hide(); // Hide the window instead if (!tray) { // Create tray icon if it doesn't exist createTray(); } } }, () => { // on-blur handler const mw = getMainWindow(); if (mw && mw.isVisible() && !isQuitting && settingsManager.getSetting('autoHideEnabled')) { mw.hide(); if (!tray) { createTray(); } } } ); // Listen for 'before-input-event' to handle Escape key if (mainWindow && mainWindow.webContents) { mainWindow.webContents.on('before-input-event', (event, input) => { if (input.key === 'Escape' && input.type === 'keyDown' && mainWindow.isFocused()) { mainWindow.hide(); event.preventDefault(); // Prevent default Escape behavior (like closing modals) } }); } return mainWindow; // Ensure mainWindow is returned } function createSettingsWindow() { if (settingsWindow) { settingsWindow.focus(); return; } settingsWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), // Optional: if you need preload for settings nodeIntegration: true, // Required for ipcRenderer in settingsRenderer.js contextIsolation: false, // Required for ipcRenderer in settingsRenderer.js }, parent: getMainWindow(), // Optional: makes it a child of the main window modal: false, // Set to true if you want it to be a modal dialog autoHideMenuBar: true, }); settingsWindow.loadFile(path.join(__dirname, 'settings.html')); settingsWindow.on('closed', () => { settingsWindow = null; }); } function createTray() { let iconPath; try { iconPath = path.join(__dirname, 'assets', 'icon.png'); const image = nativeImage.createFromPath(iconPath); // Fallback to generic or minimal icon if the custom icon fails to load if (image.isEmpty() && process.platform === 'win32') { const genericIcon = nativeImage.createFromNamedImage('application-icon', [32, 32]); tray = new Tray(genericIcon); } else if (image.isEmpty()) { const minimalIcon = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAE0lEQVR42mNkIAAYRxWAAQAG9AAFdG1U2AAAAABJRU5ErkJggg=='); tray = new Tray(minimalIcon); } else { tray = new Tray(image); } } catch (e) { const fallbackImage = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAE0lEQVR42mNkIAAYRxWAAQAG9AAFdG1U2AAAAABJRU5ErkJggg=='); tray = new Tray(fallbackImage); } // Read package.json to get the version let buildTime = 'N/A'; try { const packageJsonPath = path.join(app.getAppPath(), 'package.json'); const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8'); const packageData = JSON.parse(packageJsonContent); const version = packageData.version; // e.g., "1.20250517.172559" if (version) { const parts = version.split('.'); if (parts.length === 3) { const datePart = parts[1]; // "YYYYMMDD" const timePart = parts[2]; // "HHMMSS" const year = datePart.substring(0, 4); const month = datePart.substring(4, 6); const day = datePart.substring(6, 8); const hour = timePart.substring(0, 2); const minute = timePart.substring(2, 4); const second = timePart.substring(4, 6); buildTime = `Build: ${year}-${month}-${day} ${hour}:${minute}:${second}`; } } } catch (error) { console.error('Failed to read or parse package.json for build time:', error); // Keep buildTime as 'N/A' or some other default } const contextMenu = Menu.buildFromTemplate([ { label: "Toggles", enabled: false }, { label: 'Auto-Hide on Focus Loss', type: 'checkbox', checked: settingsManager.getSetting('autoHideEnabled'), click: () => { const newValue = !settingsManager.getSetting('autoHideEnabled'); settingsManager.updateSetting('autoHideEnabled', newValue); }, }, { label: 'Reload on Screenshot', type: 'checkbox', checked: settingsManager.getSetting('reloadOnPasteEnabled'), click: () => { const newValue = !settingsManager.getSetting('reloadOnPasteEnabled'); settingsManager.updateSetting('reloadOnPasteEnabled', newValue); }, }, { label: 'Always on Top', type: 'checkbox', checked: settingsManager.getSetting('isAlwaysOnTop'), click: () => { const newValue = !settingsManager.getSetting('isAlwaysOnTop'); settingsManager.updateSetting('isAlwaysOnTop', newValue); if (mainWindow) { mainWindow.setAlwaysOnTop(newValue); } }, }, { label: 'Title Bar Visible', type: 'checkbox', checked: settingsManager.getSetting('isFrameVisible'), click: () => { const newValue = !settingsManager.getSetting('isFrameVisible'); settingsManager.updateSetting('isFrameVisible', newValue); const mainWindow = getMainWindow(); if (mainWindow) { mainWindow.destroy(); createAppWindow(); } }, }, { label: 'Launch on Startup', type: 'checkbox', checked: settingsManager.getSetting('launchOnStartup'), click: () => { const newValue = !settingsManager.getSetting('launchOnStartup'); settingsManager.updateSetting('launchOnStartup', newValue); app.setLoginItemSettings({ openAtLogin: newValue, path: app.getPath('exe'), }); }, }, { type: 'separator' }, { label: 'Select Provider', submenu: settingsManager.getAllSettings().providers.map(provider => ({ label: provider.name, type: 'radio', checked: settingsManager.getSetting('currentProvider') === provider.name, click: () => { settingsManager.updateSetting('currentProvider', provider.name); const mainWindow = getMainWindow(); if (mainWindow) { mainWindow.loadURL(provider.url); } // Rebuild tray to reflect change (important for radio button state) if (tray) { tray.destroy(); createTray(); } } })) }, { type: 'separator' }, { label: 'Settings', click: () => { createSettingsWindow(); } }, { label: buildTime, enabled: false }, { label: 'Quit', click: () => { isQuitting = true; app.quit(); }, }, ]); tray.setToolTip('Gemini'); tray.setContextMenu(contextMenu); // Handle tray icon click: show/hide window or create if it doesn't exist tray.on('click', () => { const mainWindow = getMainWindow(); if (mainWindow) { mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show(); } else { createAppWindow(); } }); } Menu.setApplicationMenu(null) app.on('ready', () => { console.log(process.argv); // IPC Handlers for settings ipcMain.on('get-all-settings', (event) => { event.returnValue = settingsManager.getAllSettings(); }); ipcMain.on('update-setting', (event, { key, value }) => { settingsManager.updateSetting(key, value); // Optionally, notify other windows if needed, e.g., main window for 'isAlwaysOnTop' if (key === 'isAlwaysOnTop') { const mainWindow = getMainWindow(); if (mainWindow) { mainWindow.setAlwaysOnTop(value); } } if (key === 'launchOnStartup') { app.setLoginItemSettings({ openAtLogin: value, path: app.getPath('exe'), }); } // If currentProvider changes, update the main window URL if (key === 'currentProvider') { const mainWindow = getMainWindow(); if (mainWindow) { const providerUrl = settingsManager.getCurrentProviderUrl(); mainWindow.loadURL(providerUrl); } // Rebuild tray to reflect change in provider selection if (tray) { tray.destroy(); createTray(); // This will rebuild the menu with the correct checked state } } // Notify settings window to reload if changes can affect it directly if (settingsWindow) { settingsWindow.webContents.send('settings-updated'); } }); ipcMain.on('add-provider', (event, provider) => { settingsManager.addProvider(provider); if (settingsWindow) { settingsWindow.webContents.send('settings-updated'); // Refresh provider list } }); ipcMain.on('delete-provider', (event, index) => { settingsManager.deleteProvider(index); if (settingsWindow) { settingsWindow.webContents.send('settings-updated'); // Refresh provider list } }); ipcMain.on('update-provider', (event, { index, name, url }) => { settingsManager.updateProvider(index, { name, url }); if (settingsWindow) { settingsWindow.webContents.send('settings-updated'); // Refresh provider list } }); let firstRunWindow = null; // Check for Squirrel events (like first run) if (process.argv.includes('--squirrel-firstrun')) { firstRunWindow = createFirstRunWindow(app); } createAppWindow(); // Register a global shortcut (Super+Shift+G) to toggle window visibility. // 'Super' is the Windows key or Command key on macOS. const retShowHide = globalShortcut.register('Super+Shift+G', async () => { console.log('Toggle window visibility triggered'); toggleWindowVisibility(); if (firstRunWindow) { firstRunWindow.close(); // Close the first run window if it exists firstRunWindow = null; // Clear the reference } }); if (!retShowHide) { // TODO: Handle failure to register shortcut } else { // Successfully registered } // Register a global shortcut (Super+Control+G) to capture screen and paste. const retCapturePaste = globalShortcut.register('Super+Control+G', () => { console.log('Capture and paste triggered'); captureAndPaste(); if (firstRunWindow) { firstRunWindow.close(); // Close the first run window if it exists firstRunWindow = null; // Clear the reference } }); if (!retCapturePaste) { // TODO: Handle failure to register shortcut } else { // Successfully registered } // Set login item settings based on user preference (launch on startup) app.setLoginItemSettings({ openAtLogin: settingsManager.getSetting('launchOnStartup'), path: app.getPath('exe'), }); createTray(); // Listen for current-provider-changed event to update the main window URL ipcMain.on('current-provider-changed', () => { const mainWindow = getMainWindow(); if (mainWindow) { const providerUrl = settingsManager.getCurrentProviderUrl(); mainWindow.loadURL(providerUrl); } }); }); app.on('will-quit', () => { // Unregister all global shortcuts when the application is about to quit globalShortcut.unregisterAll(); }); app.on('window-all-closed', () => { // Quit the app if isQuitting is true (user explicitly chose to quit) // Otherwise, the app continues to run in the tray if (isQuitting) { app.quit(); } }); app.on('activate', () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. const mainWindow = getMainWindow(); if (!mainWindow) { createAppWindow(); } else { mainWindow.show(); } }); app.on('web-contents-created', (event, webContents) => { }); const assetsDir = path.join(__dirname, 'assets'); // Ensure assets directory exists if (!fs.existsSync(assetsDir)) { fs.mkdirSync(assetsDir); } const iconPath = path.join(assetsDir, 'icon.png'); // Create a default icon if it doesn't exist if (!fs.existsSync(iconPath)) { const base64Icon = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; // 1x1 transparent pixel const buffer = Buffer.from(base64Icon, 'base64'); fs.writeFileSync(iconPath, buffer); }