diff --git a/main.js b/main.js index a063ab1..52c65c5 100644 --- a/main.js +++ b/main.js @@ -1,6 +1,6 @@ if (require('electron-squirrel-startup')) return; -const { app, BrowserWindow, globalShortcut, Tray, Menu, nativeImage, screen, clipboard, desktopCapturer } = require('electron'); +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'); @@ -15,6 +15,7 @@ if (handleSquirrelEvent()) { 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(); @@ -95,6 +96,33 @@ function createAppWindow() { } }); } + 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() { @@ -203,7 +231,34 @@ function createTray() { }); }, }, - { type: 'separator' }, // Optional: adds a line before the build time and Quit + { 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', @@ -233,6 +288,66 @@ 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) @@ -284,6 +399,15 @@ app.on('ready', () => { }); 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', () => { @@ -311,13 +435,6 @@ app.on('activate', () => { }); 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'); diff --git a/package.json b/package.json index fa3efba..3378210 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gemini-native", - "version": "1.20250517.174518", + "version": "1.20250519.193208", "description": "A native Electron application for Google Gemini.", "main": "main.js", "scripts": { diff --git a/settings.html b/settings.html new file mode 100644 index 0000000..804682b --- /dev/null +++ b/settings.html @@ -0,0 +1,84 @@ + + + + + Settings + + + + +
+

Settings

+ +
+

General

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+

Current Provider

+
+ +
+
+ +
+

Providers

+ + + + + + + + + + + +
NameURLActions
+
+ + + +
+
+
+ + + diff --git a/src/settingsManager.js b/src/settingsManager.js index f64e62d..361480d 100644 --- a/src/settingsManager.js +++ b/src/settingsManager.js @@ -9,7 +9,18 @@ const defaultSettings = { reloadOnPasteEnabled: true, isAlwaysOnTop: true, isFrameVisible: false, - launchOnStartup: false, // Added: Setting for launch on startup + launchOnStartup: false, + + currentProvider: "Gemini", + + providers: [ + {name: "Gemini", url: "https://gemini.google.com"}, + {name: "ChatGPT", url: "https://chat.openai.com"}, + {name: "Bing Copilot", url: "https://www.bing.com/chat"}, + {name: "Claude", url: "https://claude.ai"}, + {name: "Qwen", url: "https://chat.qwen.ai/"}, + {name: "Perplexity", url: "https://www.perplexity.ai"}, + ] }; let currentSettings = { ...defaultSettings }; @@ -63,5 +74,26 @@ module.exports = { }, getAllSettings: () => { return { ...currentSettings }; + }, + addProvider: (provider) => { + currentSettings.providers.push(provider); + saveSettings(); + }, + deleteProvider: (index) => { + if (index >= 0 && index < currentSettings.providers.length) { + currentSettings.providers.splice(index, 1); + saveSettings(); + } + }, + updateProvider: (index, provider) => { + if (index >= 0 && index < currentSettings.providers.length) { + currentSettings.providers[index] = provider; + saveSettings(); + } + }, + getCurrentProviderUrl: () => { + const currentProviderName = currentSettings.currentProvider; + const provider = currentSettings.providers.find(p => p.name === currentProviderName); + return provider ? provider.url : (currentSettings.providers[0] ? currentSettings.providers[0].url : 'https://gemini.google.com'); // Fallback } }; diff --git a/src/settingsRenderer.js b/src/settingsRenderer.js new file mode 100644 index 0000000..242253d --- /dev/null +++ b/src/settingsRenderer.js @@ -0,0 +1,117 @@ +// This script will handle the renderer process for the settings.html page. +const { ipcRenderer } = require('electron'); + +document.addEventListener('DOMContentLoaded', () => { + loadSettings(); + setupEventListeners(); +}); + +function loadSettings() { + const settings = ipcRenderer.sendSync('get-all-settings'); // Synchronous call to get settings + + // General settings + document.getElementById('autoHideEnabled').checked = settings.autoHideEnabled; + document.getElementById('reloadOnPasteEnabled').checked = settings.reloadOnPasteEnabled; + document.getElementById('isAlwaysOnTop').checked = settings.isAlwaysOnTop; + document.getElementById('isFrameVisible').checked = settings.isFrameVisible; + document.getElementById('launchOnStartup').checked = settings.launchOnStartup; + + // Current Provider Dropdown + const currentProviderSelect = document.getElementById('currentProviderSelect'); + currentProviderSelect.innerHTML = ''; // Clear existing options + settings.providers.forEach(provider => { + const option = document.createElement('option'); + option.value = provider.name; + option.textContent = provider.name; + if (provider.name === settings.currentProvider) { + option.selected = true; + } + currentProviderSelect.appendChild(option); + }); + + // Providers Table + const providersTableBody = document.getElementById('providersTable').getElementsByTagName('tbody')[0]; + providersTableBody.innerHTML = ''; // Clear existing rows + + settings.providers.forEach((provider, index) => { + const row = providersTableBody.insertRow(); + row.insertCell().textContent = provider.name; + row.insertCell().textContent = provider.url; + + const actionsCell = row.insertCell(); + const deleteButton = document.createElement('button'); + deleteButton.textContent = 'Delete'; + deleteButton.onclick = () => deleteProvider(index); + actionsCell.appendChild(deleteButton); + + // Add an edit button (optional, for more advanced CRUD) + // const editButton = document.createElement('button'); + // editButton.textContent = 'Edit'; + // editButton.onclick = () => editProvider(index); + // actionsCell.appendChild(editButton); + }); +} + +function setupEventListeners() { + // General settings listeners + document.getElementById('autoHideEnabled').addEventListener('change', (event) => { + ipcRenderer.send('update-setting', { key: 'autoHideEnabled', value: event.target.checked }); + }); + document.getElementById('reloadOnPasteEnabled').addEventListener('change', (event) => { + ipcRenderer.send('update-setting', { key: 'reloadOnPasteEnabled', value: event.target.checked }); + }); + document.getElementById('isAlwaysOnTop').addEventListener('change', (event) => { + ipcRenderer.send('update-setting', { key: 'isAlwaysOnTop', value: event.target.checked }); + }); + document.getElementById('isFrameVisible').addEventListener('change', (event) => { + ipcRenderer.send('update-setting', { key: 'isFrameVisible', value: event.target.checked }); + }); + document.getElementById('launchOnStartup').addEventListener('change', (event) => { + ipcRenderer.send('update-setting', { key: 'launchOnStartup', value: event.target.checked }); + }); + + // Current provider select listener + document.getElementById('currentProviderSelect').addEventListener('change', (event) => { + ipcRenderer.send('update-setting', { key: 'currentProvider', value: event.target.value }); + // Optionally, send a specific event if the main window needs to react immediately without a full settings reload signal + ipcRenderer.send('current-provider-changed'); + }); + + // Provider table listeners + document.getElementById('addProviderBtn').addEventListener('click', () => { + const nameInput = document.getElementById('newProviderName'); + const urlInput = document.getElementById('newProviderUrl'); + const name = nameInput.value.trim(); + const url = urlInput.value.trim(); + + if (name && url) { + ipcRenderer.send('add-provider', { name, url }); + nameInput.value = ''; // Clear input + urlInput.value = ''; // Clear input + loadSettings(); // Reload to show the new provider + } + }); +} + +function deleteProvider(index) { + ipcRenderer.send('delete-provider', index); + loadSettings(); // Reload to reflect deletion +} + +// Example of editProvider (requires more UI/logic for editing) +/* +function editProvider(index) { + const provider = ipcRenderer.sendSync('get-all-settings').providers[index]; + const newName = prompt('Enter new name:', provider.name); + const newUrl = prompt('Enter new URL:', provider.url); + if (newName !== null && newUrl !== null) { + ipcRenderer.send('update-provider', { index, name: newName, url: newUrl }); + loadSettings(); + } +} +*/ + +// Listen for settings-updated from main process if other windows can change settings +ipcRenderer.on('settings-updated', () => { + loadSettings(); +}); diff --git a/src/windowManager.js b/src/windowManager.js index d6ad25f..6c0564e 100644 --- a/src/windowManager.js +++ b/src/windowManager.js @@ -39,7 +39,9 @@ function createWindow(onCloseCallback, onBlurCallback) { }); positionWindowAtBottomCenter(mainWindow); - mainWindow.loadURL('https://gemini.google.com'); + // Load the URL of the current provider + const providerUrl = settingsManager.getCurrentProviderUrl(); + mainWindow.loadURL(providerUrl); mainWindow.setMenuBarVisibility(false); mainWindow.on('close', onCloseCallback);