← back to Desktop Dotbar
dotbar: render a strip on EVERY display (Arrange button reachable on both monitors)
2f67aa2df96cae050d352ccc102fb19f81c3f3c5 · 2026-09-16 10:42:58 -0700 · Steve Abrams
One BrowserWindow per screen, pinned to each display's top edge; dropdown-open tracked
per-window, grip-resize applied to all; rebuilds on monitor plug/unplug. (Steve 2026-09-16)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Szengr4hMUDtYdw3jSCfA
Files touched
M .gitignoreM electron-main.js
Diff
commit 2f67aa2df96cae050d352ccc102fb19f81c3f3c5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 16 10:42:58 2026 -0700
dotbar: render a strip on EVERY display (Arrange button reachable on both monitors)
One BrowserWindow per screen, pinned to each display's top edge; dropdown-open tracked
per-window, grip-resize applied to all; rebuilds on monitor plug/unplug. (Steve 2026-09-16)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Szengr4hMUDtYdw3jSCfA
---
.gitignore | 1 +
electron-main.js | 93 ++++++++++++++++++++++++++++++++++++++++----------------
2 files changed, 67 insertions(+), 27 deletions(-)
diff --git a/.gitignore b/.gitignore
index 3a43e88..389368a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ tmp/
.chrome-profile/
debug.log
.barh
+electron-main.js.bak-*
diff --git a/electron-main.js b/electron-main.js
index 1febbbd..5a6768d 100644
--- a/electron-main.js
+++ b/electron-main.js
@@ -1,6 +1,12 @@
// Electron shell: a true always-on-top strip across the top of the desktop.
// Spawns the local server, then floats a frameless bar above every window
// (including fullscreen) at 'screen-saver' level. Grows only when a dropdown opens.
+//
+// MULTI-DISPLAY (Steve 2026-09-16): one strip PER display, each pinned to the top edge of
+// its OWN screen, so the dot chips + the ⧉ Arrange button are reachable on every monitor
+// (the button used to live only on the primary/left screen while Steve works on the right).
+// Each strip is an independent BrowserWindow; open-state (dropdown grow) is tracked per window,
+// and grip-resize applies to all strips so they stay the same height.
'use strict';
const { app, BrowserWindow, ipcMain, screen } = require('electron');
const { spawn } = require('child_process');
@@ -12,9 +18,10 @@ const DIR = __dirname;
const PANEL_H = 360;
const BAR_DEFAULT = 48, BAR_MIN = 22, BAR_MAX = 140;
const BARH_FILE = path.join(DIR, '.barh');
-let win, serverProc, PORT = null;
-let isOpen = false; // is a dropdown panel open
-let barH = readBarH(); // resizable strip height (persisted)
+let wins = []; // one strip per display
+let serverProc, PORT = null;
+const openState = new Map(); // win.id -> is that strip's dropdown open
+let barH = readBarH(); // resizable strip height (persisted, shared)
function clampBar(h) { return Math.max(BAR_MIN, Math.min(BAR_MAX, Math.round(h || BAR_DEFAULT))); }
function readBarH() { try { return clampBar(parseInt(fs.readFileSync(BARH_FILE, 'utf8'), 10)); } catch { return BAR_DEFAULT; } }
@@ -46,49 +53,81 @@ async function ensureServer() {
return portFromFile() || 9787;
}
-function bounds(open) {
- const wa = screen.getPrimaryDisplay().workArea; // excludes the menu bar
+// Bounds for a strip on a given display: full display width, at its workArea top (below the menu
+// bar, which only the primary display has). Grows downward by PANEL_H when its dropdown is open.
+function boundsFor(display, open) {
+ const wa = display.workArea;
return { x: wa.x, y: wa.y, width: wa.width, height: open ? barH + PANEL_H : barH };
}
+function displayOf(w) {
+ return screen.getAllDisplays().find(d => d.id === w._displayId) || screen.getPrimaryDisplay();
+}
+function raise(w) { try { if (!w.isDestroyed()) { w.setAlwaysOnTop(true, 'screen-saver'); w.moveTop(); } } catch {} }
+function applyBoundsFor(w) {
+ if (!w || w.isDestroyed()) return;
+ w.setBounds(boundsFor(displayOf(w), !!openState.get(w.id)));
+ raise(w);
+}
+function pushBarH(w) { try { if (!w.isDestroyed()) w.webContents.send('dotbar:barH', barH); } catch {} }
-// Apply the current barH (+ open state) to the window and tell the renderer its strip height.
-function applyBounds() { if (!win) return; win.setBounds(bounds(isOpen)); try { win.setAlwaysOnTop(true, 'screen-saver'); win.moveTop(); } catch {} }
-function pushBarH() { if (win) try { win.webContents.send('dotbar:barH', barH); } catch {} }
-// setBarH fires on EVERY pointermove during a grip drag, so keep it cheap: resize the window and
-// mirror the height to the renderer live, but do NOT re-assert always-on-top per frame (the 2s
-// `raise` interval covers that) and do NOT hit the disk per frame — persist once, debounced, after
-// the drag settles. Resize is the sole affordance now (keyboard resize was dropped, DTD 2026-09-16).
+// setBarH fires on EVERY pointermove during a grip drag, so keep it cheap: resize each strip and
+// mirror the height to its renderer live, but do NOT re-assert always-on-top per frame (the 2s
+// raise interval covers that) and do NOT hit the disk per frame — persist once, debounced.
let _persistTimer = null;
function setBarH(h) {
barH = clampBar(h);
- if (win) win.setBounds(bounds(isOpen)); // live resize only; no per-frame moveTop
- pushBarH();
+ for (const w of wins) {
+ if (w.isDestroyed()) continue;
+ w.setBounds(boundsFor(displayOf(w), !!openState.get(w.id))); // live resize only; no per-frame moveTop
+ pushBarH(w);
+ }
clearTimeout(_persistTimer); _persistTimer = setTimeout(writeBarH, 300);
}
-async function createWindow() {
- PORT = await ensureServer();
- win = new BrowserWindow({
- ...bounds(false),
+function createBarFor(display) {
+ const win = new BrowserWindow({
+ ...boundsFor(display, false),
type: 'panel', // NSPanel: floats above normal windows, non-activating
frame: false, resizable: false, movable: true, minimizable: false, maximizable: false,
fullscreenable: false, skipTaskbar: true, hasShadow: false, transparent: false,
focusable: false, backgroundColor: '#14161a',
webPreferences: { preload: path.join(DIR, 'preload.js'), contextIsolation: true, nodeIntegration: false },
});
+ win._displayId = display.id;
win.setAlwaysOnTop(true, 'screen-saver'); // above normal + fullscreen windows
win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
win.loadURL(`http://127.0.0.1:${PORT}/`);
- // Re-assert top without the off→on toggle (which caused a visible drop-flicker).
- // setAlwaysOnTop(true, ...) re-elevates on its own, including after setBounds.
- const raise = () => { try { win.setAlwaysOnTop(true, 'screen-saver'); win.moveTop(); } catch {} };
const dbg = (tag) => { if (process.env.DOTBAR_DEBUG !== '1') return; try { fs.appendFileSync(path.join(DIR, 'debug.log'),
- JSON.stringify({ tag, t: new Date().toISOString(), bounds: win.getBounds(), visible: win.isVisible(), onTop: win.isAlwaysOnTop() }) + '\n'); } catch {} };
- win.webContents.on('did-finish-load', () => { raise(); pushBarH(); dbg('did-finish-load'); });
+ JSON.stringify({ tag, id: win.id, t: new Date().toISOString(), bounds: win.isDestroyed() ? null : win.getBounds() }) + '\n'); } catch {} };
+ win.webContents.on('did-finish-load', () => { raise(win); pushBarH(win); dbg('did-finish-load'); });
win.webContents.on('did-fail-load', (_e, code, desc) => dbg('FAIL:' + code + ':' + desc));
- setInterval(raise, 2000); // re-assert top above other apps
- ipcMain.on('dotbar:setOpen', (_e, open) => { isOpen = !!open; applyBounds(); });
- ipcMain.on('dotbar:setBarH', (_e, h) => setBarH(h)); // absolute height (grip edge-drag)
+ wins.push(win);
+ return win;
+}
+
+// Tear down and recreate every strip — used on monitor plug/unplug so a display always has its bar
+// and a removed display leaves no orphan window.
+function rebuildBars() {
+ for (const w of wins) { try { if (!w.isDestroyed()) w.destroy(); } catch {} }
+ wins = []; openState.clear();
+ for (const d of screen.getAllDisplays()) createBarFor(d);
+}
+
+async function createWindows() {
+ PORT = await ensureServer();
+ for (const d of screen.getAllDisplays()) createBarFor(d);
+ setInterval(() => wins.forEach(raise), 2000); // re-assert top above other apps, every strip
+ // React to monitor changes (a display added/removed, or geometry changed e.g. arrangement swap).
+ screen.on('display-added', rebuildBars);
+ screen.on('display-removed', rebuildBars);
+ // Per-strip dropdown open/grow: grow the window that actually sent the event, not a global one.
+ ipcMain.on('dotbar:setOpen', (e, open) => {
+ const w = BrowserWindow.fromWebContents(e.sender);
+ if (!w) return;
+ openState.set(w.id, !!open);
+ applyBoundsFor(w);
+ });
+ ipcMain.on('dotbar:setBarH', (_e, h) => setBarH(h)); // grip drag: applies to all strips
ipcMain.on('dotbar:quit', () => app.quit());
}
@@ -96,6 +135,6 @@ app.on('window-all-closed', () => app.quit());
app.on('before-quit', () => { if (serverProc) try { serverProc.kill(); } catch {} });
app.whenReady().then(() => {
if (app.dock) app.dock.hide(); // accessory app: panels float above active apps
- createWindow().catch(e => console.log('DOTBAR: createWindow ERROR', e && e.stack || e));
+ createWindows().catch(e => console.log('DOTBAR: createWindows ERROR', e && e.stack || e));
});
process.on('uncaughtException', e => console.log('DOTBAR: uncaught', e && e.stack || e));
← 158144e auto-data-snapshot: 2026-09-16T10:40:23 (1 data files) — ele
·
back to Desktop Dotbar
·
dotbar: surface Arrange outcome on the button instead of swa 534bea8 →