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.js30
-rw-r--r--packages/meshbay-client/src/preload.js5
2 files changed, 33 insertions, 2 deletions
diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js
index 27c81b8..0a5723b 100644
--- a/packages/meshbay-client/src/main.js
+++ b/packages/meshbay-client/src/main.js
@@ -330,6 +330,12 @@ let mainWindow = null;
// ── System tray ──────────────────────────────────────────────────────────────
// Linux and Windows.
//
+// Created at launch, not on the first "minimise to tray". An indicator that
+// only appears once you have already hidden the window is one you cannot use
+// to find the application, which is most of what a tray is for -- and on
+// Windows it made the app look like it had no tray presence at all until you
+// went looking for one.
+//
// 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
@@ -342,6 +348,11 @@ let mainWindow = null;
let tray = null;
let trayLabels = null;
let trayTimer = null;
+
+// The same test preload.js publishes as `capabilities.tray`. macOS is excluded
+// deliberately: it has a menu bar rather than a tray, and the window controls
+// there already do what hiding to an indicator does elsewhere.
+const trayOS = () => process.platform === 'linux' || process.platform === 'win32';
let nodeService = null; // assigned by registerBridge()
const TRAY_FALLBACK = {
@@ -649,13 +660,24 @@ function registerBridge() {
// 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) => {
- const trayOS = process.platform === 'linux' || process.platform === 'win32';
- if (!trayOS || !mainWindow) return false;
+ if (!trayOS() || !mainWindow) return false;
ensureTray(labels);
mainWindow.hide();
return true;
});
+ // The renderer sends these once it has a locale, and again after a language
+ // change (which reloads the page, so the same call covers both). Until then
+ // the tray created at launch shows TRAY_FALLBACK, in English, for the
+ // fraction of a second the catalogue takes to arrive -- the alternative,
+ // waiting for the renderer before creating it at all, is the behaviour this
+ // replaces.
+ ipcMain.handle('tray:labels', (_e, labels) => {
+ if (!trayOS()) return false;
+ ensureTray(labels);
+ return true;
+ });
+
ipcMain.handle('device:ensure', () => ensureDeviceKey());
ipcMain.handle('device:public', () => {
const key = deviceKey();
@@ -1660,7 +1682,11 @@ if (!app.requestSingleInstanceLock()) {
app.whenReady().then(() => {
registerUiProtocol();
+ // Before ensureTray: buildTrayMenu reads `nodeService`, which registerBridge
+ // assigns, so creating the tray after it means the Start/Stop entry is on
+ // the very first menu rather than appearing one poll later.
registerBridge();
+ if (trayOS()) ensureTray();
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
diff --git a/packages/meshbay-client/src/preload.js b/packages/meshbay-client/src/preload.js
index b390bfb..13d4f37 100644
--- a/packages/meshbay-client/src/preload.js
+++ b/packages/meshbay-client/src/preload.js
@@ -50,6 +50,11 @@ contextBridge.exposeInMainWorld('meshbay', {
// Labels are passed in because the main process has no i18n; see main.js.
minimizeToTray: (labels) => ipcRenderer.invoke('window:minimize-to-tray', labels),
+ // The tray now exists from launch, so its menu needs translating before
+ // anyone minimises into it. Sent once the interface has its catalogue, and
+ // again after a language change.
+ setTrayLabels: (labels) => ipcRenderer.invoke('tray:labels', 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),