342 lines
8 KiB
TypeScript
342 lines
8 KiB
TypeScript
import { app, BrowserWindow, Tray, Menu, globalShortcut, ipcMain, nativeImage, Notification } from 'electron';
|
|
import path from 'path';
|
|
import Store from 'electron-store';
|
|
import { autoUpdater } from 'electron-updater';
|
|
|
|
const store = new Store();
|
|
let mainWindow: BrowserWindow | null = null;
|
|
let tray: Tray | null = null;
|
|
|
|
// Single instance lock
|
|
const gotTheLock = app.requestSingleInstanceLock();
|
|
|
|
if (!gotTheLock) {
|
|
app.quit();
|
|
} else {
|
|
app.on('second-instance', () => {
|
|
if (mainWindow) {
|
|
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
mainWindow.focus();
|
|
}
|
|
});
|
|
}
|
|
|
|
function createWindow() {
|
|
mainWindow = new BrowserWindow({
|
|
width: 1200,
|
|
height: 800,
|
|
minWidth: 800,
|
|
minHeight: 600,
|
|
backgroundColor: '#1a1a1a',
|
|
show: false,
|
|
webPreferences: {
|
|
nodeIntegration: false,
|
|
contextIsolation: true,
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
},
|
|
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
|
|
frame: process.platform !== 'win32',
|
|
icon: path.join(__dirname, '../../assets/icon.png'),
|
|
});
|
|
|
|
// Load app
|
|
if (process.env.NODE_ENV === 'development') {
|
|
mainWindow.loadURL('http://localhost:5173');
|
|
mainWindow.webContents.openDevTools();
|
|
} else {
|
|
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
|
|
}
|
|
|
|
// Show when ready
|
|
mainWindow.once('ready-to-show', () => {
|
|
mainWindow?.show();
|
|
checkForUpdates();
|
|
});
|
|
|
|
// Minimize to tray instead of closing
|
|
mainWindow.on('close', (event) => {
|
|
if (!app.isQuitting && store.get('minimizeToTray', true)) {
|
|
event.preventDefault();
|
|
mainWindow?.hide();
|
|
|
|
if (process.platform === 'darwin') {
|
|
app.dock.hide();
|
|
}
|
|
}
|
|
});
|
|
|
|
mainWindow.on('closed', () => {
|
|
mainWindow = null;
|
|
});
|
|
}
|
|
|
|
function createTray() {
|
|
const iconPath = path.join(__dirname, '../../assets/tray-icon.png');
|
|
const icon = nativeImage.createFromPath(iconPath);
|
|
tray = new Tray(icon.resize({ width: 16, height: 16 }));
|
|
|
|
updateTrayMenu();
|
|
|
|
tray.setToolTip('AeThex Connect');
|
|
|
|
tray.on('click', () => {
|
|
if (mainWindow) {
|
|
if (mainWindow.isVisible()) {
|
|
mainWindow.hide();
|
|
} else {
|
|
mainWindow.show();
|
|
if (process.platform === 'darwin') {
|
|
app.dock.show();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
tray.on('right-click', () => {
|
|
tray?.popUpContextMenu();
|
|
});
|
|
}
|
|
|
|
function updateTrayMenu() {
|
|
if (!tray) return;
|
|
|
|
const muted = store.get('muted', false) as boolean;
|
|
const deafened = store.get('deafened', false) as boolean;
|
|
|
|
const contextMenu = Menu.buildFromTemplate([
|
|
{
|
|
label: 'Open AeThex Connect',
|
|
click: () => {
|
|
mainWindow?.show();
|
|
if (process.platform === 'darwin') {
|
|
app.dock.show();
|
|
}
|
|
},
|
|
},
|
|
{ type: 'separator' },
|
|
{
|
|
label: 'Mute',
|
|
type: 'checkbox',
|
|
checked: muted,
|
|
click: (menuItem) => {
|
|
store.set('muted', menuItem.checked);
|
|
mainWindow?.webContents.send('toggle-mute', menuItem.checked);
|
|
updateTrayMenu();
|
|
},
|
|
},
|
|
{
|
|
label: 'Deafen',
|
|
type: 'checkbox',
|
|
checked: deafened,
|
|
click: (menuItem) => {
|
|
store.set('deafened', menuItem.checked);
|
|
mainWindow?.webContents.send('toggle-deafen', menuItem.checked);
|
|
updateTrayMenu();
|
|
},
|
|
},
|
|
{ type: 'separator' },
|
|
{
|
|
label: 'Settings',
|
|
click: () => {
|
|
mainWindow?.webContents.send('open-settings');
|
|
mainWindow?.show();
|
|
},
|
|
},
|
|
{ type: 'separator' },
|
|
{
|
|
label: 'Check for Updates',
|
|
click: () => {
|
|
checkForUpdates();
|
|
},
|
|
},
|
|
{ type: 'separator' },
|
|
{
|
|
label: 'Quit',
|
|
click: () => {
|
|
app.isQuitting = true;
|
|
app.quit();
|
|
},
|
|
},
|
|
]);
|
|
|
|
tray.setContextMenu(contextMenu);
|
|
}
|
|
|
|
function registerGlobalShortcuts() {
|
|
// Push-to-talk (Ctrl+Shift+T by default)
|
|
const pttShortcut = (store.get('pttShortcut', 'CommandOrControl+Shift+T') as string);
|
|
|
|
globalShortcut.register(pttShortcut, () => {
|
|
mainWindow?.webContents.send('push-to-talk-pressed');
|
|
});
|
|
|
|
// Toggle mute (Ctrl+Shift+M)
|
|
globalShortcut.register('CommandOrControl+Shift+M', () => {
|
|
const muted = !store.get('muted', false);
|
|
store.set('muted', muted);
|
|
mainWindow?.webContents.send('toggle-mute', muted);
|
|
updateTrayMenu();
|
|
});
|
|
|
|
// Toggle deafen (Ctrl+Shift+D)
|
|
globalShortcut.register('CommandOrControl+Shift+D', () => {
|
|
const deafened = !store.get('deafened', false);
|
|
store.set('deafened', deafened);
|
|
mainWindow?.webContents.send('toggle-deafen', deafened);
|
|
updateTrayMenu();
|
|
});
|
|
}
|
|
|
|
// Auto-updater
|
|
function checkForUpdates() {
|
|
if (process.env.NODE_ENV === 'development') return;
|
|
|
|
autoUpdater.checkForUpdatesAndNotify();
|
|
}
|
|
|
|
autoUpdater.on('update-available', () => {
|
|
const notification = new Notification({
|
|
title: 'Update Available',
|
|
body: 'A new version of AeThex Connect is being downloaded.',
|
|
});
|
|
notification.show();
|
|
});
|
|
|
|
autoUpdater.on('update-downloaded', () => {
|
|
const notification = new Notification({
|
|
title: 'Update Ready',
|
|
body: 'Restart AeThex Connect to apply the update.',
|
|
});
|
|
notification.show();
|
|
|
|
notification.on('click', () => {
|
|
autoUpdater.quitAndInstall();
|
|
});
|
|
});
|
|
|
|
// IPC Handlers
|
|
|
|
// Rich Presence
|
|
ipcMain.handle('set-rich-presence', async (event, activity) => {
|
|
console.log('Rich presence:', activity);
|
|
// TODO: Integrate with Discord RPC, Windows Game Bar, etc.
|
|
return { success: true };
|
|
});
|
|
|
|
// Screen sharing sources
|
|
ipcMain.handle('get-sources', async () => {
|
|
const { desktopCapturer } = require('electron');
|
|
const sources = await desktopCapturer.getSources({
|
|
types: ['window', 'screen'],
|
|
thumbnailSize: { width: 150, height: 150 },
|
|
});
|
|
|
|
return sources.map((source) => ({
|
|
id: source.id,
|
|
name: source.name,
|
|
thumbnail: source.thumbnail.toDataURL(),
|
|
}));
|
|
});
|
|
|
|
// Notifications
|
|
ipcMain.on('show-notification', (event, { title, body, icon }) => {
|
|
const notification = new Notification({
|
|
title: title,
|
|
body: body,
|
|
icon: icon || path.join(__dirname, '../../assets/icon.png'),
|
|
});
|
|
|
|
notification.show();
|
|
|
|
notification.on('click', () => {
|
|
mainWindow?.show();
|
|
});
|
|
});
|
|
|
|
// Auto-launch
|
|
ipcMain.handle('get-auto-launch', async () => {
|
|
return app.getLoginItemSettings().openAtLogin;
|
|
});
|
|
|
|
ipcMain.handle('set-auto-launch', async (event, enabled: boolean) => {
|
|
app.setLoginItemSettings({
|
|
openAtLogin: enabled,
|
|
openAsHidden: false,
|
|
});
|
|
return { success: true };
|
|
});
|
|
|
|
// Badge count (macOS)
|
|
ipcMain.handle('set-badge-count', async (event, count: number) => {
|
|
if (process.platform === 'darwin') {
|
|
app.dock.setBadge(count > 0 ? String(count) : '');
|
|
}
|
|
return { success: true };
|
|
});
|
|
|
|
// Window controls
|
|
ipcMain.on('minimize-window', () => {
|
|
mainWindow?.minimize();
|
|
});
|
|
|
|
ipcMain.on('maximize-window', () => {
|
|
if (mainWindow?.isMaximized()) {
|
|
mainWindow?.unmaximize();
|
|
} else {
|
|
mainWindow?.maximize();
|
|
}
|
|
});
|
|
|
|
ipcMain.on('close-window', () => {
|
|
mainWindow?.close();
|
|
});
|
|
|
|
// App initialization
|
|
app.whenReady().then(() => {
|
|
createWindow();
|
|
createTray();
|
|
registerGlobalShortcuts();
|
|
|
|
// macOS - recreate window when dock icon clicked
|
|
app.on('activate', () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
createWindow();
|
|
} else {
|
|
mainWindow?.show();
|
|
if (process.platform === 'darwin') {
|
|
app.dock.show();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
app.on('window-all-closed', () => {
|
|
if (process.platform !== 'darwin') {
|
|
app.quit();
|
|
}
|
|
});
|
|
|
|
app.on('before-quit', () => {
|
|
app.isQuitting = true;
|
|
});
|
|
|
|
app.on('will-quit', () => {
|
|
globalShortcut.unregisterAll();
|
|
});
|
|
|
|
// Handle deep links (aethex:// protocol)
|
|
if (process.defaultApp) {
|
|
if (process.argv.length >= 2) {
|
|
app.setAsDefaultProtocolClient('aethex', process.execPath, [
|
|
path.resolve(process.argv[1]),
|
|
]);
|
|
}
|
|
} else {
|
|
app.setAsDefaultProtocolClient('aethex');
|
|
}
|
|
|
|
app.on('open-url', (event, url) => {
|
|
event.preventDefault();
|
|
console.log('Deep link:', url);
|
|
mainWindow?.webContents.send('deep-link', url);
|
|
});
|