const { BrowserWindow, globalShortcut, ipcMain, screen } = require('electron'); const path = require('node:path'); const fs = require('node:fs'); const os = require('os'); const storage = require('../storage'); let mouseEventsIgnored = false; let windowResizing = false; let resizeAnimation = null; const RESIZE_ANIMATION_DURATION = 500; // milliseconds function createWindow(sendToRenderer, geminiSessionRef) { // Get layout preference (default to 'normal') let windowWidth = 1100; let windowHeight = 800; const mainWindow = new BrowserWindow({ width: windowWidth, height: windowHeight, frame: false, transparent: true, hasShadow: false, alwaysOnTop: true, webPreferences: { nodeIntegration: true, contextIsolation: false, // TODO: change to true backgroundThrottling: false, enableBlinkFeatures: 'GetDisplayMedia', webSecurity: true, allowRunningInsecureContent: false, }, backgroundColor: '#00000000', }); const { session, desktopCapturer } = require('electron'); session.defaultSession.setDisplayMediaRequestHandler( (request, callback) => { desktopCapturer.getSources({ types: ['screen'] }).then(sources => { callback({ video: sources[0], audio: 'loopback' }); }); }, { useSystemPicker: true } ); mainWindow.setResizable(false); mainWindow.setContentProtection(true); mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); // Hide from Windows taskbar if (process.platform === 'win32') { try { mainWindow.setSkipTaskbar(true); console.log('Hidden from Windows taskbar'); } catch (error) { console.warn('Could not hide from taskbar:', error.message); } } // Hide from Mission Control on macOS if (process.platform === 'darwin') { try { mainWindow.setHiddenInMissionControl(true); console.log('Hidden from macOS Mission Control'); } catch (error) { console.warn('Could not hide from Mission Control:', error.message); } } // Center window at the top of the screen const primaryDisplay = screen.getPrimaryDisplay(); const { width: screenWidth } = primaryDisplay.workAreaSize; const x = Math.floor((screenWidth - windowWidth) / 2); const y = 0; mainWindow.setPosition(x, y); if (process.platform === 'win32') { mainWindow.setAlwaysOnTop(true, 'screen-saver', 1); } mainWindow.loadFile(path.join(__dirname, '../index.html')); // After window is created, initialize keybinds mainWindow.webContents.once('dom-ready', () => { setTimeout(() => { const defaultKeybinds = getDefaultKeybinds(); let keybinds = defaultKeybinds; // Load keybinds from storage const savedKeybinds = storage.getKeybinds(); if (savedKeybinds) { keybinds = { ...defaultKeybinds, ...savedKeybinds }; } updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessionRef); }, 150); }); setupWindowIpcHandlers(mainWindow, sendToRenderer, geminiSessionRef); return mainWindow; } function getDefaultKeybinds() { const isMac = process.platform === 'darwin'; return { moveUp: isMac ? 'Alt+Up' : 'Ctrl+Up', moveDown: isMac ? 'Alt+Down' : 'Ctrl+Down', moveLeft: isMac ? 'Alt+Left' : 'Ctrl+Left', moveRight: isMac ? 'Alt+Right' : 'Ctrl+Right', toggleVisibility: isMac ? 'Cmd+\\' : 'Ctrl+\\', toggleClickThrough: isMac ? 'Cmd+M' : 'Ctrl+M', nextStep: isMac ? 'Cmd+Enter' : 'Ctrl+Enter', previousResponse: isMac ? 'Cmd+[' : 'Ctrl+[', nextResponse: isMac ? 'Cmd+]' : 'Ctrl+]', scrollUp: isMac ? 'Cmd+Shift+Up' : 'Ctrl+Shift+Up', scrollDown: isMac ? 'Cmd+Shift+Down' : 'Ctrl+Shift+Down', emergencyErase: isMac ? 'Cmd+Shift+E' : 'Ctrl+Shift+E', }; } function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessionRef) { console.log('Updating global shortcuts with:', keybinds); // Unregister all existing shortcuts globalShortcut.unregisterAll(); const primaryDisplay = screen.getPrimaryDisplay(); const { width, height } = primaryDisplay.workAreaSize; const moveIncrement = Math.floor(Math.min(width, height) * 0.1); // Register window movement shortcuts const movementActions = { moveUp: () => { if (!mainWindow.isVisible()) return; const [currentX, currentY] = mainWindow.getPosition(); mainWindow.setPosition(currentX, currentY - moveIncrement); }, moveDown: () => { if (!mainWindow.isVisible()) return; const [currentX, currentY] = mainWindow.getPosition(); mainWindow.setPosition(currentX, currentY + moveIncrement); }, moveLeft: () => { if (!mainWindow.isVisible()) return; const [currentX, currentY] = mainWindow.getPosition(); mainWindow.setPosition(currentX - moveIncrement, currentY); }, moveRight: () => { if (!mainWindow.isVisible()) return; const [currentX, currentY] = mainWindow.getPosition(); mainWindow.setPosition(currentX + moveIncrement, currentY); }, }; // Register each movement shortcut Object.keys(movementActions).forEach(action => { const keybind = keybinds[action]; if (keybind) { try { globalShortcut.register(keybind, movementActions[action]); console.log(`Registered ${action}: ${keybind}`); } catch (error) { console.error(`Failed to register ${action} (${keybind}):`, error); } } }); // Register toggle visibility shortcut if (keybinds.toggleVisibility) { try { globalShortcut.register(keybinds.toggleVisibility, () => { if (mainWindow.isVisible()) { mainWindow.hide(); } else { mainWindow.showInactive(); } }); console.log(`Registered toggleVisibility: ${keybinds.toggleVisibility}`); } catch (error) { console.error(`Failed to register toggleVisibility (${keybinds.toggleVisibility}):`, error); } } // Register toggle click-through shortcut if (keybinds.toggleClickThrough) { try { globalShortcut.register(keybinds.toggleClickThrough, () => { mouseEventsIgnored = !mouseEventsIgnored; if (mouseEventsIgnored) { mainWindow.setIgnoreMouseEvents(true, { forward: true }); console.log('Mouse events ignored'); } else { mainWindow.setIgnoreMouseEvents(false); console.log('Mouse events enabled'); } mainWindow.webContents.send('click-through-toggled', mouseEventsIgnored); }); console.log(`Registered toggleClickThrough: ${keybinds.toggleClickThrough}`); } catch (error) { console.error(`Failed to register toggleClickThrough (${keybinds.toggleClickThrough}):`, error); } } // Register next step shortcut (either starts session or takes screenshot based on view) if (keybinds.nextStep) { try { globalShortcut.register(keybinds.nextStep, async () => { console.log('Next step shortcut triggered'); try { // Determine the shortcut key format const isMac = process.platform === 'darwin'; const shortcutKey = isMac ? 'cmd+enter' : 'ctrl+enter'; // Use the new handleShortcut function mainWindow.webContents.executeJavaScript(` cheatingDaddy.handleShortcut('${shortcutKey}'); `); } catch (error) { console.error('Error handling next step shortcut:', error); } }); console.log(`Registered nextStep: ${keybinds.nextStep}`); } catch (error) { console.error(`Failed to register nextStep (${keybinds.nextStep}):`, error); } } // Register previous response shortcut if (keybinds.previousResponse) { try { globalShortcut.register(keybinds.previousResponse, () => { console.log('Previous response shortcut triggered'); sendToRenderer('navigate-previous-response'); }); console.log(`Registered previousResponse: ${keybinds.previousResponse}`); } catch (error) { console.error(`Failed to register previousResponse (${keybinds.previousResponse}):`, error); } } // Register next response shortcut if (keybinds.nextResponse) { try { globalShortcut.register(keybinds.nextResponse, () => { console.log('Next response shortcut triggered'); sendToRenderer('navigate-next-response'); }); console.log(`Registered nextResponse: ${keybinds.nextResponse}`); } catch (error) { console.error(`Failed to register nextResponse (${keybinds.nextResponse}):`, error); } } // Register scroll up shortcut if (keybinds.scrollUp) { try { globalShortcut.register(keybinds.scrollUp, () => { console.log('Scroll up shortcut triggered'); sendToRenderer('scroll-response-up'); }); console.log(`Registered scrollUp: ${keybinds.scrollUp}`); } catch (error) { console.error(`Failed to register scrollUp (${keybinds.scrollUp}):`, error); } } // Register scroll down shortcut if (keybinds.scrollDown) { try { globalShortcut.register(keybinds.scrollDown, () => { console.log('Scroll down shortcut triggered'); sendToRenderer('scroll-response-down'); }); console.log(`Registered scrollDown: ${keybinds.scrollDown}`); } catch (error) { console.error(`Failed to register scrollDown (${keybinds.scrollDown}):`, error); } } // Register emergency erase shortcut if (keybinds.emergencyErase) { try { globalShortcut.register(keybinds.emergencyErase, () => { console.log('Emergency Erase triggered!'); if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.hide(); if (geminiSessionRef.current) { geminiSessionRef.current.close(); geminiSessionRef.current = null; } sendToRenderer('clear-sensitive-data'); setTimeout(() => { const { app } = require('electron'); app.quit(); }, 300); } }); console.log(`Registered emergencyErase: ${keybinds.emergencyErase}`); } catch (error) { console.error(`Failed to register emergencyErase (${keybinds.emergencyErase}):`, error); } } } function setupWindowIpcHandlers(mainWindow, sendToRenderer, geminiSessionRef) { ipcMain.on('view-changed', (event, view) => { if (view !== 'assistant' && !mainWindow.isDestroyed()) { mainWindow.setIgnoreMouseEvents(false); } }); ipcMain.handle('window-minimize', () => { if (!mainWindow.isDestroyed()) { mainWindow.minimize(); } }); ipcMain.on('update-keybinds', (event, newKeybinds) => { if (!mainWindow.isDestroyed()) { updateGlobalShortcuts(newKeybinds, mainWindow, sendToRenderer, geminiSessionRef); } }); ipcMain.handle('toggle-window-visibility', async event => { try { if (mainWindow.isDestroyed()) { return { success: false, error: 'Window has been destroyed' }; } if (mainWindow.isVisible()) { mainWindow.hide(); } else { mainWindow.showInactive(); } return { success: true }; } catch (error) { console.error('Error toggling window visibility:', error); return { success: false, error: error.message }; } }); function animateWindowResize(mainWindow, targetWidth, targetHeight, layoutMode) { return new Promise(resolve => { // Check if window is destroyed before starting animation if (mainWindow.isDestroyed()) { console.log('Cannot animate resize: window has been destroyed'); resolve(); return; } // Clear any existing animation if (resizeAnimation) { clearInterval(resizeAnimation); resizeAnimation = null; } const [startWidth, startHeight] = mainWindow.getSize(); // If already at target size, no need to animate if (startWidth === targetWidth && startHeight === targetHeight) { console.log(`Window already at target size for ${layoutMode} mode`); resolve(); return; } console.log(`Starting animated resize from ${startWidth}x${startHeight} to ${targetWidth}x${targetHeight}`); windowResizing = true; mainWindow.setResizable(true); const frameRate = 60; // 60 FPS const totalFrames = Math.floor(RESIZE_ANIMATION_DURATION / (1000 / frameRate)); let currentFrame = 0; const widthDiff = targetWidth - startWidth; const heightDiff = targetHeight - startHeight; resizeAnimation = setInterval(() => { currentFrame++; const progress = currentFrame / totalFrames; // Use easing function (ease-out) const easedProgress = 1 - Math.pow(1 - progress, 3); const currentWidth = Math.round(startWidth + widthDiff * easedProgress); const currentHeight = Math.round(startHeight + heightDiff * easedProgress); if (!mainWindow || mainWindow.isDestroyed()) { clearInterval(resizeAnimation); resizeAnimation = null; windowResizing = false; return; } mainWindow.setSize(currentWidth, currentHeight); // Re-center the window during animation const primaryDisplay = screen.getPrimaryDisplay(); const { width: screenWidth } = primaryDisplay.workAreaSize; const x = Math.floor((screenWidth - currentWidth) / 2); const y = 0; mainWindow.setPosition(x, y); if (currentFrame >= totalFrames) { clearInterval(resizeAnimation); resizeAnimation = null; windowResizing = false; // Check if window is still valid before final operations if (!mainWindow.isDestroyed()) { mainWindow.setResizable(false); // Ensure final size is exact mainWindow.setSize(targetWidth, targetHeight); const finalX = Math.floor((screenWidth - targetWidth) / 2); mainWindow.setPosition(finalX, 0); } console.log(`Animation complete: ${targetWidth}x${targetHeight}`); resolve(); } }, 1000 / frameRate); }); } ipcMain.handle('update-sizes', async event => { try { if (mainWindow.isDestroyed()) { return { success: false, error: 'Window has been destroyed' }; } // Get current view and layout mode from renderer let viewName, layoutMode; try { viewName = await event.sender.executeJavaScript('cheatingDaddy.getCurrentView()'); layoutMode = await event.sender.executeJavaScript('cheatingDaddy.getLayoutMode()'); } catch (error) { console.warn('Failed to get view/layout from renderer, using defaults:', error); viewName = 'main'; layoutMode = 'normal'; } console.log('Size update requested for view:', viewName, 'layout:', layoutMode); let targetWidth, targetHeight; // Determine base size from layout mode const baseWidth = layoutMode === 'compact' ? 700 : 900; const baseHeight = layoutMode === 'compact' ? 500 : 600; // Adjust height based on view switch (viewName) { case 'main': targetWidth = baseWidth; targetHeight = layoutMode === 'compact' ? 320 : 400; break; case 'customize': case 'settings': targetWidth = baseWidth; targetHeight = layoutMode === 'compact' ? 700 : 800; break; case 'help': targetWidth = baseWidth; targetHeight = layoutMode === 'compact' ? 650 : 750; break; case 'history': targetWidth = baseWidth; targetHeight = layoutMode === 'compact' ? 650 : 750; break; case 'assistant': case 'onboarding': default: targetWidth = baseWidth; targetHeight = baseHeight; break; } const [currentWidth, currentHeight] = mainWindow.getSize(); console.log('Current window size:', currentWidth, 'x', currentHeight); // If currently resizing, the animation will start from current position if (windowResizing) { console.log('Interrupting current resize animation'); } await animateWindowResize(mainWindow, targetWidth, targetHeight, `${viewName} view (${layoutMode})`); return { success: true }; } catch (error) { console.error('Error updating sizes:', error); return { success: false, error: error.message }; } }); // Region selection window for capturing areas outside the main window let regionSelectionWindow = null; ipcMain.handle('start-region-selection', async (event, { screenshotDataUrl }) => { try { // Hide main window first const wasVisible = mainWindow.isVisible(); if (wasVisible) { mainWindow.hide(); } // Small delay to ensure window is hidden await new Promise(resolve => setTimeout(resolve, 100)); // Get all displays to cover all screens const displays = screen.getAllDisplays(); const primaryDisplay = screen.getPrimaryDisplay(); // Calculate bounds that cover all displays let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; displays.forEach(display => { minX = Math.min(minX, display.bounds.x); minY = Math.min(minY, display.bounds.y); maxX = Math.max(maxX, display.bounds.x + display.bounds.width); maxY = Math.max(maxY, display.bounds.y + display.bounds.height); }); const totalWidth = maxX - minX; const totalHeight = maxY - minY; // Create fullscreen transparent window for selection regionSelectionWindow = new BrowserWindow({ x: minX, y: minY, width: totalWidth, height: totalHeight, frame: false, transparent: true, alwaysOnTop: true, skipTaskbar: true, resizable: false, movable: false, hasShadow: false, // Hide from screen capture/sharing ...(process.platform === 'darwin' ? { type: 'panel' } : {}), webPreferences: { nodeIntegration: true, contextIsolation: false, }, }); // Hide window content from screen capture (macOS) if (process.platform === 'darwin') { regionSelectionWindow.setContentProtection(true); } regionSelectionWindow.setAlwaysOnTop(true, 'screen-saver', 1); // Create HTML content for selection overlay const htmlContent = `
Click and drag to select region • ESC to cancel
`; regionSelectionWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(htmlContent)}`); return new Promise((resolve) => { ipcMain.once('region-selected', (event, rect) => { if (regionSelectionWindow && !regionSelectionWindow.isDestroyed()) { regionSelectionWindow.close(); regionSelectionWindow = null; } if (wasVisible) { mainWindow.showInactive(); } resolve({ success: true, rect }); }); ipcMain.once('region-selection-cancelled', () => { if (regionSelectionWindow && !regionSelectionWindow.isDestroyed()) { regionSelectionWindow.close(); regionSelectionWindow = null; } if (wasVisible) { mainWindow.showInactive(); } resolve({ success: false, cancelled: true }); }); // Also handle window close regionSelectionWindow.on('closed', () => { regionSelectionWindow = null; if (wasVisible && !mainWindow.isDestroyed()) { mainWindow.showInactive(); } }); }); } catch (error) { console.error('Error starting region selection:', error); if (regionSelectionWindow && !regionSelectionWindow.isDestroyed()) { regionSelectionWindow.close(); regionSelectionWindow = null; } if (!mainWindow.isDestroyed()) { mainWindow.showInactive(); } return { success: false, error: error.message }; } }); } module.exports = { createWindow, getDefaultKeybinds, updateGlobalShortcuts, setupWindowIpcHandlers, };