diff --git a/hosts/FredOS-Gaming.nix b/hosts/FredOS-Gaming.nix index d038006..4e1f8ed 100644 --- a/hosts/FredOS-Gaming.nix +++ b/hosts/FredOS-Gaming.nix @@ -6,7 +6,11 @@ mangohud gamescope spotify - vesktop + # Preselect the last stream's audio source (e.g. Hearthstone) when it's + # running again, instead of resetting to "None" every Go Live. + (vesktop.overrideAttrs (old: { + patches = (old.patches or [ ]) ++ [ ../patches/vesktop-remember-stream-audio.patch ]; + })) lsfg-vk lsfg-vk-ui faugus-launcher diff --git a/patches/vesktop-remember-stream-audio.patch b/patches/vesktop-remember-stream-audio.patch new file mode 100644 index 0000000..21bedee --- /dev/null +++ b/patches/vesktop-remember-stream-audio.patch @@ -0,0 +1,67 @@ +--- a/src/renderer/components/ScreenSharePicker.tsx ++++ b/src/renderer/components/ScreenSharePicker.tsx +@@ -31,7 +31,14 @@ + useForceUpdater + } from "@vencord/types/utils"; + import { onceReady } from "@vencord/types/webpack"; +-import { FluxDispatcher, MediaEngineStore, Select, UserStore, useState } from "@vencord/types/webpack/common"; ++import { ++ FluxDispatcher, ++ MediaEngineStore, ++ Select, ++ useEffect, ++ UserStore, ++ useState ++} from "@vencord/types/webpack/common"; + import { Node } from "@vencord/venmic"; + import type { Dispatch, SetStateAction } from "react"; + import { addPatch } from "renderer/patches/shared"; +@@ -590,6 +597,21 @@ + const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true; + const [ignorePulseWarning, setIgnorePulseWarning] = useState(false); + ++ // Once the first source list arrives, preselect whichever of the last ++ // stream's audio sources are running again (e.g. the same game). ++ const [restored, setRestored] = useState(false); ++ useEffect(() => { ++ if (restored || loading || !sources.ok) return; ++ setRestored(true); ++ ++ const last = State.store.screenshareAudio; ++ if (includeSources !== "None" || !last || last === "None") return; ++ if (last === "Entire System") return setIncludeSources(last); ++ ++ const running = last.filter(l => sources.targets.some(t => hasMatchingProps(l, t))); ++ if (running.length) setIncludeSources(running); ++ }, [loading]); ++ + if (!sources.ok && sources.isGlibCxxOutdated) { + return ( + +@@ -746,6 +768,7 @@ + disabled={!selected} + onClick={() => { + currentSettings = settings; ++ if (isLinux) State.store.screenshareAudio = settings.includeSources; + try { + const frameRate = Number(qualitySettings.frameRate); + const height = Number(qualitySettings.resolution); +--- a/src/renderer/settings.ts ++++ b/src/renderer/settings.ts +@@ -4,6 +4,7 @@ + * SPDX-License-Identifier: GPL-3.0-or-later + */ + ++import type { Node } from "@vencord/venmic"; + import { useEffect, useReducer } from "@vencord/types/webpack/common"; + import { SettingsStore } from "shared/utils/SettingsStore"; + +@@ -37,6 +38,8 @@ + resolution: string; + frameRate: string; + }; ++ // audio sources picked for the last stream, preselected when running again ++ screenshareAudio?: "None" | "Entire System" | Node[]; + } + + const stateKey = "VesktopState"; diff --git a/settings/hyprland.nix b/settings/hyprland.nix index 08a62ad..eedc533 100644 --- a/settings/hyprland.nix +++ b/settings/hyprland.nix @@ -108,15 +108,36 @@ in # separate hyprpaper daemon. stylix.targets.hyprpaper.enable = false; - # Screenshare picker: auto-allow restore tokens (the "allow a restore - # token" checkbox) so Vesktop/Discord gets a token on the first pick - # instead of re-prompting. XDPH parses "[SELECTION]/..." from the - # picker's stdout; flag 'r' = token allowed, so inject it always. + # Screenshare picker. allow_token_by_default pre-ticks the "allow a + # restore token" box so Vesktop/Discord isn't re-prompted. The custom + # picker skips the dialog when a game is open: XDPH hands it the window + # list as id[HC>]class[HT>]title[HE>]address[HA>]… and reads back + # "[SELECTION]/window:" (flag r = token allowed). Hearthstone + # wins, else any other Proton window (steam_app_*) that isn't a launcher + # or HDT. Escape hatch: share again within 30s (cancel, retry) and the + # normal picker shows. xdg.configFile."hypr/xdph.conf".text = '' screencopy { - custom_picker_binary = ${pkgs.writeShellScript "share-picker-auto-restore" '' - ${pkgs.xdg-desktop-portal-hyprland}/bin/hyprland-share-picker "$@" \ - | ${pkgs.gnused}/bin/sed 's/^\[SELECTION\]/[SELECTION]r/' + allow_token_by_default = true + custom_picker_binary = ${pkgs.writeShellScript "share-picker-game-first" '' + stamp="''${XDG_RUNTIME_DIR:-/tmp}/xdph-autopick" + printf -v now '%(%s)T' -1 + read -r last 2>/dev/null < "$stamp" || last=0 + + game=$(printf '%s' "''${XDPH_WINDOW_SHARING_LIST-}" | ${pkgs.gawk}/bin/awk ' + BEGIN { RS = "\\[HA>\\]"; FS = "\\[H[CTE]>\\]" } + $2 ~ /^steam_app_/ && $3 == "Hearthstone" { hs = $1 } + $2 ~ /^steam_app_/ && $3 != "" && !other \ + && $3 !~ /^(Battle\.net|Hearthstone Deck Tracker|HearthstoneOverlay)/ { other = $1 } + END { print hs != "" ? hs : other }') + + if [ -n "$game" ] && (( now - last > 30 )); then + echo "$now" > "$stamp" + echo "[SELECTION]r/window:$game" + exit 0 + fi + echo 0 > "$stamp" + exec ${pkgs.xdg-desktop-portal-hyprland}/bin/hyprland-share-picker "$@" ''} } '';