aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-client
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-client')
-rw-r--r--packages/meshbay-client/src/main.js68
-rw-r--r--packages/meshbay-client/src/preload.js7
-rw-r--r--packages/meshbay-client/src/tray-icon.pngbin0 -> 415 bytes
-rw-r--r--packages/meshbay-client/src/tray-icon@2x.pngbin0 -> 755 bytes
4 files changed, 70 insertions, 5 deletions
diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js
index e996829..71b56be 100644
--- a/packages/meshbay-client/src/main.js
+++ b/packages/meshbay-client/src/main.js
@@ -20,7 +20,8 @@
'use strict';
-const { app, BrowserWindow, dialog, ipcMain, protocol, safeStorage, shell } =
+const { app, BrowserWindow, dialog, ipcMain, Menu, protocol, safeStorage,
+ shell, Tray } =
require('electron');
const { execFile, spawn } = require('node:child_process');
const crypto = require('node:crypto');
@@ -326,6 +327,56 @@ const HUB_FETCH_TIMEOUT_MS = 30000;
let mainWindow = null;
+// ── System tray ──────────────────────────────────────────────────────────────
+// Linux only for now; Windows is being done on that OS.
+//
+// The context menu is not optional. Under libappindicator -- which is how GNOME
+// shows a tray at all, via the AppIndicator extension -- `tray.on('click')`
+// never fires: the indicator only opens its menu. A tray whose only affordance
+// was a click would be inert on the one desktop this targets.
+//
+// Labels arrive from the renderer rather than being translated here. The locale
+// 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;
+
+function showFromTray() {
+ if (!mainWindow) return;
+ if (!mainWindow.isVisible()) mainWindow.show();
+ if (mainWindow.isMinimized()) mainWindow.restore();
+ mainWindow.focus();
+}
+
+function ensureTray(labels) {
+ const text = {
+ show: (labels && labels.show) || 'Show MeshBay',
+ quit: (labels && labels.quit) || 'Quit',
+ };
+ 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() },
+ ]));
+ return tray;
+ }
+ // In src/, not build/: electron-builder packages only `src/**` and `ui/**`
+ // (package.json `files`), so an icon under build/ exists in a dev run and is
+ // 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() },
+ ]));
+ // Harmless where it does nothing (GNOME), useful on desktops that do send it.
+ tray.on('click', showFromTray);
+ return tray;
+}
+
+
function createWindow() {
const bounds = (config.window && config.window.width) ? config.window : {
width: 1200, height: 800,
@@ -541,6 +592,16 @@ function registerBridge() {
} catch { return null; }
});
+ // Hide, never close: `window-all-closed` quits the app, and closing here would
+ // make "minimise to tray" mean "exit". A hidden window keeps the session, the
+ // transfers and the node connection exactly as they were.
+ ipcMain.handle('window:minimize-to-tray', (_e, labels) => {
+ if (process.platform !== 'linux' || !mainWindow) return false;
+ ensureTray(labels);
+ mainWindow.hide();
+ return true;
+ });
+
ipcMain.handle('device:ensure', () => ensureDeviceKey());
ipcMain.handle('device:public', () => {
const key = deviceKey();
@@ -1477,10 +1538,7 @@ if (!app.requestSingleInstanceLock()) {
app.quit();
} else {
app.on('second-instance', () => {
- if (mainWindow) {
- if (mainWindow.isMinimized()) mainWindow.restore();
- mainWindow.focus();
- }
+ showFromTray();
});
app.whenReady().then(() => {
diff --git a/packages/meshbay-client/src/preload.js b/packages/meshbay-client/src/preload.js
index 97a5063..1aa16e5 100644
--- a/packages/meshbay-client/src/preload.js
+++ b/packages/meshbay-client/src/preload.js
@@ -41,8 +41,15 @@ contextBridge.exposeInMainWorld('meshbay', {
localFolders: true,
nativeSave: true,
lanCast: true,
+ // Linux only for now -- Windows is being done on that OS. Declared per
+ // platform rather than always: a button that hides the window somewhere the
+ // desktop shows no indicator would hide it for good.
+ tray: process.platform === 'linux',
},
+ // Labels are passed in because the main process has no i18n; see main.js.
+ minimizeToTray: (labels) => ipcRenderer.invoke('window:minimize-to-tray', labels),
+
// Ask the main process to call the hub. The renderer has an `app://` origin,
// which CORS refuses and which is not a credential anyway.
fetch: (url, init) => ipcRenderer.invoke('hub:fetch', url, init),
diff --git a/packages/meshbay-client/src/tray-icon.png b/packages/meshbay-client/src/tray-icon.png
new file mode 100644
index 0000000..394604d
--- /dev/null
+++ b/packages/meshbay-client/src/tray-icon.png
Binary files differ
diff --git a/packages/meshbay-client/src/tray-icon@2x.png b/packages/meshbay-client/src/tray-icon@2x.png
new file mode 100644
index 0000000..3e533fb
--- /dev/null
+++ b/packages/meshbay-client/src/tray-icon@2x.png
Binary files differ