Fix windows breakage from linux dev

This commit is contained in:
2026-06-26 18:38:03 -05:00
parent 0325b6efbc
commit 266a92fedb
2 changed files with 166 additions and 10 deletions
+21 -10
View File
@@ -47,6 +47,11 @@
async function startStream(cmd) {
const key = String(cmd.displayId);
stopStream(key);
const display = cmd.display || {};
const scale = display.scaleFactor || 1;
const bounds = display.bounds || { width: 1280, height: 720 };
const physWidth = Math.round(bounds.width * scale);
const physHeight = Math.round(bounds.height * scale);
const state = {
displayId: cmd.displayId,
media: null,
@@ -74,19 +79,25 @@
video: true,
});
} else {
// The chromeMediaSource constraint is Electron's bridge from a
// desktopCapturer source id to a live media stream. The legacy
// `mandatory` wrapper was removed in Electron 29 (Chromium 116+);
// constraints must now be flat (no mandatory/optional nesting). This
// path works on Windows and X11.
// Keep the legacy desktop-capture constraint wrapper here: it binds
// the stream to the exact desktop source chosen in the main process.
// Without it Chromium can treat the request like a normal media
// request and pick the default camera device instead.
state.media = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: cmd.sourceId,
// Sampling cadence is controlled by the setInterval timer, so the
// actual capture rate is sampleMs-driven regardless of display
// refresh rate. Resolution is driven by the source itself.
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: cmd.sourceId,
minWidth: physWidth,
maxWidth: physWidth,
minHeight: physHeight,
maxHeight: physHeight,
// No maxFrameRate: sampling cadence is controlled by the
// setInterval timer below, so the actual capture rate is always
// sampleMs-driven regardless of display refresh rate or power
// mode.
},
},
});
}
+145
View File
@@ -0,0 +1,145 @@
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
test('capture worker requests the selected desktop source, not a plain camera stream', async () => {
const scriptPath = path.join(__dirname, '../../app/renderer/capture-worker.js');
const script = fs.readFileSync(scriptPath, 'utf8');
const mediaCalls = [];
const messages = [];
let onCommand = null;
let resolveStreamReady;
const streamReady = new Promise((resolve) => {
resolveStreamReady = resolve;
});
const context = {
console: {
error() {},
log() {},
warn() {},
},
captureWorkerBridge: {
onCommand(fn) {
onCommand = fn;
},
send(msg) {
messages.push(msg);
if (msg.type === 'stream-ready') resolveStreamReady();
},
},
StepForgeClickFrames: {
FrameRing: class {
constructor() {
this._frames = [];
}
push(frame) {
this._frames.push(frame);
}
frames() {
return [...this._frames];
}
latest() {
return this._frames[this._frames.length - 1] || null;
}
clear() {
this._frames = [];
}
},
selectFrameForClick() {
return null;
},
},
navigator: {
mediaDevices: {
async getUserMedia(constraints) {
mediaCalls.push(constraints);
return {
getTracks() {
return [{ stop() {} }];
},
};
},
async getDisplayMedia() {
throw new Error('unexpected getDisplayMedia call');
},
},
},
document: {
createElement(tag) {
assert.equal(tag, 'video');
return {
muted: false,
srcObject: null,
readyState: 2,
play: async () => {},
videoWidth: 1920,
videoHeight: 1080,
};
},
},
createImageBitmap: async () => ({ width: 1920, height: 1080, close() {} }),
setInterval: () => 1,
clearInterval: () => {},
OffscreenCanvas: class {
constructor(width, height) {
this.width = width;
this.height = height;
}
getContext() {
return { drawImage() {} };
}
async convertToBlob() {
return {
async arrayBuffer() {
return new ArrayBuffer(0);
},
};
}
},
};
vm.createContext(context);
vm.runInContext(script, context, { filename: scriptPath });
assert.equal(typeof onCommand, 'function', 'worker should register a command handler');
onCommand({
type: 'start-stream',
displayId: 7,
sourceId: 'screen:1:0',
display: {
bounds: { width: 1920, height: 1080 },
scaleFactor: 1,
},
sampleMs: 50,
});
await streamReady;
assert.equal(mediaCalls.length, 1);
assert.deepEqual(mediaCalls[0], {
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: 'screen:1:0',
minWidth: 1920,
maxWidth: 1920,
minHeight: 1080,
maxHeight: 1080,
},
},
});
assert.ok(messages.some((msg) => msg.type === 'stream-ready'));
});