From 3fd98c2c1b469f4f6f7e8ea1fa7ae5e61970cccd Mon Sep 17 00:00:00 2001 From: zortos293 <65777760+zortos293@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:10:08 +0000 Subject: [PATCH] Add session fullscreen shortcut and auto-toggle on connection --- opennow-stable/package-lock.json | 28 +++---- opennow-stable/src/main/index.ts | 15 ---- opennow-stable/src/main/settings.ts | 3 + opennow-stable/src/renderer/src/App.tsx | 83 ++++++++++++++----- .../renderer/src/components/SettingsPage.tsx | 48 ++++++++++- .../renderer/src/components/StreamView.tsx | 2 + opennow-stable/src/shared/gfn.ts | 1 + 7 files changed, 124 insertions(+), 56 deletions(-) diff --git a/opennow-stable/package-lock.json b/opennow-stable/package-lock.json index 1c9091aa..1bf9523a 100644 --- a/opennow-stable/package-lock.json +++ b/opennow-stable/package-lock.json @@ -62,6 +62,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -737,7 +738,6 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, - "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -759,7 +759,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -776,7 +775,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -791,7 +789,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": ">= 10.0.0" } @@ -2145,6 +2142,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2276,6 +2274,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2722,6 +2721,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -3239,8 +3239,7 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/cross-env": { "version": "10.1.0", @@ -3500,6 +3499,7 @@ "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", @@ -3838,7 +3838,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -3859,7 +3858,6 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -4696,7 +4694,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -5318,7 +5316,6 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -5782,7 +5779,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -5800,7 +5796,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -5811,6 +5806,7 @@ "integrity": "sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -5915,6 +5911,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5924,6 +5921,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -6121,7 +6119,6 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -6154,6 +6151,7 @@ "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -6218,7 +6216,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/sanitize-filename": { @@ -6591,7 +6589,6 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -7389,6 +7386,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", diff --git a/opennow-stable/src/main/index.ts b/opennow-stable/src/main/index.ts index c4fd04a9..d9f5770e 100644 --- a/opennow-stable/src/main/index.ts +++ b/opennow-stable/src/main/index.ts @@ -566,15 +566,6 @@ async function createMainWindow(): Promise { await mainWindow.loadFile(join(__dirname, "../../dist/index.html")); } - // Apply full screen on startup if the user has configured it. - if (settings.autoFullScreen) { - try { - mainWindow.setFullScreen(true); - } catch (err) { - console.warn("Failed to apply autoFullScreen on startup:", err); - } - } - mainWindow.on("closed", () => { mainWindow = null; }); @@ -1203,12 +1194,6 @@ function registerIpcHandlers(): void { settingsManager.set(key, value); // React to certain setting changes immediately in main process try { - if (key === "autoFullScreen") { - if (mainWindow && !mainWindow.isDestroyed()) { - const should = Boolean(value as unknown as boolean); - mainWindow.setFullScreen(should); - } - } if (key === "discordRichPresence") { if (value) { void connectDiscordRpc(); diff --git a/opennow-stable/src/main/settings.ts b/opennow-stable/src/main/settings.ts index 38795a9e..d6d44a74 100644 --- a/opennow-stable/src/main/settings.ts +++ b/opennow-stable/src/main/settings.ts @@ -35,6 +35,8 @@ export interface Settings { shortcutToggleStats: string; /** Toggle pointer lock shortcut */ shortcutTogglePointerLock: string; + /** Toggle fullscreen shortcut */ + shortcutToggleFullscreen: string; /** Stop stream shortcut */ shortcutStopStream: string; /** Toggle anti-AFK shortcut */ @@ -109,6 +111,7 @@ const DEFAULT_SETTINGS: Settings = { mouseAcceleration: 1, shortcutToggleStats: "F3", shortcutTogglePointerLock: "F8", + shortcutToggleFullscreen: "F10", shortcutStopStream: defaultStopShortcut, shortcutToggleAntiAfk: defaultAntiAfkShortcut, shortcutToggleMicrophone: defaultMicShortcut, diff --git a/opennow-stable/src/renderer/src/App.tsx b/opennow-stable/src/renderer/src/App.tsx index b2e9cb1f..a466764e 100644 --- a/opennow-stable/src/renderer/src/App.tsx +++ b/opennow-stable/src/renderer/src/App.tsx @@ -203,6 +203,7 @@ function hasAnyEligiblePrintedWasteZone( const DEFAULT_SHORTCUTS = { shortcutToggleStats: "F3", shortcutTogglePointerLock: "F8", + shortcutToggleFullscreen: "F10", shortcutStopStream: "Ctrl+Shift+Q", shortcutToggleAntiAfk: "Ctrl+Shift+K", shortcutToggleMicrophone: "Ctrl+Shift+M", @@ -820,6 +821,7 @@ export function App(): JSX.Element { mouseAcceleration: 1, shortcutToggleStats: DEFAULT_SHORTCUTS.shortcutToggleStats, shortcutTogglePointerLock: DEFAULT_SHORTCUTS.shortcutTogglePointerLock, + shortcutToggleFullscreen: DEFAULT_SHORTCUTS.shortcutToggleFullscreen, shortcutStopStream: DEFAULT_SHORTCUTS.shortcutStopStream, shortcutToggleAntiAfk: DEFAULT_SHORTCUTS.shortcutToggleAntiAfk, shortcutToggleMicrophone: DEFAULT_SHORTCUTS.shortcutToggleMicrophone, @@ -1889,15 +1891,17 @@ export function App(): JSX.Element { }; const toggleStats = parseWithFallback(settings.shortcutToggleStats, DEFAULT_SHORTCUTS.shortcutToggleStats); const togglePointerLock = parseWithFallback(settings.shortcutTogglePointerLock, DEFAULT_SHORTCUTS.shortcutTogglePointerLock); + const toggleFullscreen = parseWithFallback(settings.shortcutToggleFullscreen, DEFAULT_SHORTCUTS.shortcutToggleFullscreen); const stopStream = parseWithFallback(settings.shortcutStopStream, DEFAULT_SHORTCUTS.shortcutStopStream); const toggleAntiAfk = parseWithFallback(settings.shortcutToggleAntiAfk, DEFAULT_SHORTCUTS.shortcutToggleAntiAfk); const toggleMicrophone = parseWithFallback(settings.shortcutToggleMicrophone, DEFAULT_SHORTCUTS.shortcutToggleMicrophone); const screenshot = parseWithFallback(settings.shortcutScreenshot, DEFAULT_SHORTCUTS.shortcutScreenshot); const recording = parseWithFallback(settings.shortcutToggleRecording, DEFAULT_SHORTCUTS.shortcutToggleRecording); - return { toggleStats, togglePointerLock, stopStream, toggleAntiAfk, toggleMicrophone, screenshot, recording }; + return { toggleStats, togglePointerLock, toggleFullscreen, stopStream, toggleAntiAfk, toggleMicrophone, screenshot, recording }; }, [ settings.shortcutToggleStats, settings.shortcutTogglePointerLock, + settings.shortcutToggleFullscreen, settings.shortcutStopStream, settings.shortcutToggleAntiAfk, settings.shortcutToggleMicrophone, @@ -1905,6 +1909,28 @@ export function App(): JSX.Element { settings.shortcutToggleRecording, ]); + const setSessionFullscreen = useCallback(async (nextFullscreen: boolean) => { + try { + if (nextFullscreen) { + if (!document.fullscreenElement) { + await document.documentElement.requestFullscreen(); + } + } else if (document.fullscreenElement) { + await document.exitFullscreen(); + } + } catch {} + + try { + await window.openNow.setFullscreen(nextFullscreen); + } catch (error) { + console.warn(`Failed to sync native fullscreen state (${nextFullscreen ? "enter" : "exit"}):`, error); + } + }, []); + + const toggleSessionFullscreen = useCallback(async () => { + await setSessionFullscreen(!document.fullscreenElement); + }, [setSessionFullscreen]); + const requestEscLockedPointerCapture = useCallback(async (target: HTMLVideoElement) => { const lockTarget = (target.parentElement as HTMLElement | null) ?? target; const requestPointerLockCompat = async ( @@ -1917,7 +1943,7 @@ export function App(): JSX.Element { }; if (settings.autoFullScreen && !document.fullscreenElement) { - await document.documentElement.requestFullscreen().catch(() => {}); + await setSessionFullscreen(true); } const nav = navigator as any; @@ -1935,7 +1961,7 @@ export function App(): JSX.Element { throw err; }) .catch(() => {}); - }, [settings.autoFullScreen]); + }, [setSessionFullscreen, settings.autoFullScreen]); const handleRequestPointerLock = useCallback(() => { if (videoRef.current) { @@ -1985,14 +2011,27 @@ export function App(): JSX.Element { // so navigator.keyboard.lock() can capture Escape in fullscreen) useEffect(() => { const unsubscribe = window.openNow.onToggleFullscreen(() => { - if (document.fullscreenElement) { - document.exitFullscreen().catch(() => {}); - } else { - document.documentElement.requestFullscreen().catch(() => {}); - } + void toggleSessionFullscreen(); }); return () => unsubscribe(); - }, []); + }, [toggleSessionFullscreen]); + + const autoFullscreenRequestedRef = useRef(false); + + useEffect(() => { + const isSessionConnecting = streamStatus === "connecting" || streamStatus === "streaming"; + if (!settings.autoFullScreen || !isSessionConnecting) { + autoFullscreenRequestedRef.current = false; + return; + } + + if (autoFullscreenRequestedRef.current || document.fullscreenElement) { + return; + } + + autoFullscreenRequestedRef.current = true; + void setSessionFullscreen(true); + }, [setSessionFullscreen, settings.autoFullScreen, streamStatus]); // Anti-AFK interval useEffect(() => { @@ -2170,14 +2209,6 @@ export function App(): JSX.Element { }); setLaunchError(null); setStreamStatus("streaming"); - // Auto-enter fullscreen on stream start if user enabled it - try { - if ((settings as any).autoFullScreen) { - void (window as any).openNow?.setFullscreen?.(true); - } - } catch (err) { - console.warn("Failed to auto-fullscreen on stream start:", err); - } } } else if (event.type === "remote-ice") { await clientRef.current?.addRemoteCandidate(event.candidate); @@ -3117,6 +3148,16 @@ export function App(): JSX.Element { return; } + if (isShortcutMatch(e, shortcuts.toggleFullscreen)) { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + if (streamStatus === "connecting" || streamStatus === "streaming") { + void toggleSessionFullscreen(); + } + return; + } + if (isShortcutMatch(e, shortcuts.stopStream)) { e.preventDefault(); e.stopPropagation(); @@ -3157,6 +3198,7 @@ export function App(): JSX.Element { settings.clipboardPaste, shortcuts, streamStatus, + toggleSessionFullscreen, ]); const filteredGames = games; @@ -3273,6 +3315,7 @@ export function App(): JSX.Element { shortcuts={{ toggleStats: formatShortcutForDisplay(settings.shortcutToggleStats, isMac), togglePointerLock: formatShortcutForDisplay(settings.shortcutTogglePointerLock, isMac), + toggleFullscreen: formatShortcutForDisplay(settings.shortcutToggleFullscreen, isMac), stopStream: formatShortcutForDisplay(settings.shortcutStopStream, isMac), toggleAntiAfk: shortcuts.toggleAntiAfk.canonical, toggleMicrophone: formatShortcutForDisplay(settings.shortcutToggleMicrophone, isMac), @@ -3294,11 +3337,7 @@ export function App(): JSX.Element { gameTitle={streamingGame?.title ?? "Game"} platformStore={streamingStore ?? undefined} onToggleFullscreen={() => { - if (document.fullscreenElement) { - document.exitFullscreen().catch(() => {}); - } else { - document.documentElement.requestFullscreen().catch(() => {}); - } + void toggleSessionFullscreen(); }} onConfirmExit={handleExitPromptConfirm} onCancelExit={handleExitPromptCancel} diff --git a/opennow-stable/src/renderer/src/components/SettingsPage.tsx b/opennow-stable/src/renderer/src/components/SettingsPage.tsx index cfa10814..69e9bf88 100644 --- a/opennow-stable/src/renderer/src/components/SettingsPage.tsx +++ b/opennow-stable/src/renderer/src/components/SettingsPage.tsx @@ -115,6 +115,7 @@ const shortcutExamples = "Examples: F3, Ctrl+Shift+Q, Ctrl+Shift+K"; const shortcutDefaults = { shortcutToggleStats: "F3", shortcutTogglePointerLock: "F8", + shortcutToggleFullscreen: "F10", shortcutStopStream: "Ctrl+Shift+Q", shortcutToggleAntiAfk: "Ctrl+Shift+K", shortcutToggleMicrophone: "Ctrl+Shift+M", @@ -442,6 +443,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, const [toggleStatsInput, setToggleStatsInput] = useState(settings.shortcutToggleStats); const [togglePointerLockInput, setTogglePointerLockInput] = useState(settings.shortcutTogglePointerLock); + const [toggleFullscreenInput, setToggleFullscreenInput] = useState(settings.shortcutToggleFullscreen); const [stopStreamInput, setStopStreamInput] = useState(settings.shortcutStopStream); const [toggleAntiAfkInput, setToggleAntiAfkInput] = useState(settings.shortcutToggleAntiAfk); const [toggleMicrophoneInput, setToggleMicrophoneInput] = useState(settings.shortcutToggleMicrophone); @@ -449,6 +451,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, const [recordingInput, setRecordingInput] = useState(settings.shortcutToggleRecording); const [toggleStatsError, setToggleStatsError] = useState(null); const [togglePointerLockError, setTogglePointerLockError] = useState(null); + const [toggleFullscreenError, setToggleFullscreenError] = useState(null); const [stopStreamError, setStopStreamError] = useState(null); const [toggleAntiAfkError, setToggleAntiAfkError] = useState(null); const [toggleMicrophoneError, setToggleMicrophoneError] = useState(null); @@ -479,6 +482,10 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, setTogglePointerLockInput(settings.shortcutTogglePointerLock); }, [settings.shortcutTogglePointerLock]); + useEffect(() => { + setToggleFullscreenInput(settings.shortcutToggleFullscreen); + }, [settings.shortcutToggleFullscreen]); + useEffect(() => { setStopStreamInput(settings.shortcutStopStream); }, [settings.shortcutStopStream]); @@ -787,6 +794,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, switch (key) { case "shortcutToggleStats": setToggleStatsError(msg); break; case "shortcutTogglePointerLock": setTogglePointerLockError(msg); break; + case "shortcutToggleFullscreen": setToggleFullscreenError(msg); break; case "shortcutStopStream": setStopStreamError(msg); break; case "shortcutToggleAntiAfk": setToggleAntiAfkError(msg); break; case "shortcutToggleMicrophone": setToggleMicrophoneError(msg); break; @@ -802,6 +810,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, switch (key) { case "shortcutToggleStats": setToggleStatsError(msg); break; case "shortcutTogglePointerLock": setTogglePointerLockError(msg); break; + case "shortcutToggleFullscreen": setToggleFullscreenError(msg); break; case "shortcutStopStream": setStopStreamError(msg); break; case "shortcutToggleAntiAfk": setToggleAntiAfkError(msg); break; case "shortcutToggleMicrophone": setToggleMicrophoneError(msg); break; @@ -816,6 +825,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, switch (key) { case "shortcutToggleStats": setToggleStatsError(conflict); break; case "shortcutTogglePointerLock": setTogglePointerLockError(conflict); break; + case "shortcutToggleFullscreen": setToggleFullscreenError(conflict); break; case "shortcutStopStream": setStopStreamError(conflict); break; case "shortcutToggleAntiAfk": setToggleAntiAfkError(conflict); break; case "shortcutToggleMicrophone": setToggleMicrophoneError(conflict); break; @@ -828,6 +838,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, switch (key) { case "shortcutToggleStats": setToggleStatsError(null); break; case "shortcutTogglePointerLock": setTogglePointerLockError(null); break; + case "shortcutToggleFullscreen": setToggleFullscreenError(null); break; case "shortcutStopStream": setStopStreamError(null); break; case "shortcutToggleAntiAfk": setToggleAntiAfkError(null); break; case "shortcutToggleMicrophone": setToggleMicrophoneError(null); break; @@ -838,6 +849,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, switch (key) { case "shortcutToggleStats": setToggleStatsInput(normalized.canonical); break; case "shortcutTogglePointerLock": setTogglePointerLockInput(normalized.canonical); break; + case "shortcutToggleFullscreen": setToggleFullscreenInput(normalized.canonical); break; case "shortcutStopStream": setStopStreamInput(normalized.canonical); break; case "shortcutToggleAntiAfk": setToggleAntiAfkInput(normalized.canonical); break; case "shortcutToggleMicrophone": setToggleMicrophoneInput(normalized.canonical); break; @@ -856,6 +868,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, switch (key) { case "shortcutToggleStats": setToggleStatsError(conflict); break; case "shortcutTogglePointerLock": setTogglePointerLockError(conflict); break; + case "shortcutToggleFullscreen": setToggleFullscreenError(conflict); break; case "shortcutStopStream": setStopStreamError(conflict); break; case "shortcutToggleAntiAfk": setToggleAntiAfkError(conflict); break; case "shortcutToggleMicrophone": setToggleMicrophoneError(conflict); break; @@ -868,6 +881,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, switch (key) { case "shortcutToggleStats": setToggleStatsError(null); break; case "shortcutTogglePointerLock": setTogglePointerLockError(null); break; + case "shortcutToggleFullscreen": setToggleFullscreenError(null); break; case "shortcutStopStream": setStopStreamError(null); break; case "shortcutToggleAntiAfk": setToggleAntiAfkError(null); break; case "shortcutToggleMicrophone": setToggleMicrophoneError(null); break; @@ -878,6 +892,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, switch (key) { case "shortcutToggleStats": setToggleStatsInput(canonical); break; case "shortcutTogglePointerLock": setTogglePointerLockInput(canonical); break; + case "shortcutToggleFullscreen": setToggleFullscreenInput(canonical); break; case "shortcutStopStream": setStopStreamInput(canonical); break; case "shortcutToggleAntiAfk": setToggleAntiAfkInput(canonical); break; case "shortcutToggleMicrophone": setToggleMicrophoneInput(canonical); break; @@ -922,6 +937,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, switch (key) { case "shortcutToggleStats": setToggleStatsError(msg); break; case "shortcutTogglePointerLock": setTogglePointerLockError(msg); break; + case "shortcutToggleFullscreen": setToggleFullscreenError(msg); break; case "shortcutStopStream": setStopStreamError(msg); break; case "shortcutToggleAntiAfk": setToggleAntiAfkError(msg); break; case "shortcutToggleMicrophone": setToggleMicrophoneError(msg); break; @@ -937,6 +953,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, () => settings.shortcutToggleStats === shortcutDefaults.shortcutToggleStats && settings.shortcutTogglePointerLock === shortcutDefaults.shortcutTogglePointerLock + && settings.shortcutToggleFullscreen === shortcutDefaults.shortcutToggleFullscreen && settings.shortcutStopStream === shortcutDefaults.shortcutStopStream && settings.shortcutToggleAntiAfk === shortcutDefaults.shortcutToggleAntiAfk && settings.shortcutToggleMicrophone === shortcutDefaults.shortcutToggleMicrophone @@ -945,6 +962,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, [ settings.shortcutToggleStats, settings.shortcutTogglePointerLock, + settings.shortcutToggleFullscreen, settings.shortcutStopStream, settings.shortcutToggleAntiAfk, settings.shortcutToggleMicrophone, @@ -956,6 +974,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, const handleResetShortcuts = useCallback(() => { setToggleStatsInput(shortcutDefaults.shortcutToggleStats); setTogglePointerLockInput(shortcutDefaults.shortcutTogglePointerLock); + setToggleFullscreenInput(shortcutDefaults.shortcutToggleFullscreen); setStopStreamInput(shortcutDefaults.shortcutStopStream); setToggleAntiAfkInput(shortcutDefaults.shortcutToggleAntiAfk); setToggleMicrophoneInput(shortcutDefaults.shortcutToggleMicrophone); @@ -963,6 +982,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, setRecordingInput(shortcutDefaults.shortcutToggleRecording); setToggleStatsError(null); setTogglePointerLockError(null); + setToggleFullscreenError(null); setStopStreamError(null); setToggleAntiAfkError(null); setToggleMicrophoneError(null); @@ -2078,6 +2098,25 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, /> +
+ Toggle Full Screen + e.target.select()} + onBlur={() => handleShortcutBlur("shortcutToggleFullscreen", toggleFullscreenInput)} + onPaste={(e) => handleShortcutPaste("shortcutToggleFullscreen", e)} + onKeyDown={(e) => handleShortcutCaptureKeyDown("shortcutToggleFullscreen", e)} + placeholder="Click here, then press a key" + title="Focus and press the key combination to bind" + spellCheck={false} + /> +
+
Stop Stream
- {(toggleStatsError || togglePointerLockError || stopStreamError || toggleAntiAfkError || toggleMicrophoneError || screenshotError || recordingError) && ( + {(toggleStatsError || togglePointerLockError || toggleFullscreenError || stopStreamError || toggleAntiAfkError || toggleMicrophoneError || screenshotError || recordingError) && ( {toggleStatsError || togglePointerLockError + || toggleFullscreenError || stopStreamError || toggleAntiAfkError || toggleMicrophoneError @@ -2199,9 +2239,9 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults, )} - {!toggleStatsError && !togglePointerLockError && !stopStreamError && !toggleAntiAfkError && !toggleMicrophoneError && !screenshotError && !recordingError && ( + {!toggleStatsError && !togglePointerLockError && !toggleFullscreenError && !stopStreamError && !toggleAntiAfkError && !toggleMicrophoneError && !screenshotError && !recordingError && ( - Click a field and press the keys to bind, or paste a shortcut ({shortcutExamples}). Escape cancels focus. Stop: {formatShortcutForDisplay(settings.shortcutStopStream, isMac)}. Mic: {formatShortcutForDisplay(settings.shortcutToggleMicrophone, isMac)}. Screenshot: {formatShortcutForDisplay(settings.shortcutScreenshot, isMac)}. Recording: {formatShortcutForDisplay(settings.shortcutToggleRecording, isMac)}. + Click a field and press the keys to bind, or paste a shortcut ({shortcutExamples}). Escape cancels focus. Full screen: {formatShortcutForDisplay(settings.shortcutToggleFullscreen, isMac)}. Stop: {formatShortcutForDisplay(settings.shortcutStopStream, isMac)}. Mic: {formatShortcutForDisplay(settings.shortcutToggleMicrophone, isMac)}. Screenshot: {formatShortcutForDisplay(settings.shortcutScreenshot, isMac)}. Recording: {formatShortcutForDisplay(settings.shortcutToggleRecording, isMac)}. )} @@ -2254,7 +2294,7 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults,
diff --git a/opennow-stable/src/shared/gfn.ts b/opennow-stable/src/shared/gfn.ts index 17854b62..753f6868 100644 --- a/opennow-stable/src/shared/gfn.ts +++ b/opennow-stable/src/shared/gfn.ts @@ -141,6 +141,7 @@ export interface Settings { mouseAcceleration: number; shortcutToggleStats: string; shortcutTogglePointerLock: string; + shortcutToggleFullscreen: string; shortcutStopStream: string; shortcutToggleAntiAfk: string; shortcutToggleMicrophone: string;