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
+
+
+
+
+
Current Provider
+
+
+
+
+
+
+
+
+
+
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);