diff --git a/desktop/main.js b/desktop/main.js index fe364b2..627a8ce 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -91,10 +91,12 @@ const activeNotifs = new Set(); ipcMain.handle('reply-notification', async (_e, payload = {}) => { if (!Notification.isSupported()) return null; // Do NOT block the toast on the avatar download (that made desktop notifications lag ~15s vs the - // browser's instant one). Race it against a short cap: use the DP only if it's ready fast. + // browser's instant one). Race it against a cap: use the DP only if it's ready in time. Chat toasts + // stay snappy (700ms). CALL toasts (persistent) ring for ~40s, so we can afford to wait longer + // (2.5s) to actually show the caller's photo — the whole point of a call notification. const img = await Promise.race([ avatarToTempPng(payload.avatar), - new Promise((r) => setTimeout(() => r(null), 600)), + new Promise((r) => setTimeout(() => r(null), payload.persistent ? 2500 : 700)), ]); return await new Promise((resolve) => { let done = false; @@ -219,16 +221,61 @@ const GRANTED = new Set([ 'clipboard-read', 'clipboard-sanitized-write', 'fullscreen', 'pointerLock', ]); +// Custom "Share your screen" picker. Enumerates screens + windows, shows a branded modal grid with +// live thumbnails, and resolves to the chosen desktopCapturer source (or null if cancelled). Replaces +// the unreliable OS system picker. Only one picker at a time. +let pickerWin = null; +function pickShareSource() { + return new Promise((resolve) => { + let settled = false; + const finish = (v) => { if (settled) return; settled = true; ipcMain.removeListener('picker-choose', onChoose); if (pickerWin && !pickerWin.isDestroyed()) { try { pickerWin.close(); } catch (_) {} } pickerWin = null; resolve(v); }; + let allSources = []; + const onChoose = (_e, id) => { + if (!id) return finish(null); + finish(allSources.find((s) => s.id === id) || null); + }; + desktopCapturer.getSources({ types: ['screen', 'window'], thumbnailSize: { width: 320, height: 200 }, fetchWindowIcons: true }) + .then((sources) => { + allSources = sources; + const payload = { screen: [], window: [] }; + for (const s of sources) { + const bucket = s.id.startsWith('screen:') ? 'screen' : 'window'; + payload[bucket].push({ + id: s.id, + name: s.name || (bucket === 'screen' ? 'Screen' : 'Window'), + thumb: s.thumbnail ? s.thumbnail.toDataURL() : '', + appIcon: s.appIcon && !s.appIcon.isEmpty() ? s.appIcon.toDataURL() : null, + }); + } + if (pickerWin && !pickerWin.isDestroyed()) { try { pickerWin.close(); } catch (_) {} } + pickerWin = new BrowserWindow({ + width: 760, height: 560, parent: win || undefined, modal: !!win, resizable: true, + minimizable: false, maximizable: false, title: 'Share your screen', backgroundColor: '#f4f6fb', + show: false, autoHideMenuBar: true, + webPreferences: { preload: undefined, nodeIntegration: true, contextIsolation: false }, + }); + pickerWin.setMenu(null); + pickerWin.loadFile(path.join(__dirname, 'picker.html')); + pickerWin.once('ready-to-show', () => { pickerWin.show(); pickerWin.webContents.send('picker-sources', payload); }); + pickerWin.on('closed', () => { if (!settled) finish(null); }); // closed via the X → cancel + ipcMain.on('picker-choose', onChoose); + }) + .catch(() => finish(null)); + }); +} + function configureSession() { const ses = session.fromPartition('persist:bizconnect'); - // getDisplayMedia: prefer the OS's native screen/window PICKER (Windows 11 / macOS) so the user - // chooses what to share (and can pick a single window, avoiding the whole-screen mirror). If the - // system picker isn't available, this handler falls back to auto-selecting the primary display. + // getDisplayMedia: show OUR OWN branded screen/window picker. The Electron `useSystemPicker` + // option silently no-ops on many Windows 11 builds (it needs a specific WebRTC feature) and then + // auto-shares the primary display with no choice — which is exactly the "no picker appears" bug. + // So we enumerate sources ourselves and pop a picker window (pickShareSource) to let the user + // pick a specific screen or window. ses.setDisplayMediaRequestHandler((request, callback) => { - desktopCapturer.getSources({ types: ['screen', 'window'] }).then((sources) => { - callback(sources.length ? { video: sources[0], audio: 'loopback' } : {}); + pickShareSource().then((source) => { + callback(source ? { video: source, audio: 'loopback' } : {}); // {} = user cancelled → no share }).catch(() => callback({})); - }, { useSystemPicker: true }); + }, { useSystemPicker: false }); // Async grant (getUserMedia, notifications, …) ses.setPermissionRequestHandler((_wc, permission, callback) => callback(GRANTED.has(permission))); // Sync check (some getUserMedia paths query this before requesting) @@ -255,7 +302,10 @@ app.whenReady().then(() => { // Restart now" banner (restartToUpdate IPC does the install). No native dialog — that was // unbranded. It still installs on next launch if the user never clicks Restart. autoUpdater.on('update-downloaded', (info) => sendUpdate({ phase: 'ready', version: info && info.version })); - const check = () => autoUpdater.checkForUpdatesAndNotify().catch(() => {}); + // checkForUpdates (NOT ...AndNotify): ...AndNotify pops electron-updater's OWN native "Update ready + // — Restart/Later" toast on download, which duplicated our branded in-app banner (the user saw TWO + // restart prompts). Plain checkForUpdates still auto-downloads and fires 'update-downloaded'. + const check = () => autoUpdater.checkForUpdates().catch(() => {}); check(); setInterval(check, 6 * 60 * 60 * 1000); } diff --git a/desktop/package.json b/desktop/package.json index 3c0b3d8..d3e8336 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,6 +1,6 @@ { "name": "biz-connect-desktop", - "version": "0.1.7", + "version": "0.1.8", "description": "Biz Connect technician desktop client — loads the Connect web UI with native screen capture", "author": { "name": "BizGaze", diff --git a/desktop/picker.html b/desktop/picker.html new file mode 100644 index 0000000..d84df27 --- /dev/null +++ b/desktop/picker.html @@ -0,0 +1,83 @@ + + +
+ + +Choose a screen or a window to share with the call.
+