Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package vermouth for openSUSE:Factory 
checked in at 2026-08-15 22:40:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/vermouth (Old)
 and      /work/SRC/openSUSE:Factory/.vermouth.new.1258 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "vermouth"

Sat Aug 15 22:40:55 2026 rev:22 rq:1371264 version:2.0.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/vermouth/vermouth.changes        2026-08-09 
21:37:54.523491343 +0200
+++ /work/SRC/openSUSE:Factory/.vermouth.new.1258/vermouth.changes      
2026-08-15 22:41:24.088223887 +0200
@@ -1,0 +2,9 @@
+Fri Aug 14 21:46:23 UTC 2026 - Jehu Marcos Herrera Puentes 
<[email protected]>
+
+- Bump to version 2.0.2:
+  * fix(runtime): Fixes for missing runtimes and download
+  * feat: Playtime
+  * Update KDE runtime on the flatpack
+  * Update sha/commits
+
+-------------------------------------------------------------------

Old:
----
  vermouth-2.0.1.tar.xz

New:
----
  vermouth-2.0.2.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ vermouth.spec ++++++
--- /var/tmp/diff_new_pack.knV2Gu/_old  2026-08-15 22:41:24.829249972 +0200
+++ /var/tmp/diff_new_pack.knV2Gu/_new  2026-08-15 22:41:24.831250042 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           vermouth
-Version:        2.0.1
+Version:        2.0.2
 Release:        0
 Summary:        A Wine/Proton game launcher for KDE
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.knV2Gu/_old  2026-08-15 22:41:24.869251380 +0200
+++ /var/tmp/diff_new_pack.knV2Gu/_new  2026-08-15 22:41:24.873251520 +0200
@@ -3,7 +3,7 @@
   <service name="tar_scm" mode="manual">
     <param name="scm">git</param>
     <param name="url">https://github.com/dekomote/vermouth.git</param>
-    <param name="revision">v2.0.1</param>
+    <param name="revision">v2.0.2</param>
     <param name="match-tag">v[0-9]*</param>
     <param name="filename">vermouth</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.knV2Gu/_old  2026-08-15 22:41:24.905252647 +0200
+++ /var/tmp/diff_new_pack.knV2Gu/_new  2026-08-15 22:41:24.908252753 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/dekomote/vermouth.git</param>
-              <param 
name="changesrevision">2b13fc2e4eecac239df0f2ab4bba9b5749ddecbc</param></service></servicedata>
+              <param 
name="changesrevision">bd9d0f8d76ac1a966bb912afa027cdd4e06f645d</param></service></servicedata>
 (No newline at EOF)
 

++++++ vermouth-2.0.1.tar.xz -> vermouth-2.0.2.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/CMakeLists.txt 
new/vermouth-2.0.2/CMakeLists.txt
--- old/vermouth-2.0.1/CMakeLists.txt   2026-08-05 15:43:55.000000000 +0200
+++ new/vermouth-2.0.2/CMakeLists.txt   2026-08-11 12:07:42.000000000 +0200
@@ -1,5 +1,5 @@
 cmake_minimum_required(VERSION 3.20)
-project(vermouth VERSION 2.0.1 LANGUAGES CXX)
+project(vermouth VERSION 2.0.2 LANGUAGES CXX)
 
 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -95,6 +95,7 @@
         qml/GogView.qml
         qml/DownloaderProgress.qml
         qml/FlatpakHostHint.qml
+        qml/TimeUtils.js
     NO_RESOURCE_TARGET_PATH
 )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vermouth-2.0.1/assets/com.dekomote.vermouth.metainfo.xml.in 
new/vermouth-2.0.2/assets/com.dekomote.vermouth.metainfo.xml.in
--- old/vermouth-2.0.1/assets/com.dekomote.vermouth.metainfo.xml.in     
2026-08-05 15:43:55.000000000 +0200
+++ new/vermouth-2.0.2/assets/com.dekomote.vermouth.metainfo.xml.in     
2026-08-11 12:07:42.000000000 +0200
@@ -40,6 +40,11 @@
   </screenshots>
   <content_rating type="oars-1.1" />
   <releases>
+      <release version="2.0.2" date="2026-08-11">
+        <description>
+          <p>This release adds the playtime feature, fixes GE Proton download 
url and adds a check when runtimes are missing.</p>
+        </description>
+    </release>
     <release version="2.0.1" date="2026-08-05">
         <description>
           <p>This release adds the UZDOOM runtime, the Default Runtime 
feature, the Sort feature and several minor fixes/QoL addons.</p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/com.dekomote.vermouth.flathub.yml 
new/vermouth-2.0.2/com.dekomote.vermouth.flathub.yml
--- old/vermouth-2.0.1/com.dekomote.vermouth.flathub.yml        2026-08-05 
15:43:55.000000000 +0200
+++ new/vermouth-2.0.2/com.dekomote.vermouth.flathub.yml        2026-08-11 
12:07:42.000000000 +0200
@@ -1,6 +1,6 @@
 app-id: com.dekomote.vermouth
 runtime: org.kde.Platform
-runtime-version: '6.10'
+runtime-version: '6.11'
 sdk: org.kde.Sdk
 command: vermouth
 
@@ -98,5 +98,5 @@
     sources:
       - type: git
         url: https://github.com/dekomote/vermouth.git
-        tag: v2.0.1
-        commit: 90b29349531f4294a18659b11c95d49cdfe421fb
+        tag: v2.0.2
+        commit: 2b13fc2e4eecac239df0f2ab4bba9b5749ddecbc
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/com.dekomote.vermouth.yml 
new/vermouth-2.0.2/com.dekomote.vermouth.yml
--- old/vermouth-2.0.1/com.dekomote.vermouth.yml        2026-08-05 
15:43:55.000000000 +0200
+++ new/vermouth-2.0.2/com.dekomote.vermouth.yml        2026-08-11 
12:07:42.000000000 +0200
@@ -1,6 +1,6 @@
 app-id: com.dekomote.vermouth
 runtime: org.kde.Platform
-runtime-version: '6.10'
+runtime-version: '6.11'
 sdk: org.kde.Sdk
 command: vermouth
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/flake.nix new/vermouth-2.0.2/flake.nix
--- old/vermouth-2.0.1/flake.nix        2026-08-05 15:43:55.000000000 +0200
+++ new/vermouth-2.0.2/flake.nix        2026-08-11 12:07:42.000000000 +0200
@@ -16,7 +16,7 @@
         default = pkgs.stdenv.mkDerivation {
           pname = "vermouth";
           dontWrapQtApps = true;
-          version = "2.0.1";
+          version = "2.0.2";
           src = ./.;
 
           nativeBuildInputs = with pkgs; [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/packaging/.SRCINFO 
new/vermouth-2.0.2/packaging/.SRCINFO
--- old/vermouth-2.0.1/packaging/.SRCINFO       2026-08-05 15:43:55.000000000 
+0200
+++ new/vermouth-2.0.2/packaging/.SRCINFO       2026-08-11 12:07:42.000000000 
+0200
@@ -1,6 +1,6 @@
 pkgbase = vermouth
        pkgdesc = A game and app launcher for Linux - native, Windows, and retro
-       pkgver = 2.0.1
+       pkgver = 2.0.2
        pkgrel = 1
        url = https://github.com/dekomote/vermouth
        arch = x86_64
@@ -19,7 +19,7 @@
        optdepends = sdl2: gamepad support
        provides = vermouth
        conflicts = vermouth
-       source = 
vermouth-2.0.1.tar.gz::https://github.com/dekomote/vermouth/archive/refs/tags/v2.0.1.tar.gz
-       sha256sums = 
6b4e90b986dddc4086f746801aab3e1fbe564af77fcbbaaffd4408a49143cc7d
+       source = 
vermouth-2.0.2.tar.gz::https://github.com/dekomote/vermouth/archive/refs/tags/v2.0.2.tar.gz
+       sha256sums = 
60269ae19bc12f6004250899580a70afbce4fbc6c310bf3c634b3e29925ffb05
 
 pkgname = vermouth
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/packaging/PKGBUILD 
new/vermouth-2.0.2/packaging/PKGBUILD
--- old/vermouth-2.0.1/packaging/PKGBUILD       2026-08-05 15:43:55.000000000 
+0200
+++ new/vermouth-2.0.2/packaging/PKGBUILD       2026-08-11 12:07:42.000000000 
+0200
@@ -1,6 +1,6 @@
 # Maintainer: Dejan Noveski <[email protected]>
 pkgname=vermouth
-pkgver=2.0.1
+pkgver=2.0.2
 pkgrel=1
 pkgdesc="A game and app launcher for Linux - native, Windows, and retro"
 arch=('x86_64')
@@ -27,7 +27,7 @@
     'sdl2: gamepad support'
 )
 
source=("${pkgname}-${pkgver}.tar.gz::https://github.com/dekomote/vermouth/archive/refs/tags/v${pkgver}.tar.gz";)
-sha256sums=('6b4e90b986dddc4086f746801aab3e1fbe564af77fcbbaaffd4408a49143cc7d')
+sha256sums=('60269ae19bc12f6004250899580a70afbce4fbc6c310bf3c634b3e29925ffb05')
 
 build() {
     cd "${pkgname}-${pkgver}"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/packaging/vermouth.spec 
new/vermouth-2.0.2/packaging/vermouth.spec
--- old/vermouth-2.0.1/packaging/vermouth.spec  2026-08-05 15:43:55.000000000 
+0200
+++ new/vermouth-2.0.2/packaging/vermouth.spec  2026-08-11 12:07:42.000000000 
+0200
@@ -1,5 +1,5 @@
 Name:           vermouth
-Version:        2.0.1
+Version:        2.0.2
 Release:        1%{?dist}
 Summary:        A no-frills Wine/Proton game launcher for KDE
 License:        MIT
@@ -74,6 +74,8 @@
 %{_datadir}/locale/*/LC_MESSAGES/vermouth.mo
 
 %changelog
+* Tue Aug 11 2026 Dejan Noveski <[email protected]> - 2.0.2-1
+- Play time support, runtime fixes
 * Wed Aug 5 2026 Dejan Noveski <[email protected]> - 2.0.1-1
 - UZDOOM runtime, Default runtime, Sort
 * Wed Jul 15 2026 Dejan Noveski <[email protected]> - 1.9.7-1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/qml/AddAppDialog.qml 
new/vermouth-2.0.2/qml/AddAppDialog.qml
--- old/vermouth-2.0.1/qml/AddAppDialog.qml     2026-08-05 15:43:55.000000000 
+0200
+++ new/vermouth-2.0.2/qml/AddAppDialog.qml     2026-08-11 12:07:42.000000000 
+0200
@@ -3,6 +3,7 @@
 import QtQuick.Layouts
 import QtQuick.Dialogs
 import org.kde.kirigami as Kirigami
+import "TimeUtils.js" as TimeUtils
 
 Kirigami.Dialog {
     id: dialog
@@ -58,6 +59,7 @@
         logoField.text = "";
         steamGridDbIdField.text = "";
         steamIdField.text = "";
+        playTimeField.text = "";
         platformCombo.currentIndex = -1;
         artSection.expanded = false;
         pendingAutoDownload = false;
@@ -152,6 +154,7 @@
         logoField.text = app.logoPath || "";
         steamGridDbIdField.text = app.steamGridDbId > 0 ? 
app.steamGridDbId.toString() : "";
         steamIdField.text = app.steamAppId > 0 ? app.steamAppId.toString() : 
"";
+        playTimeField.text = TimeUtils.formatPlayTime(app.playTime) || 
"0:00:00";
         platformCombo.currentIndex = platformCombo.find(app.platformSlug);
         artSection.expanded = gridField.text !== "" || heroField.text !== "" 
|| logoField.text !== "";
         prefixBasePath = protonScanner.prefixBasePath();
@@ -196,6 +199,10 @@
             validationError = i18n("Please select a platform.");
             return false;
         }
+        if (isNaN(TimeUtils.parsePlayTime(playTimeField.text))) {
+            validationError = i18n("Invalid play time. Use the format 
H:MM:SS.");
+            return false;
+        }
         validationError = "";
         return true;
     }
@@ -234,7 +241,8 @@
             "launchOptions": launchOptionsField.text || "",
             "enableLogging": enableLoggingCheck.checked,
             "logoPath": logoField.text || "",
-            "steamGridDbId": sgdbId
+            "steamGridDbId": sgdbId,
+            "playTime": TimeUtils.parsePlayTime(playTimeField.text)
         };
 
         if (editMode) {
@@ -321,6 +329,14 @@
                 placeholderText: i18n("My Game")
             }
 
+            QQC2.TextField {
+                id: playTimeField
+                Layout.topMargin: 10
+                Layout.fillWidth: true
+                Kirigami.FormData.label: i18n("Play Time (H:MM:SS):")
+                placeholderText: "0:00:00"
+            }
+
             RowLayout {
                 Layout.fillWidth: true
                 visible: runtimePicker.runtimeType === "steam"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/qml/AppGridView.qml 
new/vermouth-2.0.2/qml/AppGridView.qml
--- old/vermouth-2.0.1/qml/AppGridView.qml      2026-08-05 15:43:55.000000000 
+0200
+++ new/vermouth-2.0.2/qml/AppGridView.qml      2026-08-11 12:07:42.000000000 
+0200
@@ -93,6 +93,7 @@
         required property string uzdoomPath
         required property var uzdoomMods
         required property bool hidden
+        required property int playTime
 
         readonly property bool hasPrefix: runtimeType === "proton" || 
runtimeType === "wine"
         readonly property string resolvedRuntimeType: runtimeType === 
"default" ? settingsManager.defaultRuntimeType : runtimeType
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/qml/GameCardFrame.qml 
new/vermouth-2.0.2/qml/GameCardFrame.qml
--- old/vermouth-2.0.1/qml/GameCardFrame.qml    2026-08-05 15:43:55.000000000 
+0200
+++ new/vermouth-2.0.2/qml/GameCardFrame.qml    2026-08-11 12:07:42.000000000 
+0200
@@ -2,6 +2,7 @@
 import QtQuick.Controls as QQC2
 import QtQuick.Layouts
 import org.kde.kirigami as Kirigami
+import "TimeUtils.js" as TimeUtils
 
 Item {
     id: frameRoot
@@ -273,6 +274,39 @@
                     maximumLineCount: 2
                 }
             }
+
+            Rectangle {
+                id: playTimeChip
+                visible: frameRoot.gv && frameRoot.gv.viewType !== "icon" && 
frameRoot.gv.showPlayTime && frameRoot.playTime > 0
+                anchors.bottom: artNameOverlay.visible ? artNameOverlay.top : 
parent.bottom
+                anchors.right: parent.right
+                anchors.bottomMargin: Kirigami.Units.smallSpacing
+                anchors.rightMargin: Kirigami.Units.smallSpacing
+                height: playTimeLabel.implicitHeight + 
Kirigami.Units.smallSpacing * 2
+                width: playTimeRow.implicitWidth + Kirigami.Units.smallSpacing 
* 2
+                radius: Kirigami.Units.cornerRadius
+                color: Qt.rgba(0, 0, 0, 0.65)
+
+                Row {
+                    id: playTimeRow
+                    anchors.centerIn: parent
+                    spacing: Kirigami.Units.smallSpacing
+
+                    Kirigami.Icon {
+                        anchors.verticalCenter: parent.verticalCenter
+                        width: 12 * frameRoot.gv.scaleFactor
+                        height: 12 * frameRoot.gv.scaleFactor
+                        source: "clock-symbolic"
+                        color: "#ffffff"
+                    }
+                    QQC2.Label {
+                        id: playTimeLabel
+                        text: TimeUtils.formatPlayTime(frameRoot.playTime)
+                        color: "#ffffff"
+                        font.pixelSize: 10 * frameRoot.gv.scaleFactor
+                    }
+                }
+            }
         }
 
         Rectangle {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/qml/GameGridView.qml 
new/vermouth-2.0.2/qml/GameGridView.qml
--- old/vermouth-2.0.1/qml/GameGridView.qml     2026-08-05 15:43:55.000000000 
+0200
+++ new/vermouth-2.0.2/qml/GameGridView.qml     2026-08-11 12:07:42.000000000 
+0200
@@ -8,6 +8,7 @@
     property real scaleFactor: 1.0
     property string viewType: "grid"
     property bool showNames: true
+    property bool showPlayTime: true
     property bool active: true
     property bool lightsOut: false
     property bool showHidden: true
@@ -24,6 +25,7 @@
         property alias scaleFactor: gridView.scaleFactor
         property alias viewType: gridView.viewType
         property alias showNames: gridView.showNames
+        property alias showPlayTime: gridView.showPlayTime
         property alias showHidden: gridView.showHidden
         property alias sortField: gridView.sortField
         property alias sortAscending: gridView.sortAscending
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/qml/Main.qml 
new/vermouth-2.0.2/qml/Main.qml
--- old/vermouth-2.0.1/qml/Main.qml     2026-08-05 15:43:55.000000000 +0200
+++ new/vermouth-2.0.2/qml/Main.qml     2026-08-11 12:07:42.000000000 +0200
@@ -277,6 +277,13 @@
                         checked: gridView.showNames
                         onTriggered: gridView.showNames = checked
                     }
+                    QQC2.MenuItem {
+                        text: i18n("Show play time")
+                        icon.name: "clock-symbolic"
+                        checkable: true
+                        checked: gridView.showPlayTime
+                        onTriggered: gridView.showPlayTime = checked
+                    }
                     QQC2.MenuSeparator {}
                     RowLayout {
                         spacing: Kirigami.Units.smallSpacing
@@ -328,6 +335,12 @@
                             checked: gridView.sortField === "date"
                             onTriggered: gridView.sortField = "date"
                         }
+                        QQC2.MenuItem {
+                            text: i18n("Play Time")
+                            checkable: true
+                            checked: gridView.sortField === "playtime"
+                            onTriggered: gridView.sortField = "playtime"
+                        }
                         QQC2.MenuSeparator {}
                         QQC2.MenuItem {
                             text: i18n("A–Z")
@@ -851,6 +864,12 @@
                             checked: gridView.sortField === "date"
                             onTriggered: gridView.sortField = "date"
                         }
+                        QQC2.MenuItem {
+                            text: i18n("Play Time")
+                            checkable: true
+                            checked: gridView.sortField === "playtime"
+                            onTriggered: gridView.sortField = "playtime"
+                        }
                         QQC2.MenuSeparator {}
                         QQC2.MenuItem {
                             text: i18n("A–Z")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/qml/TimeUtils.js 
new/vermouth-2.0.2/qml/TimeUtils.js
--- old/vermouth-2.0.1/qml/TimeUtils.js 1970-01-01 01:00:00.000000000 +0100
+++ new/vermouth-2.0.2/qml/TimeUtils.js 2026-08-11 12:07:42.000000000 +0200
@@ -0,0 +1,40 @@
+.pragma library
+
+// Format a play time given in seconds as "H:MM:SS". Returns an empty string
+// when there is nothing to show (zero or invalid input).
+function formatPlayTime(seconds) {
+    if (!seconds || seconds <= 0)
+        return "";
+    seconds = Math.floor(seconds);
+    var h = Math.floor(seconds / 3600);
+    var m = Math.floor((seconds % 3600) / 60);
+    var s = seconds % 60;
+    function pad(n) {
+        return n < 10 ? "0" + n : "" + n;
+    }
+    return h + ":" + pad(m) + ":" + pad(s);
+}
+
+// Parse a play time in "H:MM:SS", "M:SS" or plain-seconds form into seconds.
+// Returns 0 for an empty string, or NaN for invalid input.
+function parsePlayTime(text) {
+    var t = (text || "").trim();
+    if (t === "")
+        return 0;
+    var parts = t.split(":");
+    if (parts.length > 3)
+        return NaN;
+    var total = 0;
+    for (var i = 0; i < parts.length; i++) {
+        var n = parseInt(parts[i], 10);
+        if (isNaN(n) || n < 0)
+            return NaN;
+        if (i === parts.length - 1)
+            total += n; // seconds
+        else if (i === parts.length - 2)
+            total += n * 60; // minutes
+        else
+            total += n * 3600; // hours
+    }
+    return total;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/src/appentry.cpp 
new/vermouth-2.0.2/src/appentry.cpp
--- old/vermouth-2.0.1/src/appentry.cpp 2026-08-05 15:43:55.000000000 +0200
+++ new/vermouth-2.0.2/src/appentry.cpp 2026-08-11 12:07:42.000000000 +0200
@@ -52,6 +52,7 @@
     obj[QStringLiteral("launchOptions")] = launchOptions;
     obj[QStringLiteral("enableLogging")] = enableLogging;
     obj[QStringLiteral("hidden")] = hidden;
+    obj[QStringLiteral("playTime")] = static_cast<double>(playTime);
     obj[QStringLiteral("dateAdded")] = dateAdded.toString(Qt::ISODate);
     return obj;
 }
@@ -80,6 +81,7 @@
         {QStringLiteral("launchOptions"), launchOptions},
         {QStringLiteral("enableLogging"), enableLogging},
         {QStringLiteral("hidden"), hidden},
+        {QStringLiteral("playTime"), playTime},
         {QStringLiteral("dateAdded"), dateAdded},
     };
 }
@@ -113,6 +115,7 @@
     e.launchOptions = obj[QStringLiteral("launchOptions")].toString();
     e.enableLogging = obj[QStringLiteral("enableLogging")].toBool(false);
     e.hidden = obj[QStringLiteral("hidden")].toBool(false);
+    e.playTime = obj.value(QStringLiteral("playTime")).toInteger(0);
     e.dateAdded = 
QDateTime::fromString(obj[QStringLiteral("dateAdded")].toString(), Qt::ISODate);
     return e;
 }
@@ -142,4 +145,5 @@
     launchOptions = app[QStringLiteral("launchOptions")].toString();
     enableLogging = app.value(QStringLiteral("enableLogging"), false).toBool();
     hidden = app.value(QStringLiteral("hidden"), false).toBool();
+    playTime = app.value(QStringLiteral("playTime"), 0).toLongLong();
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/src/appentry.h 
new/vermouth-2.0.2/src/appentry.h
--- old/vermouth-2.0.1/src/appentry.h   2026-08-05 15:43:55.000000000 +0200
+++ new/vermouth-2.0.2/src/appentry.h   2026-08-11 12:07:42.000000000 +0200
@@ -60,6 +60,7 @@
     QString launchOptions;
     bool enableLogging = false;
     bool hidden = false;
+    qint64 playTime = 0; // seconds
     QDateTime dateAdded;
 
     QJsonObject toJson() const;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/src/appmodel.cpp 
new/vermouth-2.0.2/src/appmodel.cpp
--- old/vermouth-2.0.1/src/appmodel.cpp 2026-08-05 15:43:55.000000000 +0200
+++ new/vermouth-2.0.2/src/appmodel.cpp 2026-08-11 12:07:42.000000000 +0200
@@ -38,6 +38,10 @@
             result = ea.dateAdded > eb.dateAdded ? 1 : (ea.dateAdded < 
eb.dateAdded ? -1 : 0);
             if (result == 0)
                 result = ea.name.compare(eb.name, Qt::CaseInsensitive);
+        } else if (m_sortField == QLatin1String("playtime")) {
+            result = ea.playTime < eb.playTime ? -1 : (ea.playTime > 
eb.playTime ? 1 : 0);
+            if (result == 0)
+                result = ea.name.compare(eb.name, Qt::CaseInsensitive);
         } else {
             result = ea.name.compare(eb.name, Qt::CaseInsensitive);
         }
@@ -145,6 +149,8 @@
         return e.enableLogging;
     case HiddenRole:
         return e.hidden;
+    case PlayTimeRole:
+        return e.playTime;
     }
     return {};
 }
@@ -173,6 +179,7 @@
         {LaunchOptionsRole, "launchOptions"},
         {EnableLoggingRole, "enableLogging"},
         {HiddenRole, "hidden"},
+        {PlayTimeRole, "playTime"},
     };
 }
 
@@ -271,6 +278,28 @@
     }
 }
 
+void AppModel::addPlayTime(const QString &exePath, qint64 seconds)
+{
+    for (int i = 0; i < m_entries.size(); ++i) {
+        if (m_entries[i].exePath != exePath)
+            continue;
+        m_entries[i].playTime += seconds;
+        for (int f = 0; f < m_filtered.size(); ++f) {
+            if (m_filtered[f] == i) {
+                QModelIndex idx = index(f, 0);
+                Q_EMIT dataChanged(idx, idx, {PlayTimeRole});
+                break;
+            }
+        }
+        // Throttle disk writes: flush at most every 10 seconds while a game 
runs.
+        if (++m_saveCounter >= 10) {
+            m_saveCounter = 0;
+            save();
+        }
+        return;
+    }
+}
+
 QVariantMap AppModel::getApp(int index) const
 {
     int src = sourceIndex(index);
@@ -354,8 +383,14 @@
     for (const auto &e : m_entries)
         arr.append(e.toJson());
 
-    QFile f(configPath());
+    // Atomic write: write to a temp file, then rename over the real one.
+    const QString path = configPath();
+    const QString tmpPath = path + QStringLiteral(".tmp");
+    QFile f(tmpPath);
     if (!f.open(QIODevice::WriteOnly))
         return;
     f.write(QJsonDocument(arr).toJson());
+    f.close();
+    QFile::remove(path);
+    QFile::rename(tmpPath, path);
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/src/appmodel.h 
new/vermouth-2.0.2/src/appmodel.h
--- old/vermouth-2.0.1/src/appmodel.h   2026-08-05 15:43:55.000000000 +0200
+++ new/vermouth-2.0.2/src/appmodel.h   2026-08-11 12:07:42.000000000 +0200
@@ -35,6 +35,7 @@
         LaunchOptionsRole,
         EnableLoggingRole,
         HiddenRole,
+        PlayTimeRole,
     };
 
     explicit AppModel(QObject *parent = nullptr);
@@ -60,6 +61,7 @@
     Q_INVOKABLE QString generateUUID() const;
     Q_INVOKABLE void
     updateAppArt(const QString &id, const QString &iconPath, const QString 
&gridPath, const QString &heroPath, const QString &logoPath, int steamGridDbId 
= 0);
+    Q_INVOKABLE void addPlayTime(const QString &exePath, qint64 seconds);
 
     bool showHidden() const
     {
@@ -99,4 +101,5 @@
     bool m_showHidden = false;
     QString m_sortField = QStringLiteral("name");
     bool m_sortAscending = true;
+    int m_saveCounter = 0;
 };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/src/launcher.cpp 
new/vermouth-2.0.2/src/launcher.cpp
--- old/vermouth-2.0.1/src/launcher.cpp 2026-08-05 15:43:55.000000000 +0200
+++ new/vermouth-2.0.2/src/launcher.cpp 2026-08-11 12:07:42.000000000 +0200
@@ -359,6 +359,16 @@
                         const QString &logName,
                         bool appendExe)
 {
+    if (binary.isEmpty()) {
+        Q_EMIT launchError(exePath, QStringLiteral("No runtime is set for this 
game."));
+        return -1;
+    }
+    const bool binaryFound = binary.contains(QLatin1Char('/')) ? 
QFileInfo::exists(binary) : !QStandardPaths::findExecutable(binary).isEmpty();
+    if (!binaryFound) {
+        Q_EMIT launchError(exePath, QStringLiteral("Runtime binary not found: 
%1").arg(binary));
+        return -1;
+    }
+
     auto *proc = new QProcess(this);
     auto *timer = new QElapsedTimer();
     timer->start();
@@ -450,6 +460,10 @@
 
     // "default" runtime is inferred from settings at launch time - never 
copied into the entry.
     if (runtimeType == QStringLiteral("default")) {
+        if (m_defaultRuntimeType.isEmpty()) {
+            Q_EMIT launchError(name, QStringLiteral("No default runtime is set 
in Settings."));
+            return -1;
+        }
         QVariantMap resolved = app;
         resolved[QStringLiteral("runtimeType")] = m_defaultRuntimeType;
         if (resolved[QStringLiteral("protonPath")].toString().isEmpty())
@@ -502,20 +516,30 @@
 
     if (runtimeType == QStringLiteral("proton")) {
         QString protonPath = app[QStringLiteral("protonPath")].toString();
+        if (protonPath.isEmpty()) {
+            Q_EMIT launchError(name, QStringLiteral("Proton is not set. 
Download it or set its path in Settings."));
+            return -1;
+        }
         QString prefix = app[QStringLiteral("protonPrefix")].toString();
         if (!prefix.isEmpty())
             QDir().mkpath(prefix);
 
-        QString umoBin = m_umuPath;
-        if (umoBin.isEmpty())
-            umoBin = QStandardPaths::findExecutable(QStringLiteral("umu-run"));
-
-        if (!umoBin.isEmpty()) {
+        QString umuBin;
+        if (!m_umuPath.isEmpty() && QFileInfo::exists(m_umuPath))
+            umuBin = m_umuPath;
+        if (umuBin.isEmpty())
+            umuBin = QStandardPaths::findExecutable(QStringLiteral("umu-run"));
+
+        if (!umuBin.isEmpty()) {
+            if (!QFileInfo::exists(protonPath + QStringLiteral("/proton"))) {
+                Q_EMIT launchError(name, QStringLiteral("Proton not found at 
%1. Download it or set its path in Settings.").arg(protonPath));
+                return -1;
+            }
             env.insert(QStringLiteral("PROTONPATH"), protonPath);
             env.insert(QStringLiteral("STEAM_COMPAT_DATA_PATH"), prefix);
             env.insert(QStringLiteral("GAMEID"), QStringLiteral("0"));
             env.insert(QStringLiteral("WINEPREFIX"), prefix);
-            return launch(umoBin, {}, exePath, env, opts, logging, name);
+            return launch(umuBin, {}, exePath, env, opts, logging, name);
         } else {
             env.insert(QStringLiteral("STEAM_COMPAT_CLIENT_INSTALL_PATH"), 
QDir::homePath() + QStringLiteral("/.steam/steam"));
             env.insert(QStringLiteral("STEAM_COMPAT_DATA_PATH"), prefix);
@@ -550,12 +574,17 @@
         env.insert(QStringLiteral("APPIMAGE"), exePath);
         return launch(binary, baseArgs, exePath, env, opts, logging, name, 
false);
     } else {
+        QString wineBinary = app[QStringLiteral("wineBinary")].toString();
+        if (wineBinary.isEmpty()) {
+            Q_EMIT launchError(name, QStringLiteral("Wine is not set. Set its 
path in Settings."));
+            return -1;
+        }
         QString prefix = app[QStringLiteral("winePrefix")].toString();
         if (!prefix.isEmpty()) {
             QDir().mkpath(prefix);
             env.insert(QStringLiteral("WINEPREFIX"), prefix);
         }
-        return launch(app[QStringLiteral("wineBinary")].toString(), {}, 
exePath, env, opts, logging, name);
+        return launch(wineBinary, {}, exePath, env, opts, logging, name);
     }
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/src/main.cpp 
new/vermouth-2.0.2/src/main.cpp
--- old/vermouth-2.0.1/src/main.cpp     2026-08-05 15:43:55.000000000 +0200
+++ new/vermouth-2.0.2/src/main.cpp     2026-08-11 12:07:42.000000000 +0200
@@ -35,6 +35,7 @@
 #include <QQmlContext>
 #include <QQuickImageProvider>
 #include <QQuickStyle>
+#include <QTimer>
 
 class IconImageProvider : public QQuickImageProvider
 {
@@ -217,6 +218,33 @@
 
     QObject::connect(&app, &QApplication::aboutToQuit, [&]() {
         launcher.restoreHdrState();
+        appModel.save();
+    });
+
+    QTimer playTimeTimer;
+    playTimeTimer.setInterval(1000);
+    QObject::connect(&playTimeTimer, &QTimer::timeout, [&]() {
+        const auto paths = launcher.runningExePaths();
+        for (const QString &path : paths)
+            appModel.addPlayTime(path, 1);
+    });
+    QTimer playTimeStartDelay;
+    playTimeStartDelay.setSingleShot(true);
+    playTimeStartDelay.setInterval(5000);
+    QObject::connect(&playTimeStartDelay, &QTimer::timeout, [&]() {
+        playTimeTimer.start();
+    });
+    QObject::connect(&launcher, &Launcher::runningExePathsChanged, [&]() {
+        if (launcher.runningExePaths().isEmpty()) {
+            playTimeStartDelay.stop();
+            playTimeTimer.stop();
+            appModel.save();
+        } else if (!playTimeTimer.isActive() && 
!playTimeStartDelay.isActive()) {
+            playTimeStartDelay.start();
+        }
+    });
+    QObject::connect(&launcher, &Launcher::processFinished, [&](int) {
+        appModel.save();
     });
 
     // Restore sleep inhibition state
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vermouth-2.0.1/src/protondownloader.h 
new/vermouth-2.0.2/src/protondownloader.h
--- old/vermouth-2.0.1/src/protondownloader.h   2026-08-05 15:43:55.000000000 
+0200
+++ new/vermouth-2.0.2/src/protondownloader.h   2026-08-11 12:07:42.000000000 
+0200
@@ -19,7 +19,7 @@
     {"ge",
      "https://github.com/GloriousEggroll/proton-ge-custom/releases/latest";,
      
"https://github.com/GloriousEggroll/proton-ge-custom/releases/download/%1/%2";,
-     "%1.tar.gz",
+     "%1-x86_64.tar.gz",
      "-xzf",
      ".tar.gz"},
     {"cachyos",

Reply via email to