diff options
15 files changed, 105 insertions, 26 deletions
diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js index 71b56be..273d151 100644 --- a/packages/meshbay-client/src/main.js +++ b/packages/meshbay-client/src/main.js @@ -339,6 +339,19 @@ let mainWindow = null; // files are the interface's, the main process has no i18n, and a second string // table is how two of them start disagreeing. let tray = null; +let trayLabels = null; +let trayTimer = null; +let nodeService = null; // assigned by registerBridge() + +const TRAY_FALLBACK = { + show: 'Show MeshBay', quit: 'Quit', + start_node: 'Start the node', stop_node: 'Stop the node', +}; + +// libappindicator offers no "menu is about to open" event, so a menu built once +// would show a stale Start/Stop for as long as the app runs. `systemctl --user +// show` is a few milliseconds and this only ticks while an indicator exists. +const TRAY_POLL_MS = 5000; function showFromTray() { if (!mainWindow) return; @@ -347,18 +360,51 @@ function showFromTray() { mainWindow.focus(); } +async function buildTrayMenu() { + const text = { ...TRAY_FALLBACK, ...(trayLabels || {}) }; + const items = [{ label: text.show, click: showFromTray }]; + + // Only when there is a daemon to act on: not installed means no entry at all, + // rather than a control that reports failure when used. + let status = null; + try { + status = nodeService ? await nodeService.status() : null; + } catch { + status = null; // unreadable state is the same as no control + } + if (status && status.supported && status.installed) { + const running = status.activeState === 'active'; + items.push({ type: 'separator' }); + items.push({ + label: running ? text.stop_node : text.start_node, + click: async () => { + try { + // `restart` starts a stopped unit, which is what systemd's restart + // means; there is no separate start verb to call. + if (running) await nodeService.stop(); + else await nodeService.restart(); + } catch (err) { + console.error('[MeshBay] tray: node action failed', err); + } + refreshTrayMenu(); // reflect the new state without waiting a tick + }, + }); + } + + items.push({ type: 'separator' }, + { label: text.quit, click: () => app.quit() }); + return Menu.buildFromTemplate(items); +} + +async function refreshTrayMenu() { + if (!tray) return; + tray.setContextMenu(await buildTrayMenu()); +} + function ensureTray(labels) { - const text = { - show: (labels && labels.show) || 'Show MeshBay', - quit: (labels && labels.quit) || 'Quit', - }; + if (labels) trayLabels = labels; // the person may have changed language if (tray) { - // Relabel in place: the person may have changed language since it was made. - tray.setContextMenu(Menu.buildFromTemplate([ - { label: text.show, click: showFromTray }, - { type: 'separator' }, - { label: text.quit, click: () => app.quit() }, - ])); + refreshTrayMenu(); return tray; } // In src/, not build/: electron-builder packages only `src/**` and `ui/**` @@ -366,17 +412,13 @@ function ensureTray(labels) { // missing from every installed one. tray = new Tray(path.join(__dirname, 'tray-icon.png')); tray.setToolTip('MeshBay'); - tray.setContextMenu(Menu.buildFromTemplate([ - { label: text.show, click: showFromTray }, - { type: 'separator' }, - { label: text.quit, click: () => app.quit() }, - ])); + refreshTrayMenu(); // Harmless where it does nothing (GNOME), useful on desktops that do send it. tray.on('click', showFromTray); + if (!trayTimer) trayTimer = setInterval(refreshTrayMenu, TRAY_POLL_MS); return tray; } - function createWindow() { const bounds = (config.window && config.window.width) ? config.window : { width: 1200, height: 800, @@ -1073,7 +1115,16 @@ function registerBridge() { // of the Node page. Deliberately not `probeNode()`: that asks the daemon's // own HTTP API, which cannot answer while the daemon is stopped or crash- // looping — exactly the states this panel exists to show and act on. - ipcMain.handle('node:service-status', async () => { + // The tray menu drives the same daemon controls as the Node page. Declared + // here because these close over registerBridge's helpers; the tray is created + // long after, so it reads them through this binding rather than duplicating + // the systemctl and Task Scheduler branches. + nodeService = { status: nodeServiceStatus, stop: nodeServiceStop, + restart: nodeServiceRestart }; + + ipcMain.handle('node:service-status', () => nodeServiceStatus()); + + async function nodeServiceStatus() { if (process.platform === 'win32') { const svc = await winServiceTaskStatus(); if (svc.installed) { @@ -1133,9 +1184,11 @@ function registerBridge() { }); }); }); - }); + } - ipcMain.handle('node:service-stop', async () => { + ipcMain.handle('node:service-stop', () => nodeServiceStop()); + + async function nodeServiceStop() { if (process.platform === 'win32') { const svc = await winServiceTaskStatus(); if (svc.installed) await winServiceTaskEnd(); @@ -1154,9 +1207,11 @@ function registerBridge() { }); }); return { stopped: true }; - }); + } - ipcMain.handle('node:service-restart', async () => { + ipcMain.handle('node:service-restart', () => nodeServiceRestart()); + + async function nodeServiceRestart() { if (process.platform === 'win32') { const svc = await winServiceTaskStatus(); if (svc.installed) await winServiceTaskEnd(); @@ -1181,7 +1236,7 @@ function registerBridge() { }); }); return { restarted: true }; - }); + } // Install / remove the Windows Startup-folder launcher, and query it. ipcMain.handle('node:autostart', async (_e, action) => { diff --git a/packages/meshbay-client/src/tray-icon.png b/packages/meshbay-client/src/tray-icon.png Binary files differindex 394604d..98fd63b 100644 --- a/packages/meshbay-client/src/tray-icon.png +++ b/packages/meshbay-client/src/tray-icon.png diff --git a/packages/meshbay-client/src/tray-icon@2x.png b/packages/meshbay-client/src/tray-icon@2x.png Binary files differindex 3e533fb..9cf613d 100644 --- a/packages/meshbay-client/src/tray-icon@2x.png +++ b/packages/meshbay-client/src/tray-icon@2x.png diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index 5857821..c7e46d8 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -244,8 +244,9 @@ function Nav({ user, theme, onThemeChange, onLogout, onMenuToggle, unreadCount, <div class="nav-right"> ${user && html`<${TransferWidget} />`} ${platform.capabilities.tray && html` - <button class="nav-tray" onClick=${() => platform.minimizeToTray( - { show: t('tray.show'), quit: t('tray.quit') })} + <button class="nav-tray" onClick=${() => platform.minimizeToTray({ + show: t('tray.show'), quit: t('tray.quit'), + start_node: t('tray.start_node'), stop_node: t('tray.stop_node') })} title=${t('nav.minimize_tray')} aria-label=${t('nav.minimize_tray')}> <${Icon} name="tray" /> </button> diff --git a/packages/meshbay-hub/src/meshbay_hub/static/icon.js b/packages/meshbay-hub/src/meshbay_hub/static/icon.js index 79a3a08..90d9194 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/icon.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/icon.js @@ -13,8 +13,11 @@ import { html } from './vendor/htm-preact.js'; const ICON_PATHS = { menu: ['M4 7h16M4 12h16M4 17h16'], - tray: ['M12 3.5v9', 'M8.5 9l3.5 3.5L15.5 9', - 'M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3'], + // A window with a chevron folding into it. Deliberately no shaft: a + // vertical stem above a receptacle is the download glyph, and the first + // attempt here read as one. + tray: ['M6 4h12a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V7a3 3 0 0 1 3-3z', + 'M8 10l4 4 4-4'], bell: ['M18 9a6 6 0 1 0-12 0c0 6-2.5 7.5-2.5 7.5h17S18 15 18 9', 'M10.3 20a2 2 0 0 0 3.4 0'], shield: ['M12 3l7.5 3v5.2c0 4.6-3.1 8.6-7.5 10.3-4.4-1.7-7.5-5.7-7.5-10.3V6z'], diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js index 24ff315..46168cf 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js @@ -17,6 +17,8 @@ export default { 'nav.minimize_tray': 'In den Infobereich minimieren', 'tray.show': 'MeshBay anzeigen', 'tray.quit': 'Beenden', + 'tray.start_node': 'Node starten', + 'tray.stop_node': 'Node stoppen', // Sidebar 'sidebar.my_groups': 'Meine Gruppen', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js index ce08f1f..2d38e4b 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js @@ -20,6 +20,8 @@ export default { 'nav.minimize_tray': 'Minimize to tray', 'tray.show': 'Show MeshBay', 'tray.quit': 'Quit', + 'tray.start_node': 'Start the node', + 'tray.stop_node': 'Stop the node', // Sidebar 'sidebar.my_groups': 'My Groups', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js index 7aa1d98..471ad82 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js @@ -16,6 +16,8 @@ export default { 'nav.minimize_tray': 'Minimizar en la bandeja', 'tray.show': 'Mostrar MeshBay', 'tray.quit': 'Salir', + 'tray.start_node': 'Iniciar el nodo', + 'tray.stop_node': 'Detener el nodo', // Sidebar 'sidebar.my_groups': 'Mis grupos', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js index b87cb81..48f26a2 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js @@ -16,6 +16,8 @@ export default { 'nav.minimize_tray': 'Réduire dans la zone de notification', 'tray.show': 'Afficher MeshBay', 'tray.quit': 'Quitter', + 'tray.start_node': 'Démarrer le nœud', + 'tray.stop_node': 'Arrêter le nœud', // Sidebar 'sidebar.my_groups': 'Mes groupes', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js index 242bdb4..564f34b 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js @@ -17,6 +17,8 @@ export default { 'nav.minimize_tray': 'Riduci nella barra di sistema', 'tray.show': 'Mostra MeshBay', 'tray.quit': 'Esci', + 'tray.start_node': 'Avvia il nodo', + 'tray.stop_node': 'Arresta il nodo', // Sidebar 'sidebar.my_groups': 'I miei gruppi', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js index 8c8f654..3eed305 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js @@ -17,6 +17,8 @@ export default { 'nav.minimize_tray': 'トレイに最小化', 'tray.show': 'MeshBay を表示', 'tray.quit': '終了', + 'tray.start_node': 'ノードを開始', + 'tray.stop_node': 'ノードを停止', // Sidebar 'sidebar.my_groups': 'マイグループ', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js index 566677d..711a22f 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js @@ -17,6 +17,8 @@ export default { 'nav.minimize_tray': 'Minimaliseren naar systeemvak', 'tray.show': 'MeshBay tonen', 'tray.quit': 'Afsluiten', + 'tray.start_node': 'Node starten', + 'tray.stop_node': 'Node stoppen', // Sidebar 'sidebar.my_groups': 'Mijn groepen', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js index 4e75670..67615a2 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js @@ -20,6 +20,8 @@ export default { 'nav.minimize_tray': 'Zminimalizuj do zasobnika', 'tray.show': 'Pokaż MeshBay', 'tray.quit': 'Zakończ', + 'tray.start_node': 'Uruchom węzeł', + 'tray.stop_node': 'Zatrzymaj węzeł', // Sidebar 'sidebar.my_groups': 'Moje grupy', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js index a9fd638..2d07994 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js @@ -18,6 +18,8 @@ export default { 'nav.minimize_tray': 'Minimizar na bandeja', 'tray.show': 'Mostrar o MeshBay', 'tray.quit': 'Sair', + 'tray.start_node': 'Iniciar o nó', + 'tray.stop_node': 'Parar o nó', // Sidebar 'sidebar.my_groups': 'Meus grupos', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js index ca39837..fd0c7f9 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js @@ -17,6 +17,8 @@ export default { 'nav.minimize_tray': '最小化到托盘', 'tray.show': '显示 MeshBay', 'tray.quit': '退出', + 'tray.start_node': '启动节点', + 'tray.stop_node': '停止节点', // Sidebar 'sidebar.my_groups': '我的群组', |