--- a/src/main/screenShare.ts +++ b/src/main/screenShare.ts @@ -4,7 +4,9 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { desktopCapturer, session, Streams } from "electron"; +import { app, desktopCapturer, session, Streams } from "electron"; +import { writeFileSync } from "fs"; +import { join } from "path"; import type { StreamPick } from "renderer/components/ScreenSharePicker"; import { IpcCommands, IpcEvents } from "shared/IpcEvents"; @@ -14,7 +16,31 @@ 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"; + +// 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"), + String(Math.floor(Date.now() / 1000)) + ); + } catch {} +} + +// Shift held while clicking share = full picker +let shiftDown = false; + export function registerScreenShareHandler() { + app.on("browser-window-created", (_, win) => { + win.webContents.on("before-input-event", (_, input) => { + if (input.key === "Shift") shiftDown = input.type === "keyDown"; + }); + win.on("blur", () => (shiftDown = false)); + }); + handle(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, async (_, id: string) => { const sources = await desktopCapturer.getSources({ types: ["window", "screen"], @@ -29,15 +55,19 @@ session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => { // request full resolution on wayland right away because we always only end up with one result anyway const width = isWayland ? 1920 : 176; - const sources = await desktopCapturer - .getSources({ - types: ["window", "screen"], - thumbnailSize: { - width, - height: width * (9 / 16) - } - }) - .catch(err => console.error("Error during screenshare picker", err)); + const getSources = () => + desktopCapturer + .getSources({ + types: ["window", "screen"], + thumbnailSize: { + width, + height: width * (9 / 16) + } + }) + .catch(err => console.error("Error during screenshare picker", err)); + + if (isWayland && shiftDown) forceFullPicker(); + let sources = await getSources(); if (!sources) return callback({}); @@ -48,14 +78,22 @@ })); if (isWayland) { - const video = data[0]; - if (video) { + let video = data[0]; + while (video) { const stream = await sendRendererCommand(IpcCommands.SCREEN_SHARE_PICKER, { screens: [video], skipPicker: true }).catch(() => null); if (stream === null) return callback({}); + if (stream.id !== PICK_ANOTHER) break; + + // re-run the portal, this time with the full picker + forceFullPicker(); + sources = await getSources(); + if (!sources) return callback({}); + const [source] = sources; + video = source && { id: source.id, name: source.name, url: source.thumbnail.toDataURL() }; } callback(video ? { video: sources[0] } : {}); --- a/src/renderer/components/ScreenSharePicker.tsx +++ b/src/renderer/components/ScreenSharePicker.tsx @@ -82,6 +82,10 @@ url: string; } +// Returned instead of a source id when "Pick Another Window" is clicked; main +// re-runs the portal with the full picker (keep in sync with main/screenShare.ts). +export const PICK_ANOTHER = "vesktop-pick-another"; + export let currentSettings: StreamSettings | null = null; const logger = new Logger("VesktopScreenShare"); @@ -829,6 +833,18 @@ Go Live + {skipPicker && ( + + )} + {selected && !skipPicker ? (