254 lines
9.6 KiB
QML
254 lines
9.6 KiB
QML
// 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 the Focus tab.";
|
|
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.openSettings("focus")
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|