Compare commits

..
Author SHA1 Message Date
theupriser b625ff98eb UI: a click on a row only selects it; the switch and the BIOS button act on their own click
Sync git.upriser.nl mirror / sync (push) Skipped
2026-10-15 20:38:23 +02:00
theupriser b9d14fa4de UI: a switch flips with one click (the first click only selected the row)
Sync git.upriser.nl mirror / sync (push) Skipped
2026-10-15 20:37:39 +02:00
theupriser ad3b07097f UI: on a desktop keep the desktop's own scale (1.0), not a cap of 1.5
Sync git.upriser.nl mirror / sync (push) Skipped
2026-10-15 20:35:34 +02:00
theupriser e942f3264d UI: stop scaling at 1.5 on a desktop (a 1440p or ultrawide screen made everything twice as big); the TV mode still scales to the screen
Sync git.upriser.nl mirror / sync (push) Skipped
2026-10-15 20:33:16 +02:00
theupriser 0bb5708769 GNOME theme: Restart GNOME button after applying the theme (relogin through GDM when set up, else a logout) 2026-10-15 20:29:13 +02:00
theupriser 0c46b209b9 Merge pull request #85 from theupriser/feat/gnome-settings-prebuilt
Sync git.upriser.nl mirror / sync (push) Skipped
Single-file build / bundle (push) Successful in 28s
GNOME theme: install the prebuilt GNOME Settings with the Desktop panel
2026-10-15 20:17:44 +02:00
theupriser 2905b45ef7 Release 2.13.0: version, changelog, GNOME theme feature version 2026-10-15 20:10:50 +02:00
theupriser 645b3314be GNOME theme: install the prebuilt GNOME Settings with the Desktop panel
Downloaded from the steamify-gnome-theme 'settings' release (built by GitHub
Actions on current Arch, rebuilt weekly), checked against its sha256, installed
with pacman -U --ask 4. A failure only warns and keeps the stock Settings;
disabling the theme puts the stock gnome-control-center back.
2026-10-15 19:49:56 +02:00
10 changed files with 108 additions and 11 deletions

No files matched your search

+10
View File
@@ -5,6 +5,16 @@ All notable changes, per version and per commit. Versions follow
`steamify.sh`. Versions before 0.7.0 were numbered afterwards,
one per merged pull request.
## 2.13.0 - 2026-10-15
- **feat: GNOME support (the "SteamOS theme for GNOME" option, GDM session switching, the GNOME launcher/notifier/tray behaviour): the Vapor colors, the Steam Deck wallpaper, Ubuntu Dock, Tiling Assistant, Desktop Icons NG, AppIndicator and Steamify's own Vapor shell extension, with Switch to Desktop remembering KDE or GNOME. Prototype work since 2.12.0: `5dfc916`, `fc28ea4`, `ae12c1b`, `2b84e6a`, `c5b59d9`, `62a30e6`, `039f269`, `12eb562`, `c7f5f51`, `7904833`, `ce1aa1b`, `ab3b581`, `eaa3f28`, `4221ae6`, `8432f63`, `09ef0b1`, `68342e7`, `0e0bf57`, `7a84b0d`, `dbaeaa1`**
- `2ca288d` **fix: the NVIDIA gaming mode setup no longer fails with "the patched gamescope doesn't start" on a gamescope that works: the smoke test judged gamescope's exit status through a pipe into `grep -q` under `pipefail`, so gamescope's crash on the closed pipe (or a hung shutdown) counted as a failed test, intermittently, for the CPU-specific and the generic build alike (seen on an RTX 5080). The test now reads gamescope's output from a file and kills a hung shutdown (`timeout -k`)**
- `645b331` **feat: the GNOME theme installs GNOME Settings with the "GNOME Desktop" panel (dock, desktop icons, tiling) as a prebuilt package, downloaded from the `settings` release of steamify-gnome-theme (built by GitHub Actions on current Arch, rebuilt weekly) and checked against its `.sha256`; a failure keeps the stock Settings. Turning the theme off puts the stock `gnome-control-center` back. Installs with the GNOME theme on re-apply it (feature version 2.13.0)**
- **feat: after the GNOME theme is applied in a GNOME session, the app's done screen has "Restart GNOME" (GNOME Shell on Wayland can't restart in place, and the dock, extensions and Settings panel only show after a new login): with GDM autologin (the SteamOS conversion) Steamify's relogin helper logs the user straight back into the session they were in (without changing the session gaming mode's boot goes back to), else it is a plain logout (`gnome-session-quit`)**
- **fix: the app on a desktop (a window, however large, also fullscreen) keeps the desktop's own scale instead of blowing its 1280x720 layout up to the screen's height (twice as big on a 1440p or ultrawide monitor): the layout fills the window at the scale of GNOME or KDE, and only a window smaller than 1280x720 shrinks it. The TV mode (`--fullscreen`, gamescope) still scales to the screen**
- **fix: in the app a click on a row only selects it (a second click on the selected row used to flip it, so a click on a switch needed two); a click on the switch itself, or on the BIOS update button, acts at once and selects the row too**
## 2.12.0 - 2026-10-13
- **feat: setting up the SteamOS conversion on NVIDIA first checks that nothing blocks it (a pacman lock, less than 1.5 GB free), and a failed setup is remembered: a desktop notification now and a reminder in the menu until a setup works**
+4 -1
View File
@@ -195,7 +195,10 @@ backend_apply() {
feature_record_unticked
os_version_refresh
[[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " =~ \ (gaming|single|boot|kpin)\ ]] && RESTART_FOR_LOGIN=true
backend_event finished "\"failed\":$(json_list "${failed[@]}"),\"restart\":$(restart_needed && echo true || echo false)"
# (Only when the theme itself worked: a failed one has nothing to log in again for.)
local relogin=false
[[ " ${failed[*]:-} " != *" gnometheme "* ]] && gnometheme_relogin_needed "${TO_DISABLE[@]}" "${TO_ENABLE[@]}" && relogin=true
backend_event finished "\"failed\":$(json_list "${failed[@]}"),\"restart\":$(restart_needed && echo true || echo false),\"relogin\":$relogin"
}
backend_main() {
+60
View File
@@ -19,6 +19,10 @@ TILING_URL="https://github.com/ubuntu/Tiling-Assistant/releases/download/v56/til
TILING_SHA256=961da0c6b8e5960770e68a06d71205765870435dbe8fe0ccc1ec50d4cc78265a
GNOME_USER_EXTENSIONS="${XDG_DATA_HOME:-$HOME/.local/share}/gnome-shell/extensions"
GNOME_THEME_WALLPAPER="/usr/share/wallpapers/Steam Deck Logo Default.jpg"
# GNOME Settings with the "GNOME Desktop" panel (dock, desktop icons, tiling): built by GitHub Actions on current Arch
# and rebuilt weekly (steamify-gnome-theme, settings/), so it isn't compiled here (a few minutes, 800 steps).
GNOME_SETTINGS_PKG=steamify-gnome-control-center
GNOME_SETTINGS_URL="https://github.com/theupriser/steamify-gnome-theme/releases/download/settings"
gnometheme_status() {
grep -qsF "$GNOME_THEME_MARK" "$GNOME_THEME_CSS"
@@ -107,6 +111,54 @@ gnometheme_shell() {
done
}
gnometheme_relogin_needed() {
# A GNOME session is running and this run changed the theme: GNOME Shell on Wayland can't be restarted in place, so
# the new extensions, the dock and the Settings panel show after the user logs in again.
[[ " $* " == *" gnometheme "* ]] && pgrep -u "$(id -u)" -x gnome-shell >/dev/null 2>&1
}
gnometheme_relogin() {
# End the GNOME session. With GDM autologin set up by the conversion, steamify-gdm-relogin logs the user straight back
# into the session they were in (GDM takes it from AccountsService; the session is not changed here, unlike
# steamos-session-select, which would make GNOME the one gaming mode's boot goes back to). Without that helper there
# is no autologin: a plain logout, and the user logs in again.
local helper=/usr/local/libexec/steamify-gdm-relogin
if [[ -x "$helper" ]] && sudo -n "$helper"; then return 0; fi
gnome-session-quit --logout --no-prompt
}
gnometheme_settings_install() {
# The prebuilt GNOME Settings with the Desktop panel, replacing the stock one. Never fatal: the theme works without the
# panel, so a failed download or install keeps the stock Settings and says so.
pacman -Q "$GNOME_SETTINGS_PKG" >/dev/null 2>&1 && return 0
pacman -Q gnome-control-center >/dev/null 2>&1 || return 0
local cache="${XDG_CACHE_HOME:-$HOME/.cache}/steamify" file="$GNOME_SETTINGS_PKG-x86_64.pkg.tar.zst" want got
mkdir -p "$cache" || return 0
info "Downloading GNOME Settings with the Desktop panel..."
want="$(curl -fsL --connect-timeout 10 --max-time 30 "$GNOME_SETTINGS_URL/$file.sha256" 2>/dev/null | awk 'NR==1 {print $1}')"
if [[ -z "$want" ]] ||
! curl -fsL --retry 2 --connect-timeout 15 --max-time 300 -o "$cache/$file.part" "$GNOME_SETTINGS_URL/$file"; then
rm -f "$cache/$file.part"
warn "Couldn't download GNOME Settings with the Desktop panel: the stock GNOME Settings stays."
return 0
fi
got="$(sha256sum "$cache/$file.part" | awk '{print $1}')"
if [[ "$got" != "$want" ]]; then
rm -f "$cache/$file.part"
warn "GNOME Settings with the Desktop panel didn't match its checksum: the stock GNOME Settings stays."
return 0
fi
mv -f "$cache/$file.part" "$cache/$file"
# --ask 4: pacman may replace the stock gnome-control-center it conflicts with.
if sudo pacman -U --noconfirm --ask 4 "$cache/$file"; then
state_set gnometheme installed_settings 1
ok "GNOME Settings with the Desktop panel installed (under Appearance, after the next login)."
else
warn "Installing GNOME Settings with the Desktop panel failed (its libraries may differ from this PC's): the stock GNOME Settings stays."
fi
return 0
}
gnometheme_enable() {
if ! pacman -Q cachyos-vapor >/dev/null 2>&1; then
info "Installing cachyos-vapor (its wallpapers and colors)..."
@@ -134,7 +186,10 @@ gnometheme_enable() {
fi
{ echo "$GNOME_THEME_MARK"; patch_file steamify-gnome-gtk4.css; } > "$GNOME_THEME_CSS"
gnometheme_shell
gnometheme_settings_install
ok "SteamOS theme for GNOME on (apps pick up the colors when they start again)."
# (GNOME Shell on Wayland can't restart in place: the app offers "Restart GNOME", which is this.)
gnometheme_relogin_needed gnometheme && info "Log out and back in for the dock, the extensions and the Settings panel to show."
}
gnometheme_disable() {
@@ -154,6 +209,11 @@ gnometheme_disable() {
sudo pacman -R --noconfirm cachyos-vapor >/dev/null 2>&1 ||
info "Keeping cachyos-vapor installed (another package needs it)."
fi
if [[ -n "$(state_get gnometheme installed_settings)" ]]; then
# The stock GNOME Settings back (it conflicts with, and so replaces, ours).
sudo pacman -S --noconfirm --ask 4 gnome-control-center >/dev/null 2>&1 ||
info "Keeping GNOME Settings with the Desktop panel (the stock one couldn't be installed)."
fi
state_clear gnometheme
ok "Previous GNOME look restored."
}
+1 -1
View File
@@ -26,7 +26,7 @@ NO_PRESELECT=(boot silent nvsilent cec kpin hdmi extended_controller_support)
# FEATURE_BASELINE.
FEATURE_BASELINE=2.1.0
declare -A FEATURE_VERSION=(
[gaming]=2.9.0 [boot]=2.1.0 [silent]=2.11.0 [nvidia]=2.12.0 [bigpicture]=2.10.0 [nvsilent]=2.11.0 [theme]=2.1.0 [gnometheme]=2.12.0 [glyphs]=2.1.0 [single]=2.1.0
[gaming]=2.9.0 [boot]=2.1.0 [silent]=2.11.0 [nvidia]=2.12.0 [bigpicture]=2.10.0 [nvsilent]=2.11.0 [theme]=2.1.0 [gnometheme]=2.13.0 [glyphs]=2.1.0 [single]=2.1.0
[launcher]=2.1.0 [cec]=2.7.0 [machine]=2.9.0 [poweroff]=2.2.0 [vram]=2.3.0 [notify]=2.5.0 [steamgame]=2.5.1 [extended_controller_support]=2.10.0
[kpin]=2.1.0 [hdmi]=2.1.0
)
+1 -1
View File
@@ -15,7 +15,7 @@
set -uo pipefail
# Release version, see CHANGELOG.md.
VERSION=2.12.0
VERSION=2.13.0
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+4 -2
View File
@@ -18,6 +18,7 @@ Item {
property var steps: ({}) // id -> "wait"|"run"|"ok"|"fail"
property var failed: []
property bool restartNeeded: false
property bool reloginNeeded: false // GNOME loads the new theme's extensions at the next login
// Review & apply only when something would change; "Re-apply what's on"
// is for when nothing did.
readonly property bool canApply: computePlan().length > 0
@@ -123,7 +124,7 @@ Item {
}
// --- Password and apply ---
function resetRun() { steps = {}; failed = []; restartNeeded = false; runError = ""; doneCount = 0; logModel.clear(); }
function resetRun() { steps = {}; failed = []; restartNeeded = false; reloginNeeded = false; runError = ""; doneCount = 0; logModel.clear(); }
function startApply(password) {
resetRun();
var s = {};
@@ -203,7 +204,7 @@ Item {
// A successful BIOS check ends with bios-ready instead, keeping
// the password for the flash.
app.sessionPassword = "";
app.failed = ev.failed || []; app.restartNeeded = !!ev.restart;
app.failed = ev.failed || []; app.restartNeeded = !!ev.restart; app.reloginNeeded = !!ev.relogin;
app.runError = app.errorText(ev.error);
app.screen = "done";
}
@@ -249,6 +250,7 @@ Item {
else if (a === "release") { holding = false; if (holdProgress < 1) holdProgress = 0; }
} else if (screen === "done") {
if (a === "accept" && restartNeeded) backend.restart();
else if (a === "accept" && reloginNeeded) backend.restartGnome();
else if (a === "back" || a === "accept") backToMenu();
}
}
+4 -2
View File
@@ -9,7 +9,8 @@ Item {
readonly property bool problem: app.failed.length > 0 || app.runError !== ""
readonly property string message: {
if (app.runError) return app.runError;
if (!app.biosRun) return app.plan.length + (app.plan.length === 1 ? " change" : " changes") + " applied." + (app.restartNeeded ? " Some take effect after a restart." : "");
if (!app.biosRun) return app.plan.length + (app.plan.length === 1 ? " change" : " changes") + " applied." + (app.restartNeeded ? " Some take effect after a restart." : "")
+ (app.reloginNeeded && !app.restartNeeded ? " GNOME shows the dock, extensions and Settings panel after you log in again: Restart GNOME does that." : "");
if (app.failed.length) return "The BIOS was not changed.";
return (app.bios && app.bios.dryRun ? "Dry run: nothing was flashed. " : "BIOS " + (app.biosInfo.newest || "") + " is staged and is written during the restart. ")
+ "Keep the power on and don't touch the machine until it has fully started again, even if the screen stays black.";
@@ -33,6 +34,7 @@ Item {
delegate: Text { required property string line; width: details.width - 12; text: line; color: "#aab5c4"; font.family: Theme.mono; font.pixelSize: 13; wrapMode: Text.WrapAnywhere } } }
Row { anchors.horizontalCenter: parent.horizontalCenter; spacing: 14; topPadding: 8
Btn { k: Input.g.back; text: "Back to the menu"; height: 52; onClicked: app.act("back") }
Btn { visible: app.restartNeeded; k: Input.g.ok; text: "Restart now"; primary: true; height: 52; onClicked: app.backend.restart() } }
Btn { visible: app.restartNeeded; k: Input.g.ok; text: "Restart now"; primary: true; height: 52; onClicked: app.backend.restart() }
Btn { visible: app.reloginNeeded && !app.restartNeeded; k: Input.g.ok; text: "Restart GNOME"; primary: true; height: 52; onClicked: app.backend.restartGnome() } }
}
}
+5 -1
View File
@@ -36,7 +36,11 @@ ApplicationWindow {
// own shape, so the bars reach the edges and the content fills it.
Item {
id: stage
readonly property real k: Math.min(win.width / 1280, win.height / 720)
// On a desktop (a window, however large) the UI keeps the desktop's own scale: 1 here is one logical pixel, which Qt
// multiplies by the scale of GNOME or KDE, like any other app (a 1440p or ultrawide monitor would otherwise show
// everything twice as big). Only a window smaller than the layout shrinks it. The TV mode (--fullscreen, gamescope)
// scales to the screen: it is read from the couch.
readonly property real k: Math.min(win.width / 1280, win.height / 720, win.fullscreen ? Infinity : 1)
width: win.width / k; height: win.height / k
transformOrigin: Item.TopLeft
scale: k
+9 -3
View File
@@ -31,7 +31,9 @@ Item {
color: row.selected ? Theme.cardSel : Theme.card
border.width: row.selected ? 2 : 0; border.color: Theme.accent
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor
onClicked: { Input.type = "keyboard"; if (row.selected) app.act("accept"); else app.sel = row.rowIndex; } }
// A click on the row only selects it; the switch, the Gaming/Desktop buttons and the BIOS button act on their own
// click (below), selecting the row too.
onClicked: { Input.type = "keyboard"; app.sel = row.rowIndex; } }
Text { visible: !!row.it.parent; x: 20; anchors.verticalCenter: parent.verticalCenter; text: "└"; color: "#56627a"; font.pixelSize: 18 }
Column {
@@ -67,14 +69,18 @@ Item {
Rectangle { id: sw; visible: row.it.kind === "toggle"; anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter
width: 46; height: 26; radius: 13; color: row.on ? Theme.accent : "#343f50"
Behavior on color { ColorAnimation { duration: 120 } }
Rectangle { width: 20; height: 20; radius: 10; color: "#f4f7fb"; y: 3; x: row.on ? 23 : 3; Behavior on x { NumberAnimation { duration: 120 } } } }
Rectangle { width: 20; height: 20; radius: 10; color: "#f4f7fb"; y: 3; x: row.on ? 23 : 3; Behavior on x { NumberAnimation { duration: 120 } } }
MouseArea { anchors.fill: parent; anchors.margins: -6; cursorShape: Qt.PointingHandCursor
onClicked: { Input.type = "keyboard"; app.sel = row.rowIndex; app.act("accept"); } } }
Text { visible: row.it.kind === "toggle"; anchors.right: sw.left; anchors.rightMargin: 12; anchors.verticalCenter: parent.verticalCenter
text: row.it.on ? "now on" : "now off"; color: row.it.on ? Theme.good : Theme.faint; font.family: Theme.mono; font.pixelSize: 12 }
// action (Update BIOS)
Rectangle { visible: row.it.kind === "action"; anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter
width: at.implicitWidth + 24; height: 30; radius: 8; color: Theme.warnBg
Text { id: at; anchors.centerIn: parent; color: Theme.warn; font.family: Theme.body; font.pixelSize: 13; font.weight: Font.DemiBold
text: app.biosChecking ? "Checking…" : app.bios && app.bios.selectable ? "Update…" : (app.bios && app.bios.newest ? "Up to date" : "Unavailable") } }
text: app.biosChecking ? "Checking…" : app.bios && app.bios.selectable ? "Update…" : (app.bios && app.bios.newest ? "Up to date" : "Unavailable") }
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor
onClicked: { Input.type = "keyboard"; app.sel = row.rowIndex; app.act("accept"); } } }
}
}
}
+10
View File
@@ -345,6 +345,16 @@ class Backend(QObject):
def restart(self):
QProcess.startDetached("systemctl", ["reboot"])
@Slot()
def restartGnome(self):
# GNOME Shell on Wayland can't restart in place: end the session. With GDM autologin (the SteamOS conversion)
# Steamify's helper logs the user straight back in; else a plain logout.
import subprocess
helper = "/usr/local/libexec/steamify-gdm-relogin"
if os.access(helper, os.X_OK) and subprocess.run(["sudo", "-n", helper], capture_output=True).returncode == 0:
return
QProcess.startDetached("gnome-session-quit", ["--logout", "--no-prompt"])
def shutdown(self):
if self._proc and self._proc.state() != QProcess.NotRunning:
self._proc.kill()