From dbaeaa14c9c6f1e8fdab658a847a7f75ce084b80 Mon Sep 17 00:00:00 2001 From: Rick Peters Date: Wed, 14 Oct 2026 15:44:37 +0200 Subject: [PATCH] App id io.github.theupriser.Steamify (GNOME matches windows to launchers only by reverse-DNS ids), own dark window frame on GNOME, launcher icon as a full path --- AGENTS.md | 4 ++-- lib/wizard-shortcut.sh | 24 +++++++++++++++++------- patches/steamify-notifier.py | 2 +- ui/qml/Header.qml | 35 ++++++++++++++++++++++++++++++++++- ui/qml/Main.qml | 20 +++++++++++++++++++- ui/steamify-app.sh | 6 ++++-- ui/steamify-ui | 8 +++++++- 7 files changed, 84 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 69ce721..c522a89 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -162,7 +162,7 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine - A release that adds, removes or renames a menu row also retakes the README screenshot (`assets/screenshot-menu-steam-machine.png`; NVIDIA PCs get the same menu, so there is no separate one): the app from the branch on a Steam Machine, every row visible and the window only as tall as its options need, the header showing the new - `VERSION`, 1600 px wide. Size the window with a KWin script (match the `steamify-ui` class exactly: a title match also hits + `VERSION`, 1600 px wide. Size the window with a KWin script (match the `io.github.theupriser.Steamify` class exactly (`steamify-ui` until the GNOME work): a title match also hits Konsole and editor windows) and crop the full-screen capture to the client rectangle; mind the display scale (the Steam Machine runs at 1.75). - Users install through `releases/latest/download/steamify.sh` @@ -259,7 +259,7 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine again. Scripted input that runs out quits without restarting (never restart on EOF: `ask_yn` would take its default). - Steamify shortcut (`launcher`): "Steamify CachyOS" (desktop and launcher, - id `steamify-ui.desktop`, the app's own id) runs `curl | bash` of + id `io.github.theupriser.Steamify.desktop`, the app's own id; `steamify-ui.desktop` until the GNOME work) runs `curl | bash` of `releases/latest/download/steamify-app.sh` without a terminal (in Konsole only while PySide6 is missing, for sudo); "Steamify Terminal" (launcher) runs `steamify.sh` in Konsole. Both always the newest release. The entry diff --git a/lib/wizard-shortcut.sh b/lib/wizard-shortcut.sh index c41b8be..f02bc92 100644 --- a/lib/wizard-shortcut.sh +++ b/lib/wizard-shortcut.sh @@ -14,9 +14,11 @@ WIZARD_ICON="${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/scalable/apps/st WIZARD_LAUNCHER="$STEAMIFY_BIN/run-wizard" WIZARD_APP_LAUNCHER="$STEAMIFY_BIN/run-app" WIZARD_APPS="${XDG_DATA_HOME:-$HOME/.local/share}/applications" -# steamify-ui: the id the app gives itself, so its window belongs to this +# io.github.theupriser.Steamify: the id the app gives itself (reverse-DNS: GNOME matches windows only by such ids), so its window belongs to this # entry. steamify-app.sh leaves an entry with X-Steamify-Shortcut alone. -WIZARD_DESKTOP_NAME="steamify-ui.desktop" +WIZARD_DESKTOP_NAME="io.github.theupriser.Steamify.desktop" +# Until 2.12.0 the id was steamify-ui. +WIZARD_ID_BEFORE_ENTRY="steamify-ui.desktop" WIZARD_APP_ENTRY="$WIZARD_APPS/$WIZARD_DESKTOP_NAME" WIZARD_TERMINAL_ENTRY="$WIZARD_APPS/steamify-terminal.desktop" # Before 2.0.1 the shortcut opened the terminal menu under this name. @@ -56,7 +58,12 @@ launcher_status() { # A shortcut from before 2.0.1 (terminal only): the menu ticks the item, so a # normal run replaces it. -launcher_repair() { [[ -f "$WIZARD_APPS/$WIZARD_OLD_NAME" ]] && ! launcher_status; } +launcher_repair() { + # Also the shortcut from before the reverse-DNS app id (its entry carries the marker): the menu ticks it, a normal run replaces it. + ! launcher_status || return 1 + [[ -f "$WIZARD_APPS/$WIZARD_OLD_NAME" ]] || + grep -q '^X-Steamify-Shortcut=true' "$WIZARD_APPS/$WIZARD_ID_BEFORE_ENTRY" 2>/dev/null +} launcher_enable() { local terminal @@ -105,7 +112,8 @@ EOF tmp="$(mktemp)" if curl -fsSL --max-time 20 "$WIZARD_RELEASE/steam-gaming-settings.svg" -o "$tmp" && grep -q '/dev/null || true else warn "Couldn't download the shortcut's icon; using Steam's icon instead." @@ -121,7 +129,7 @@ Exec=$WIZARD_APP_LAUNCHER Icon=$icon Terminal=false Categories=Game;System;Settings; -StartupWMClass=steamify-ui +StartupWMClass=io.github.theupriser.Steamify X-Steamify-Shortcut=true" printf '%s\n' "$entry" | install_executable "$WIZARD_APP_ENTRY" 644 printf '%s\n' "$entry" | install_executable "$(wizard_desktop_dir)/$WIZARD_DESKTOP_NAME" 755 @@ -135,8 +143,9 @@ Exec=$terminal $WIZARD_LAUNCHER Icon=$icon Terminal=false Categories=Game;System;Settings;" | install_executable "$WIZARD_TERMINAL_ENTRY" 644 - # The terminal-only shortcut from before 2.0.1. - rm -f "$(wizard_desktop_dir)/$WIZARD_OLD_NAME" "$WIZARD_APPS/$WIZARD_OLD_NAME" + # The terminal-only shortcut from before 2.0.1, and the app's entry under its id before the reverse-DNS one. + rm -f "$(wizard_desktop_dir)/$WIZARD_OLD_NAME" "$WIZARD_APPS/$WIZARD_OLD_NAME" \ + "$(wizard_desktop_dir)/$WIZARD_ID_BEFORE_ENTRY" "$WIZARD_APPS/$WIZARD_ID_BEFORE_ENTRY" kbuildsycoca6 >/dev/null 2>&1 || true ok "Steamify shortcut added: \"Steamify CachyOS\" (the app) on the desktop and in the launcher, \"Steamify Terminal\" in the launcher." } @@ -147,6 +156,7 @@ launcher_disable() { grep -q '^X-Steamify-Shortcut=true' "$WIZARD_APP_ENTRY" 2>/dev/null && rm -f "$WIZARD_APP_ENTRY" rm -f "$(wizard_desktop_dir)/$WIZARD_DESKTOP_NAME" "$WIZARD_TERMINAL_ENTRY" \ "$(wizard_desktop_dir)/$WIZARD_OLD_NAME" "$WIZARD_APPS/$WIZARD_OLD_NAME" \ + "$(wizard_desktop_dir)/$WIZARD_ID_BEFORE_ENTRY" "$WIZARD_APPS/$WIZARD_ID_BEFORE_ENTRY" \ "$WIZARD_LAUNCHER" "$WIZARD_APP_LAUNCHER" "$WIZARD_ICON" rmdir "$(dirname "$WIZARD_LAUNCHER")" 2>/dev/null || true kbuildsycoca6 >/dev/null 2>&1 || true diff --git a/patches/steamify-notifier.py b/patches/steamify-notifier.py index e4cb67d..542c629 100644 --- a/patches/steamify-notifier.py +++ b/patches/steamify-notifier.py @@ -107,7 +107,7 @@ def main(): app = QApplication(sys.argv[:1]) app.setApplicationName("Steamify") - app.setDesktopFileName("steamify-ui") + app.setDesktopFileName("io.github.theupriser.Steamify") app.setQuitOnLastWindowClosed(False) icon = tray_icon() diff --git a/ui/qml/Header.qml b/ui/qml/Header.qml index db4d73c..99f2329 100644 --- a/ui/qml/Header.qml +++ b/ui/qml/Header.qml @@ -4,6 +4,8 @@ import QtQuick Rectangle { required property AppState app required property string iconUrl + // Our own window frame (GNOME): drag the header, double-click to maximize, and the buttons at the right. + property bool customFrame: false readonly property var titles: ({ review: "Review", password: "Password", applying: app.biosRun ? "BIOS update" : "Applying", done: "Done", bios1: "BIOS update", bios2: "BIOS update" }) width: parent.width; height: 72; color: Theme.bar @@ -18,8 +20,39 @@ Rectangle { } Rectangle { visible: app.status.steamMachine === true - anchors.right: parent.right; anchors.rightMargin: 40; anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right; anchors.rightMargin: customFrame ? 190 : 40; anchors.verticalCenter: parent.verticalCenter height: 34; radius: 17; color: "#1b2330"; width: mt.implicitWidth + 28 Text { id: mt; anchors.centerIn: parent; text: "Steam Machine ยท CachyOS"; color: Theme.label; font.family: Theme.body; font.pixelSize: 14 } } + // Moving the window (and maximizing by double-click) is the part of the header left of the buttons. + Item { + visible: customFrame + anchors { left: parent.left; top: parent.top; bottom: parent.bottom; right: controls.left } + DragHandler { target: null; onActiveChanged: if (active) Window.window.startSystemMove() } + TapHandler { onDoubleTapped: Window.window.visibility === Window.Maximized ? Window.window.showNormal() : Window.window.showMaximized() } + } + Row { + id: controls + visible: customFrame + anchors.right: parent.right; anchors.rightMargin: 16; anchors.verticalCenter: parent.verticalCenter; spacing: 6 + Repeater { + model: [ { g: "\u2013", a: "min" }, { g: "\u25a1", a: "max" }, { g: "\u2715", a: "close" } ] + Rectangle { + required property var modelData + width: 44; height: 44; radius: 22 + color: area.containsMouse ? (modelData.a === "close" ? "#c0392b" : Theme.line) : "transparent" + Text { anchors.centerIn: parent; text: modelData.g; color: Theme.text; font.family: Theme.body; font.pixelSize: 20 } + MouseArea { + id: area + anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor + onClicked: { + var w = Window.window + if (modelData.a === "min") w.showMinimized() + else if (modelData.a === "max") { if (w.visibility === Window.Maximized) w.showNormal(); else w.showMaximized() } + else Qt.quit() + } + } + } + } + } } diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index 41ced9b..59f3032 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -11,12 +11,15 @@ ApplicationWindow { required property var backend required property var gamepad required property bool fullscreen + // The app draws the window frame (GNOME): the header carries the buttons, the edges resize. + required property bool customFrame required property string iconUrl required property bool fromSteam Component.onCompleted: Input.fromSteam = fromSteam width: 1280; height: 720 minimumWidth: 960; minimumHeight: 540 + flags: customFrame ? (Qt.Window | Qt.FramelessWindowHint) : Qt.Window visibility: fullscreen ? Window.FullScreen : Window.Windowed title: "Steamify" color: Theme.bg @@ -41,7 +44,7 @@ ApplicationWindow { Keys.onPressed: function (e) { steamify.keyPressed(e); } Keys.onReleased: function (e) { steamify.keyReleased(e); } - Header { id: header; app: steamify; iconUrl: win.iconUrl } + Header { id: header; app: steamify; iconUrl: win.iconUrl; customFrame: win.customFrame } Item { id: content @@ -55,4 +58,19 @@ ApplicationWindow { DoneScreen { anchors.fill: parent; app: steamify; visible: steamify.screen === "done" } } } + + // Resize handles around a window with our own frame. + component Edge: MouseArea { + required property int edges + hoverEnabled: true + onPressed: win.startSystemResize(edges) + } + Edge { visible: win.customFrame; edges: Qt.LeftEdge; width: 6; anchors { left: parent.left; top: parent.top; bottom: parent.bottom; topMargin: 8; bottomMargin: 8 } cursorShape: Qt.SizeHorCursor } + Edge { visible: win.customFrame; edges: Qt.RightEdge; width: 6; anchors { right: parent.right; top: parent.top; bottom: parent.bottom; topMargin: 8; bottomMargin: 8 } cursorShape: Qt.SizeHorCursor } + Edge { visible: win.customFrame; edges: Qt.TopEdge; height: 6; anchors { top: parent.top; left: parent.left; right: parent.right; leftMargin: 8; rightMargin: 8 } cursorShape: Qt.SizeVerCursor } + Edge { visible: win.customFrame; edges: Qt.BottomEdge; height: 6; anchors { bottom: parent.bottom; left: parent.left; right: parent.right; leftMargin: 8; rightMargin: 8 } cursorShape: Qt.SizeVerCursor } + Edge { visible: win.customFrame; edges: Qt.LeftEdge | Qt.TopEdge; width: 8; height: 8; anchors { left: parent.left; top: parent.top } cursorShape: Qt.SizeFDiagCursor } + Edge { visible: win.customFrame; edges: Qt.RightEdge | Qt.TopEdge; width: 8; height: 8; anchors { right: parent.right; top: parent.top } cursorShape: Qt.SizeBDiagCursor } + Edge { visible: win.customFrame; edges: Qt.LeftEdge | Qt.BottomEdge; width: 8; height: 8; anchors { left: parent.left; bottom: parent.bottom } cursorShape: Qt.SizeBDiagCursor } + Edge { visible: win.customFrame; edges: Qt.RightEdge | Qt.BottomEdge; width: 8; height: 8; anchors { right: parent.right; bottom: parent.bottom } cursorShape: Qt.SizeFDiagCursor } } diff --git a/ui/steamify-app.sh b/ui/steamify-app.sh index fd3c012..ca6ecd6 100755 --- a/ui/steamify-app.sh +++ b/ui/steamify-app.sh @@ -57,8 +57,9 @@ fi # Steamify shortcut's entry has the same id and always starts the newest # release: left alone. mkdir -p "$APPS" -grep -q '^X-Steamify-Shortcut=true' "$APPS/steamify-ui.desktop" 2>/dev/null || -cat > "$APPS/steamify-ui.desktop" << EOF +rm -f "$APPS/steamify-ui.desktop" # its name until 2.12.0 +grep -q '^X-Steamify-Shortcut=true' "$APPS/io.github.theupriser.Steamify.desktop" 2>/dev/null || +cat > "$APPS/io.github.theupriser.Steamify.desktop" << EOF [Desktop Entry] Type=Application Name=Steamify CachyOS @@ -67,6 +68,7 @@ Exec=$DEST/ui/steamify-ui Icon=$DEST/assets/steam-gaming-settings.svg Terminal=false Categories=Game;Settings; +StartupWMClass=io.github.theupriser.Steamify EOF command -v update-desktop-database >/dev/null && update-desktop-database "$APPS" 2>/dev/null || true diff --git a/ui/steamify-ui b/ui/steamify-ui index 2fefa95..8bf2f27 100755 --- a/ui/steamify-ui +++ b/ui/steamify-ui @@ -362,8 +362,13 @@ def main(): fullscreen = "--fullscreen" in sys.argv or (in_gamescope and "--windowed" not in sys.argv) app = QGuiApplication(sys.argv) + # GNOME draws no frame around a Qt window (Qt's own is light grey, whatever the style): the app draws + # its own, in its colors. Other desktops keep theirs (KWin's follows the Plasma colors). + custom_frame = (not fullscreen and app.platformName() == "wayland" + and "GNOME" in os.environ.get("XDG_CURRENT_DESKTOP", "").upper()) app.setApplicationName("Steamify") - app.setDesktopFileName("steamify-ui") + # A reverse-DNS id: GNOME on Wayland only matches a window to its launcher (icon in the dock) by such an id. + app.setDesktopFileName("io.github.theupriser.Steamify") icon = os.path.join(HERE, "..", "assets", "steam-gaming-settings.svg") if os.path.isfile(icon): app.setWindowIcon(QIcon(icon)) @@ -373,6 +378,7 @@ def main(): engine = QQmlApplicationEngine() engine.setInitialProperties({"backend": backend, "gamepad": pad, "fullscreen": fullscreen, + "customFrame": custom_frame, # Started from Steam: its keys are a Steam # Controller's buttons (desktop layout). "fromSteam": bool(os.environ.get("SteamGameId")),