While Caelestia's native GameMode.qml service successfully disabled compositor-level features via Hypr.extras.applyOptions, the shell itself was left untouched: its UI animations kept rendering in the background, and panels remained translucent without any blur behind them.
Extending the service to disable internal shell animations and panel translucency introduced visual artifacts: application windows remained transparent (without blur, which made the desktop background leak through games) and repeated toggles broke. Additionally, if the shell restarted or crashed while Game Mode was active, all shell-level animations remained permanently locked at speed 0 on subsequent boots.
I analyzed Caelestia Shell's singleton services and variables configuration files to isolate three integration issues:
- Window Rules Stack Overflow: The service attempted to enforce opacity by dispatching
opaque truerules dynamically via socket. But Hyprland already hadwindowrule = opacity $windowOpacity overrideinrules.conf. Appending dynamic rules via IPC messages polluted the stack and created race conditions on rapid toggles. - Volatile Animation State Caching: When Game Mode runs, the service updates
GlobalConfig.appearance.anim.durations.scale = 0. This is immediately saved intoshell.jsonon disk. When the shell crashed or restarted, it loaded with scale 0, and since the minimum UI scale configurable is 0.1, the user was stuck without animations forever. - Static Palette Binding: The colors service compiled the translucent palette statically from
Tokens.transparency.enabledinstead of monitoring the reactive configuration service, causing the panel's transparency to stay active during Game Mode.
I integrated Caelestia Shell's native Game Mode service with internal visual switches and resolved these visual conflicts using the following QML and configuration patches:
1. Bridging Game Mode through Sourced Config Files
Rather than injecting window rules through socket commands, I leveraged Hyprland's native file-based sourcing system. Hyprland reads hypr-vars.conf before any rules. I modified the toggle to write directly to this file:
onEnabledChanged: {
if (enabled) {
// Save old states
oldAnimScale = GlobalConfig.appearance.anim.durations.scale;
oldTransparency = GlobalConfig.appearance.transparency.enabled;
// Force shell elements to be fast and opaque
GlobalConfig.appearance.anim.durations.scale = 0;
GlobalConfig.appearance.transparency.enabled = false;
setDynamicConfs(); // apply options: animations 0, shadows 0, blur 0...
// Write the $windowOpacity override to the sourced vars config and reload
Quickshell.execDetached(["bash", "-c", "echo '$windowOpacity = 1.0' > /home/franz/.config/caelestia/hypr-vars.conf && hyprctl reload"]);
} else {
// Restore shell variables
GlobalConfig.appearance.anim.durations.scale = oldAnimScale;
GlobalConfig.appearance.transparency.enabled = oldTransparency;
// Clear overrides and reload compositor configuration
Quickshell.execDetached(["bash", "-c", "true > /home/franz/.config/caelestia/hypr-vars.conf && hyprctl reload"]);
}
}
2. Preventing Persistent Animation Lockouts on Startup
To prevent a crash or sudden restart from locking scale at 0, I added an auto-recovery verification block in the component completion handler. If the scale factor loaded is 0 while Game Mode is off, it auto-resets back to 1:
Component.onCompleted: {
if (!enabled && GlobalConfig.appearance.anim.durations.scale === 0) {
GlobalConfig.appearance.anim.durations.scale = 1;
GlobalConfig.appearance.transparency.enabled = true;
}
}
3. Securing Shell Transparency Responsiveness
In Colours.qml, I rewrote the translucent palette mapping and the Transparency component definitions to rely on the reactive global configuration property instead of the static token property. This guarantees QML immediately updates panel opacities whenever the toggle changes:
readonly property var tPalette: transparency.enabled ? translucentPalette : palette
component Transparency: QtObject {
readonly property bool enabled: GlobalConfig.appearance.transparency.enabled
}