diff --git a/apps/overlay/electron/main.js b/apps/overlay/electron/main.js
index b6f512c..cbfa9d8 100644
--- a/apps/overlay/electron/main.js
+++ b/apps/overlay/electron/main.js
@@ -25,6 +25,7 @@ let clickThrough = false;
// default for the single-controller window is 720×540 — a clean capture size.
const DEFAULT_WINDOW_SIZE = { width: 720, height: 540 };
const MULTI_WINDOW_SIZE = { width: 1100, height: 520 };
+const LOBBY_WINDOW_SIZE = { width: 820, height: 660 };
let windowStatePath = null; // resolved once app is ready (needs userData path)
let saveWindowSizeTimer = null;
@@ -114,8 +115,11 @@ function upsertHidController(d, connected) {
function hidControllerList() { return [...hidControllers.values()]; }
function broadcastHidControllers() {
- if (inventoryWindow && !inventoryWindow.isDestroyed()) {
- inventoryWindow.webContents.send('hid-controllers-snapshot', hidControllerList());
+ const list = hidControllerList();
+ // The inventory window and the lobby both render this (the lobby shows each
+ // seat's per-unit serial/MAC — see its Controllers panel).
+ for (const w of [inventoryWindow, mainWindow]) {
+ if (w && !w.isDestroyed()) w.webContents.send('hid-controllers-snapshot', list);
}
}
@@ -139,6 +143,8 @@ function openInventoryWindow() {
// Which size profile this launch uses. Single source of truth so the window
// creation and the "reset to default" handler agree on key + default size.
function getSizeMode() {
+ const useLobby = process.env.OVERLAY_LOBBY === '1' || process.argv.includes('--lobby');
+ if (useLobby) return { stateKey: 'lobby', defaultSize: LOBBY_WINDOW_SIZE };
const useMulti = process.env.OVERLAY_MULTI === '1' || process.argv.includes('--multi');
return {
stateKey: useMulti ? 'multi' : 'main',
@@ -173,7 +179,7 @@ function createWindow() {
},
});
- const entry = stateKey === 'multi' ? 'multi.html' : 'index.html';
+ const entry = stateKey === 'lobby' ? 'lobby.html' : stateKey === 'multi' ? 'multi.html' : 'index.html';
mainWindow.loadFile(path.join(__dirname, '..', 'src', entry));
mainWindow.once('ready-to-show', () => {
@@ -354,6 +360,7 @@ app.whenReady().then(() => {
gyro: { file: 'gyro-window.html', width: 260, height: 260 },
axis: { file: 'axis-window.html', width: 280, height: 90 },
roll: { file: 'roll-window.html', width: 240, height: 170 },
+ controllers: { file: 'controllers-window.html', width: 320, height: 460 },
};
ipcMain.handle('open-hud-window', async (_event, { kind, profile, greenScreen }) => {
@@ -420,6 +427,11 @@ app.whenReady().then(() => {
if (w && !w.isDestroyed()) w.webContents.send('hud-state-update', state);
});
+ // Controllers window → main overlay: a row was clicked; switch the shown pad.
+ ipcMain.on('controller-select', (_event, payload) => {
+ if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('controller-select', payload);
+ });
+
// A detached window's close button uses this to close itself (frameless
// windows have no titlebar close affordance).
ipcMain.on('close-this-window', (event) => {
diff --git a/apps/overlay/electron/preload.js b/apps/overlay/electron/preload.js
index b5b4d4e..269d218 100644
--- a/apps/overlay/electron/preload.js
+++ b/apps/overlay/electron/preload.js
@@ -64,4 +64,11 @@ contextBridge.exposeInMainWorld('electronAPI', {
// Periodic snapshot of currently-present HID controllers (with serials).
onHidControllersSnapshot: (callback) =>
ipcRenderer.on('hid-controllers-snapshot', (_, list) => callback(list)),
+
+ // ── Controllers window → overlay selection ──
+ // The detached Controllers list sends the clicked controller's key; the main
+ // overlay receives it and switches which controller drives the visualization.
+ selectController: (payload) => ipcRenderer.send('controller-select', payload),
+ onControllerSelect: (callback) =>
+ ipcRenderer.on('controller-select', (_, payload) => callback(payload)),
});
diff --git a/apps/overlay/package.json b/apps/overlay/package.json
index d75abd3..26011be 100644
--- a/apps/overlay/package.json
+++ b/apps/overlay/package.json
@@ -6,10 +6,12 @@
"scripts": {
"prestart": "node scripts/copy-three.js && node scripts/copy-workspace.js",
"prestart:multi": "node scripts/copy-three.js && node scripts/copy-workspace.js",
+ "prestart:lobby": "node scripts/copy-three.js && node scripts/copy-workspace.js",
"prepackage": "node scripts/copy-three.js && node scripts/copy-workspace.js",
"premake": "node scripts/copy-three.js && node scripts/copy-workspace.js",
"start": "electron .",
"start:multi": "electron . --multi",
+ "start:lobby": "electron . --lobby",
"make": "electron-forge make",
"make:dmg": "npm run package && bash scripts/make-dmg.sh",
"package": "electron-forge package"
diff --git a/apps/overlay/src/controllers-window.html b/apps/overlay/src/controllers-window.html
new file mode 100644
index 0000000..a84c48a
--- /dev/null
+++ b/apps/overlay/src/controllers-window.html
@@ -0,0 +1,49 @@
+
+
+
+
+Controllers
+
+
+
+
+
CONTROLLERS
+
+
click a controller to show it in the overlay · ● in use now · drag to move
+
+
+
+
+
+
diff --git a/apps/overlay/src/index.html b/apps/overlay/src/index.html
index ff6af63..f8a63c3 100644
--- a/apps/overlay/src/index.html
+++ b/apps/overlay/src/index.html
@@ -101,6 +101,21 @@
letter-spacing: 0.5px;
}
+ /* Controllers list (which controller drives the overlay) */
+ #ovl-ctrl-list { display: flex; flex-direction: column; gap: 5px; max-height: 210px; overflow-y: auto; }
+ .ovl-ctrl-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 8px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.09); cursor: pointer; }
+ .ovl-ctrl-row:hover { border-color: rgba(255,255,255,0.28); }
+ .ovl-ctrl-row.sel { border-color: #6ce08f; box-shadow: 0 0 0 1px rgba(108,224,143,.4); }
+ .ovl-dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; background: #39404e; transition: background .08s, box-shadow .08s; }
+ .ovl-dot.on { background: #6ce08f; box-shadow: 0 0 7px 1px #6ce08f; }
+ .ovl-ctrl-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
+ .ovl-ctrl-name { font-size: 12px; font-weight: 600; color: #e7ecf5; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+ .ovl-ctrl-sub { font: 9px/1.3 ui-monospace, monospace; color: #8894a8; }
+ .ovl-tag { flex: 0 0 auto; font: 800 8px/1 ui-monospace, monospace; letter-spacing: .6px; padding: 3px 5px; border-radius: 4px; }
+ .ovl-tag.active { color: #06140c; background: #6ce08f; }
+ .ovl-tag.available { color: #c9d6ff; background: rgba(110,168,255,0.18); border: 1px solid rgba(110,168,255,0.4); }
+ .ovl-ctrl-empty { font: 10px/1.4 ui-monospace, monospace; color: #6a7184; text-align: center; padding: 8px; }
+
.setting-row {
display: flex;
align-items: center;
@@ -919,7 +934,12 @@
Controller Overlay
-
+
+
+
+
+
+