diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-05 00:15:55 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-05 13:32:26 +0200 |
| commit | b3362e6b25bc77dfe94f4c088a865e6fd2be2601 (patch) | |
| tree | f783bdab2e3d9b5a5aa3465b049fa73e8fd1d478 /packages/meshbay-client/src | |
| parent | 89ed51e9b7ea38bd4475ce3a234f8c783b3d9b09 (diff) | |
| download | meshbay-b3362e6b25bc77dfe94f4c088a865e6fd2be2601.tar.gz | |
feat(client): tray menu starts and stops the node, and a clearer icon
The first icon was an arrow dropping into a receptacle, which is the download
glyph -- a vertical stem above a container reads that way whatever the context.
Replaced with a window folding a chevron into itself: no stem, and the frame
says which object is being minimised. Applied to both the nav button and the
panel indicator, which carried the same wrong shape.
The menu now offers Start or Stop for the node daemon, chosen from its actual
state and shown only when there is a daemon to act on: `supported && installed`,
so a machine with no node installed gets no entry rather than a control that
fails when used. `restart` is the start verb -- systemd's restart starts a
stopped unit, and there is no separate one to call.
The three service handlers become named functions so the tray drives exactly
what the Node page drives, instead of a second copy of the systemctl and Task
Scheduler branches. A read of the state that throws is treated as no control
at all.
The menu is rebuilt on a 5s timer while an indicator exists, and again straight
after an action. libappindicator has no "menu is about to open" event, so a menu
built once would show a stale Start/Stop for the life of the process; `systemctl
--user show` costs a few milliseconds.
Locales: tray.start_node / tray.stop_node in all ten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtfG7z6wHWj8RKHCvxQtY1
Diffstat (limited to 'packages/meshbay-client/src')
| -rw-r--r-- | packages/meshbay-client/src/main.js | 99 | ||||
| -rw-r--r-- | packages/meshbay-client/src/tray-icon.png | bin | 415 -> 504 bytes | |||
| -rw-r--r-- | packages/meshbay-client/src/tray-icon@2x.png | bin | 755 -> 967 bytes |
3 files changed, 77 insertions, 22 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 |