summaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-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
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/app.js7
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/icon.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/de.js3
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/en.js3
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/es.js3
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js3
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/it.js3
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js3
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js3
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js3
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js3
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js3
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/platform.js20
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/style.css25
18 files changed, 152 insertions, 7 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
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js
index 5311403..5857821 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js
@@ -243,6 +243,13 @@ function Nav({ user, theme, onThemeChange, onLogout, onMenuToggle, unreadCount,
</div>
<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') })}
+ title=${t('nav.minimize_tray')} aria-label=${t('nav.minimize_tray')}>
+ <${Icon} name="tray" />
+ </button>
+ `}
${user && html`
<a class="nav-notif" href="#/" title=${t('notif.title')}>
<${Icon} name="bell" />${unreadCount > 0
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/icon.js b/packages/meshbay-hub/src/meshbay_hub/static/icon.js
index cc28110..79a3a08 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/icon.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/icon.js
@@ -13,6 +13,8 @@ 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'],
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 49b5f9e..24ff315 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
@@ -14,6 +14,9 @@ export default {
'nav.dark_mode': 'Dunkles Design',
'nav.logout': 'Abmelden',
'nav.login': 'Anmelden',
+ 'nav.minimize_tray': 'In den Infobereich minimieren',
+ 'tray.show': 'MeshBay anzeigen',
+ 'tray.quit': 'Beenden',
// 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 7ea8989..ce08f1f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
@@ -17,6 +17,9 @@ export default {
'nav.dark_mode': 'Dark mode',
'nav.logout': 'Logout',
'nav.login': 'Login',
+ 'nav.minimize_tray': 'Minimize to tray',
+ 'tray.show': 'Show MeshBay',
+ 'tray.quit': 'Quit',
// 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 6109d4f..7aa1d98 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
@@ -13,6 +13,9 @@ export default {
'nav.dark_mode': 'Tema oscuro',
'nav.logout': 'Cerrar sesión',
'nav.login': 'Iniciar sesión',
+ 'nav.minimize_tray': 'Minimizar en la bandeja',
+ 'tray.show': 'Mostrar MeshBay',
+ 'tray.quit': 'Salir',
// 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 926f64a..b87cb81 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
@@ -13,6 +13,9 @@ export default {
'nav.dark_mode': 'Thème sombre',
'nav.logout': 'Déconnexion',
'nav.login': 'Connexion',
+ 'nav.minimize_tray': 'Réduire dans la zone de notification',
+ 'tray.show': 'Afficher MeshBay',
+ 'tray.quit': 'Quitter',
// 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 f7011ec..242bdb4 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -14,6 +14,9 @@ export default {
'nav.dark_mode': 'Tema scuro',
'nav.logout': 'Esci',
'nav.login': 'Accedi',
+ 'nav.minimize_tray': 'Riduci nella barra di sistema',
+ 'tray.show': 'Mostra MeshBay',
+ 'tray.quit': 'Esci',
// 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 592f939..8c8f654 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
@@ -14,6 +14,9 @@ export default {
'nav.dark_mode': 'ダークテーマ',
'nav.logout': 'ログアウト',
'nav.login': 'ログイン',
+ 'nav.minimize_tray': 'トレイに最小化',
+ 'tray.show': 'MeshBay を表示',
+ 'tray.quit': '終了',
// 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 eba234e..566677d 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
@@ -14,6 +14,9 @@ export default {
'nav.dark_mode': 'Donker thema',
'nav.logout': 'Afmelden',
'nav.login': 'Aanmelden',
+ 'nav.minimize_tray': 'Minimaliseren naar systeemvak',
+ 'tray.show': 'MeshBay tonen',
+ 'tray.quit': 'Afsluiten',
// 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 999e689..4e75670 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
@@ -17,6 +17,9 @@ export default {
'nav.dark_mode': 'Motyw ciemny',
'nav.logout': 'Wyloguj',
'nav.login': 'Zaloguj',
+ 'nav.minimize_tray': 'Zminimalizuj do zasobnika',
+ 'tray.show': 'Pokaż MeshBay',
+ 'tray.quit': 'Zakończ',
// 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 79d2079..a9fd638 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
@@ -15,6 +15,9 @@ export default {
'nav.dark_mode': 'Tema escuro',
'nav.logout': 'Sair',
'nav.login': 'Entrar',
+ 'nav.minimize_tray': 'Minimizar na bandeja',
+ 'tray.show': 'Mostrar o MeshBay',
+ 'tray.quit': 'Sair',
// 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 d67c34b..ca39837 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
@@ -14,6 +14,9 @@ export default {
'nav.dark_mode': '深色主题',
'nav.logout': '退出登录',
'nav.login': '登录',
+ 'nav.minimize_tray': '最小化到托盘',
+ 'tray.show': '显示 MeshBay',
+ 'tray.quit': '退出',
// Sidebar
'sidebar.my_groups': '我的群组',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/platform.js b/packages/meshbay-hub/src/meshbay_hub/static/platform.js
index 8f5d484..b009ebe 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/platform.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/platform.js
@@ -48,6 +48,10 @@ export const capabilities = {
nativeSave: Boolean(bridge && bridge.capabilities && bridge.capabilities.nativeSave),
// Cast decrypted video to a device on the same LAN via a local HTTP relay.
lanCast: Boolean(bridge && bridge.capabilities && bridge.capabilities.lanCast),
+ // Hide the window to a system tray indicator. Declared per-OS by the app, not
+ // by this file: a tray is only worth offering where the desktop actually shows
+ // one, so the browser and any platform without support simply never see it.
+ tray: Boolean(bridge && bridge.capabilities && bridge.capabilities.tray),
};
/**
@@ -454,9 +458,21 @@ export const cast = {
},
};
+/**
+ * Hide the window to the system tray indicator.
+ *
+ * A no-op without the bridge, so the same nav renders in a browser without a
+ * guard at the call site -- though `capabilities.tray` keeps the button itself
+ * out of a browser, where there is no tray to minimise into.
+ */
+export async function minimizeToTray(labels) {
+ if (!bridge || !bridge.minimizeToTray) return false;
+ return bridge.minimizeToTray(labels);
+}
+
export default { isNative, hubBase, capabilities, secrets, nativeSave,
apiFetch, device, bridgeMessage, folder, rootPicker, node,
- cast };
+ cast, minimizeToTray };
// Also a global, because `transport.js` is loaded as a classic script — it
// predates the module graph and exposes `MeshBayTransport` the same way. The
@@ -466,5 +482,5 @@ if (typeof window !== 'undefined') {
window.MeshBayPlatform = { isNative, hubBase, capabilities, secrets,
nativeSave, apiFetch, device,
bridgeMessage, folder, rootPicker, node,
- cast };
+ cast, minimizeToTray };
}
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index 3d9206d..2770dd7 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -2075,6 +2075,31 @@ a.transfer-name {
}
}
+/* ── Minimise to tray ────────────────────────────────────────────────────── */
+
+/* Monochrome on purpose: it sits next to the bell, and the bell's badge is the
+ only thing in this corner allowed to pull the eye. Inherits --nav-text like
+ every other nav control, so it follows the theme rather than carrying its own
+ colour, and the glyph is a stroked `currentColor` icon with nothing to tint. */
+.nav-tray {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: none;
+ border: 0;
+ padding: 4px;
+ margin-right: 2px;
+ color: var(--nav-text);
+ cursor: pointer;
+ border-radius: 6px;
+ opacity: 0.75;
+}
+.nav-tray:hover,
+.nav-tray:focus-visible {
+ opacity: 1;
+ background: rgba(255, 255, 255, 0.1);
+}
+
/* ── Notification bell ───────────────────────────────────────────────────── */
.nav-notif {