gemini-native/main.js
2025-05-19 11:40:56 +01:00

334 lines
11 KiB
JavaScript

if (require('electron-squirrel-startup')) return;
const { app, BrowserWindow, globalShortcut, Tray, Menu, nativeImage, screen, clipboard, desktopCapturer } = 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
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)
}
});
}
}
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' }, // Optional: adds a line before the build time and Quit
{ 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);
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();
});
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) => {
webContents.on('will-navigate', (event) => {
// Only allow navigation to `google.com` or subdomains
const url = new URL(webContents.getURL());
if (url.hostname !== 'google.com' && !url.hostname.endsWith('.google.com')) {
event.preventDefault();
}
});
});
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);
}