// Gaming: what the machine is doing now, and what it should do while you play. // // Live first, because unlike every other page here this one has a genuinely // live dimension -- "is Game Mode actually on, and how hot is the card" is the // question that brings someone here mid-session. // // The part that makes this Panama's page rather than a gamemode config editor // is "While a game is running": gamemode runs a script when a game starts and // another when it exits, so the power profile and Do Not Disturb can follow the // game and be put back afterwards -- back to what they were, not to a default. import Quickshell import QtQuick import qs.config import qs.services SettingsPage { id: root objectName: "gaming" title: "Gaming" lede: "What this machine is doing, and how it should behave while you play." readonly property var gpu: Gaming.primaryGpu // Polling only while this page is on screen. Component.onCompleted: { Gaming.refresh(); Gaming.watching = true; } Component.onDestruction: Gaming.watching = false TextRow { visible: Gaming.lastError !== "" label: "Gaming needs attention" detail: Gaming.lastError value: "" divider: false } // ── Right now ──────────────────────────────────────────────────────────── SettingsCard { Column { width: parent.width spacing: 8 Row { width: parent.width spacing: 12 Text { text: Gaming.active ? "Playing" : "Idle" color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeTitle font.weight: Font.DemiBold } Text { anchors.verticalCenter: parent.verticalCenter text: Gaming.active ? "Game Mode is engaged" : "No game has requested Game Mode" color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSize } } } Repeater { model: Gaming.gpus delegate: TextRow { required property var modelData required property int index width: parent.width label: modelData.discrete ? "Graphics card" : "Integrated graphics" detail: String(modelData.driver ?? "") + (modelData.discrete ? " · the one games use" : " · idle unless something asks for it") value: Gaming.gpuSummary(modelData) divider: true } } TextRow { label: "CPU governor" detail: "What the cores are scaling to right now" value: String(Gaming.gameMode?.governorNow ?? "unknown") divider: false } } // ── What Panama does about it ──────────────────────────────────────────── SettingsCard { title: "While a game is running" subtitle: Gaming.gameMode?.hooksInstalled === true ? "Panama reacts when Game Mode engages, and puts everything back when the game exits." : "Panama can react when Game Mode engages. This needs a hook in gamemode's configuration." ActionRow { visible: Gaming.gameMode?.hooksInstalled !== true label: "Let Panama react to games" detail: "Adds a start and end hook to your gamemode configuration. Nothing else in that file is touched." action: "Enable" enabled: !Gaming.busy && Gaming.gameMode?.available === true onTriggered: Gaming.installHooks() } ToggleRow { visible: Gaming.gameMode?.hooksInstalled === true setting: "gamingPerformanceProfile" } // Silencing while a game runs is a focus mode with a trigger, and it // now lives with the others -- so "something silenced my notifications" // has one answer rather than two places to look. ActionRow { visible: Gaming.gameMode?.hooksInstalled === true label: "Silence notifications" detail: { const mode = FocusModes.modes.find(entry => entry.id === "gaming"); if (!mode) return "Handled by a focus mode on Notifications & Focus."; return mode.enabled === true ? "Handled by the Gaming focus mode, which is on." : "The Gaming focus mode is off, so notifications are not silenced."; } action: "Open Focus" onTriggered: ShellState.settingsPage = "notifications" } ToggleRow { visible: Gaming.gameMode?.hooksInstalled === true setting: "gamingNotifyOnStart" } ActionRow { visible: Gaming.gameMode?.hooksInstalled === true label: "Stop reacting to games" detail: "Removes the hooks. The settings above are kept." action: "Disable" enabled: !Gaming.busy divider: false onTriggered: Gaming.removeHooks() } } // ── Game Mode itself ───────────────────────────────────────────────────── SettingsCard { title: "Game Mode" subtitle: Gaming.gameMode?.available === true ? "Applied by gamemode to a game while it runs, then undone." : "gamemode is not installed." TextRow { label: "Daemon" detail: Gaming.gameMode?.daemonRunning === true ? "Running, waiting for a game to ask" : "Not running, so no game can request it" value: Gaming.gameMode?.daemonRunning === true ? "Running" : "Stopped" } // Said plainly rather than implied: on a machine already running the // governor gamemode would switch to, its headline effect is nothing. TextRow { label: "Governor while gaming" detail: Gaming.governorAlreadyThere ? "This machine already runs that governor, so Game Mode changes nothing here" : "Switched for the duration of the game" value: String(Gaming.gameMode?.governorWhileGaming ?? "unknown") divider: false } } // ── Overlay ────────────────────────────────────────────────────────────── SettingsCard { title: "Performance overlay" subtitle: Gaming.overlay?.installed === true ? "MangoHud, drawn on top of the game." : "MangoHud is not installed." SwitchRow { label: "Show the overlay in games" detail: "Takes effect for games launched after your next sign-in, because it is read from the session environment" checked: Gaming.overlay?.globallyEnabled === true enabled: !Gaming.busy && Gaming.overlay?.installed === true onToggled: value => Gaming.setOverlay(value) } SegmentRow { label: "What it shows" detail: "Frame rate alone, or the full readout with GPU and CPU" options: [ { value: "fps", label: "Frame rate" }, { value: "detailed", label: "Detailed" } ] value: String(Gaming.overlay?.preset ?? "fps") enabled: !Gaming.busy && Gaming.overlay?.installed === true onSelected: value => Gaming.setOverlayPreset(value) } TextRow { label: "Toggle in game" detail: "Shows and hides the overlay without leaving the game" value: String(Gaming.overlay?.toggleKey ?? "") divider: false } } // ── Library ────────────────────────────────────────────────────────────── SettingsCard { title: "Library" subtitle: "Where games live, and what can run them." TextRow { label: "Installed games" detail: String(Gaming.library?.path ?? "") value: String(Gaming.library?.games ?? 0) } // Listed, not chosen. Steam picks the runtime per game in its own // properties, and a control here would be claiming an authority this // page does not have. Repeater { model: Gaming.library?.protonBuilds ?? [] delegate: TextRow { required property var modelData required property int index width: parent.width label: String(modelData.name ?? "") detail: modelData.community === true ? "Community build · chosen per game in Steam" : "Valve · chosen per game in Steam" value: "Installed" divider: true } } TextRow { label: "gamescope" detail: "Micro-compositor for scaling and frame limiting, used per game from Steam" value: Gaming.library?.gamescope === true ? "Available" : "Not installed" divider: false } } }