Vesktop: Pick Another Window button + shift-click for full picker
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a7c4513d4e
commit
2eeb29ac80
4 changed files with 140 additions and 39 deletions
|
|
@ -7,13 +7,13 @@
|
||||||
gamescope
|
gamescope
|
||||||
spotify
|
spotify
|
||||||
# Preselect the last stream's audio source (e.g. Hearthstone) when it's
|
# Preselect the last stream's audio source (e.g. Hearthstone) when it's
|
||||||
# running again, instead of resetting to "None" every Go Live; Cancel in
|
# running again, instead of resetting to "None" every Go Live.
|
||||||
# the stream dialog makes the next share show the full window picker
|
# "Pick Another Window" in the stream dialog or shift-clicking share
|
||||||
# (see xdph.conf in settings/hyprland.nix).
|
# bypasses the game auto-pick (see xdph.conf in settings/hyprland.nix).
|
||||||
(vesktop.overrideAttrs (old: {
|
(vesktop.overrideAttrs (old: {
|
||||||
patches = (old.patches or [ ]) ++ [
|
patches = (old.patches or [ ]) ++ [
|
||||||
../patches/vesktop-remember-stream-audio.patch
|
../patches/vesktop-remember-stream-audio.patch
|
||||||
../patches/vesktop-cancel-shows-picker.patch
|
../patches/vesktop-pick-another-window.patch
|
||||||
];
|
];
|
||||||
}))
|
}))
|
||||||
lsfg-vk
|
lsfg-vk
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
--- a/src/main/screenShare.ts
|
|
||||||
+++ b/src/main/screenShare.ts
|
|
||||||
@@ -5,6 +5,8 @@
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { 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";
|
|
||||||
|
|
||||||
@@ -55,7 +57,17 @@
|
|
||||||
skipPicker: true
|
|
||||||
}).catch(() => null);
|
|
||||||
|
|
||||||
- if (stream === null) return callback({});
|
|
||||||
+ if (stream === null) {
|
|
||||||
+ // Cancelled: tell the auto-picking XDPH picker (FredOS settings/hyprland.nix)
|
|
||||||
+ // to show the full picker on the next share.
|
|
||||||
+ try {
|
|
||||||
+ writeFileSync(
|
|
||||||
+ join(process.env.XDG_RUNTIME_DIR ?? "/tmp", "xdph-pick-manually"),
|
|
||||||
+ String(Math.floor(Date.now() / 1000))
|
|
||||||
+ );
|
|
||||||
+ } catch {}
|
|
||||||
+ return callback({});
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(video ? { video: sources[0] } : {});
|
|
||||||
131
patches/vesktop-pick-another-window.patch
Normal file
131
patches/vesktop-pick-another-window.patch
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
--- 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<StreamPick>(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
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
+ {skipPicker && (
|
||||||
|
+ <Button
|
||||||
|
+ variant="secondary"
|
||||||
|
+ onClick={() => {
|
||||||
|
+ submit({ id: PICK_ANOTHER, audio: false });
|
||||||
|
+ close();
|
||||||
|
+ }}
|
||||||
|
+ >
|
||||||
|
+ Pick Another Window
|
||||||
|
+ </Button>
|
||||||
|
+ )}
|
||||||
|
+
|
||||||
|
{selected && !skipPicker ? (
|
||||||
|
<Button variant="secondary" onClick={() => setSelected(void 0)}>
|
||||||
|
Back
|
||||||
|
|
@ -114,16 +114,16 @@ in
|
||||||
# list as id[HC>]class[HT>]title[HE>]address[HA>]… and reads back
|
# list as id[HC>]class[HT>]title[HE>]address[HA>]… and reads back
|
||||||
# "[SELECTION]<flags>/window:<id>" (flag r = token allowed). Hearthstone
|
# "[SELECTION]<flags>/window:<id>" (flag r = token allowed). Hearthstone
|
||||||
# wins, else any other Proton window (steam_app_*) that isn't a launcher
|
# wins, else any other Proton window (steam_app_*) that isn't a launcher
|
||||||
# or HDT. Escape hatch: Cancel in Vesktop's dialog (Gaming's patched
|
# or HDT. Gaming's patched Vesktop writes the time to xdph-pick-manually
|
||||||
# Vesktop writes the time to xdph-pick-manually) and the next share
|
# right before asking the portal ("Pick Another Window", shift-click)
|
||||||
# within a minute shows the normal picker.
|
# to get the normal picker instead.
|
||||||
xdg.configFile."hypr/xdph.conf".text = ''
|
xdg.configFile."hypr/xdph.conf".text = ''
|
||||||
screencopy {
|
screencopy {
|
||||||
allow_token_by_default = true
|
allow_token_by_default = true
|
||||||
custom_picker_binary = ${pkgs.writeShellScript "share-picker-game-first" ''
|
custom_picker_binary = ${pkgs.writeShellScript "share-picker-game-first" ''
|
||||||
flag="''${XDG_RUNTIME_DIR:-/tmp}/xdph-pick-manually"
|
flag="''${XDG_RUNTIME_DIR:-/tmp}/xdph-pick-manually"
|
||||||
printf -v now '%(%s)T' -1
|
printf -v now '%(%s)T' -1
|
||||||
{ cancelled=$(< "$flag"); } 2>/dev/null # no trailing newline, so not `read`
|
{ forced=$(< "$flag"); } 2>/dev/null # no trailing newline, so not `read`
|
||||||
echo 0 > "$flag"
|
echo 0 > "$flag"
|
||||||
|
|
||||||
game=$(printf '%s' "''${XDPH_WINDOW_SHARING_LIST-}" | ${pkgs.gawk}/bin/awk '
|
game=$(printf '%s' "''${XDPH_WINDOW_SHARING_LIST-}" | ${pkgs.gawk}/bin/awk '
|
||||||
|
|
@ -133,7 +133,7 @@ in
|
||||||
&& $3 !~ /^(Battle\.net|Hearthstone Deck Tracker|HearthstoneOverlay)/ { other = $1 }
|
&& $3 !~ /^(Battle\.net|Hearthstone Deck Tracker|HearthstoneOverlay)/ { other = $1 }
|
||||||
END { print hs != "" ? hs : other }')
|
END { print hs != "" ? hs : other }')
|
||||||
|
|
||||||
if [ -n "$game" ] && (( now - ''${cancelled:-0} > 60 )); then
|
if [ -n "$game" ] && (( now - ''${forced:-0} > 10 )); then
|
||||||
echo "[SELECTION]r/window:$game"
|
echo "[SELECTION]r/window:$game"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue