Screenshare: grim preview from picker, ~70ms instead of ~1s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-12 20:55:01 +01:00
parent 4a6fb7f5b2
commit d3ce8f66fc
2 changed files with 115 additions and 25 deletions

View file

@ -6,24 +6,26 @@
-import { desktopCapturer, session, Streams } from "electron";
+import { app, desktopCapturer, session, Streams } from "electron";
+import { writeFileSync } from "fs";
+import { readFileSync, statSync, writeFileSync } from "fs";
+import { join } from "path";
import type { StreamPick } from "renderer/components/ScreenSharePicker";
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
@@ -14,8 +16,39 @@
@@ -14,8 +16,68 @@
const isWayland =
process.platform === "linux" && (process.env.XDG_SESSION_TYPE === "wayland" || !!process.env.WAYLAND_DISPLAY);
+// renderer's PICK_ANOTHER ("Pick Another Window" in the stream dialog)
+const PICK_ANOTHER = "vesktop-pick-another";
+
+const runtimeDir = process.env.XDG_RUNTIME_DIR ?? "/tmp";
+
+// FredOS: the XDPH custom picker (settings/hyprland.nix) auto-picks a running
+// game unless this file holds a fresh timestamp, then shows the full picker.
+function forceFullPicker() {
+ try {
+ writeFileSync(
+ join(process.env.XDG_RUNTIME_DIR ?? "/tmp", "xdph-pick-manually"),
+ join(runtimeDir, "xdph-pick-manually"),
+ String(Math.floor(Date.now() / 1000))
+ );
+ } catch {}
@ -34,6 +36,33 @@
+const PENDING_PREVIEW = "vesktop-pending-preview";
+let pendingPreview: Promise<string | undefined> | undefined;
+
+// The FredOS picker grim-shots its selection to xdph-preview.jpg ~70ms after
+// answering; use that, else Electron's thumbnail (~1s). No sources at all
+// (picker closed) resolves undefined so the dialog closes.
+function previewFor(sources: Promise<Electron.DesktopCapturerSource[] | void>, requestedAt: number) {
+ const file = join(runtimeDir, "xdph-preview.jpg");
+ const jpeg = (data: Buffer) => "data:image/jpeg;base64," + data.toString("base64");
+
+ return new Promise<string | undefined>(resolve => {
+ let done = false;
+ const finish = (url?: string) => {
+ if (!done) resolve(url);
+ done = true;
+ };
+
+ sources.then(s => finish(s?.[0] ? jpeg(s[0].thumbnail.toJPEG(85)) : undefined));
+
+ const poll = () => {
+ if (done) return;
+ try {
+ if (statSync(file).mtimeMs >= requestedAt) return finish(jpeg(readFileSync(file)));
+ } catch {}
+ setTimeout(poll, 25);
+ };
+ poll();
+ });
+}
+
+// Shift held while clicking share = full picker
+let shiftDown = false;
+
@ -51,7 +80,7 @@
const sources = await desktopCapturer.getSources({
types: ["window", "screen"],
thumbnailSize: {
@@ -27,14 +60,40 @@
@@ -27,14 +89,39 @@
});
session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => {
@ -63,12 +92,11 @@
+ // let it fetch the preview (PENDING_PREVIEW) when the portal answers.
+ if (shiftDown) forceFullPicker();
+ while (true) {
+ const requestedAt = Date.now();
+ const sources = desktopCapturer
+ .getSources({ types: ["window", "screen"], thumbnailSize: { width: 960, height: 540 } })
+ .catch(err => console.error("Error during screenshare picker", err));
+ pendingPreview = sources.then(s =>
+ s?.[0] ? "data:image/jpeg;base64," + s[0].thumbnail.toJPEG(85).toString("base64") : undefined
+ );
+ pendingPreview = previewFor(sources, requestedAt);
+
+ const stream = await sendRendererCommand<StreamPick>(IpcCommands.SCREEN_SHARE_PICKER, {
+ screens: [{ id: PENDING_PREVIEW, name: "", url: "" }],
@ -96,7 +124,7 @@
}
})
.catch(err => console.error("Error during screenshare picker", err));
@@ -47,21 +106,6 @@
@@ -47,21 +134,6 @@
url: thumbnail.toDataURL()
}));