From f748b09ba776695536e017a17707a062b97b1124 Mon Sep 17 00:00:00 2001 From: Sylvain Girard Date: Thu, 8 Oct 2026 15:58:51 +0200 Subject: [PATCH 1/5] fix: detect running Flatpak Steam correctly Request only application IDs from flatpak ps so running Steam instances match correctly. Fix client selection validation and cover detection with regression tests. Refs aclist/dztui#478. --- dzgui/init/proc.py | 6 ++-- tests/test_process_detection.py | 57 +++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tests/test_process_detection.py diff --git a/dzgui/init/proc.py b/dzgui/init/proc.py index faca556..ca9a243 100644 --- a/dzgui/init/proc.py +++ b/dzgui/init/proc.py @@ -20,7 +20,7 @@ def is_steam_running(cmd: str) -> bool: if has_cmd(STEAM_CMD) is False: return False return is_running(STEAM_CMD) - elif cmd == FLATPAK_RUN_CMD or FLATPAK_SANDBOX: + elif cmd in (FLATPAK_RUN_CMD, FLATPAK_SANDBOX): return is_flatpak_steam_running() else: raise TypeError("Not a valid Steam client selection") @@ -31,7 +31,9 @@ def is_steam_running(cmd: str) -> bool: def is_flatpak_steam_running() -> bool: if has_cmd(FLATPAK_CMD) is False: return False - proc = subprocess.check_output([FLATPAK_CMD, "ps"], text=True) + proc = subprocess.check_output( + [FLATPAK_CMD, "ps", "--columns=application"], text=True + ) lines = proc.splitlines() if FLATPAK_APPID in lines: return True diff --git a/tests/test_process_detection.py b/tests/test_process_detection.py new file mode 100644 index 0000000..4643458 --- /dev/null +++ b/tests/test_process_detection.py @@ -0,0 +1,57 @@ +import unittest +from unittest.mock import patch + +from dzgui.const.constants import ( + FLATPAK_APPID, + FLATPAK_RUN_CMD, + FLATPAK_SANDBOX, + STEAM_CMD, +) +from dzgui.init import proc + + +class TestSteamDetection(unittest.TestCase): + def test_flatpak_detection_with_multiple_running_apps(self): + def flatpak_output(args, *, text): + # Default `flatpak ps` includes instance, PID, app and runtime columns. + if args == ["flatpak", "ps", "--columns=application"]: + return f"com.spotify.Client\n{FLATPAK_APPID}\n{FLATPAK_APPID}\n" + return f"3904921611\t1257689\t{FLATPAK_APPID}\torg.freedesktop.Platform\n" + + with ( + patch.object(proc, "has_cmd", return_value=True), + patch.object(proc.subprocess, "check_output", side_effect=flatpak_output), + ): + for client in (FLATPAK_RUN_CMD, FLATPAK_SANDBOX): + with self.subTest(client=client): + self.assertTrue(proc.is_steam_running(client)) + + def test_flatpak_not_running(self): + with patch.object(proc, "has_cmd", return_value=True): + for output in ("", "com.spotify.Client\n", f"{FLATPAK_APPID}Helper\n"): + with ( + self.subTest(output=output), + patch.object(proc.subprocess, "check_output", return_value=output), + ): + self.assertFalse(proc.is_flatpak_steam_running()) + + def test_flatpak_not_installed(self): + with ( + patch.object(proc, "has_cmd", return_value=False), + patch.object(proc.subprocess, "check_output") as check_output, + ): + self.assertFalse(proc.is_flatpak_steam_running()) + check_output.assert_not_called() + + def test_native_steam(self): + with patch.object(proc, "has_cmd", return_value=True): + for running in (True, False): + with ( + self.subTest(running=running), + patch.object(proc, "is_running", return_value=running), + ): + self.assertEqual(proc.is_steam_running(STEAM_CMD), running) + + def test_invalid_client(self): + with self.assertRaises(TypeError): + proc.is_steam_running("invalid-client") From 7fc1264b6507e96e98d3791bda6140b9675e8a3d Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 9 Oct 2026 03:27:07 +0900 Subject: [PATCH 2/5] fix: false positive when Flatpak client is running The process ultimately spawned from the Flatpak run command has the same process name as the one expected for STEAM_CMD (essentially just the Steam client wrapped in a Flatpak), so the old logic would return True for is_steam_running() if the Flatpak version had already been launched. To prevent unintended collisions and isolate launch parameters, STEAM_CMD and FLATPAK_RUN_CMD/FLATPAK_SANDBOX should be mutually exclusive. --- dzgui/init/proc.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dzgui/init/proc.py b/dzgui/init/proc.py index ca9a243..9be760f 100644 --- a/dzgui/init/proc.py +++ b/dzgui/init/proc.py @@ -16,12 +16,15 @@ logger = logging.getLogger(APP_NAME) def is_steam_running(cmd: str) -> bool: - if cmd == STEAM_CMD: + is_flatpak_running = is_flatpak_steam_running() + if cmd in (FLATPAK_RUN_CMD, FLATPAK_SANDBOX): + return is_flatpak_running + elif cmd == STEAM_CMD: + if is_flatpak_running: + return False if has_cmd(STEAM_CMD) is False: return False return is_running(STEAM_CMD) - elif cmd in (FLATPAK_RUN_CMD, FLATPAK_SANDBOX): - return is_flatpak_steam_running() else: raise TypeError("Not a valid Steam client selection") From 8c44a6e2552fc76ac54ce4e22a2fd66e21e5c25e Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 9 Oct 2026 03:33:27 +0900 Subject: [PATCH 3/5] fix: user-facing Steam client name not available until Options populated The old method was checking the active ComboBox string after the Options page was initialized, but before it was actually populated with the real setting. This caused it to always return the string 'Steam' instead of the actual client name. --- dzgui/controllers/mc.py | 4 ++-- dzgui/managers/connection.py | 2 +- dzgui/views/components/misc.py | 11 +++++++++-- dzgui/views/pages/options.py | 5 ++--- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index 200cc1b..59d2dd2 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -541,8 +541,8 @@ class Controller(GObject.GObject): def update_status(self) -> None: self.mediator.preconnect.mark_finished() - def get_steam_client_name(self) -> str: - return self.mediator.options.get_client_name() + def get_steam_client_name(self, cmd: str) -> str: + return self.mediator.options.get_client_name(cmd) def present_window(self) -> None: self.mediator.window.present_with_time(Gdk.CURRENT_TIME) diff --git a/dzgui/managers/connection.py b/dzgui/managers/connection.py index 2fbd91a..ffce732 100644 --- a/dzgui/managers/connection.py +++ b/dzgui/managers/connection.py @@ -184,8 +184,8 @@ class ConnectionManager: else: allows_downloads = (True, "") - client_name = self.controller.get_steam_client_name() client = self.controller.query_config(Preferences.CLIENT) + client_name = self.controller.get_steam_client_name(client) running = is_steam_running(client) steam_proc = SteamProcess(client_name, running) self.client = client diff --git a/dzgui/views/components/misc.py b/dzgui/views/components/misc.py index fa8e29c..9186246 100644 --- a/dzgui/views/components/misc.py +++ b/dzgui/views/components/misc.py @@ -15,7 +15,7 @@ class ClientCombo(Gtk.ComboBox): super().__init__() self.client_store = Gtk.ListStore(str, str) - clients = ( + self.clients = ( ( options.steam_combo, STEAM_CMD, @@ -29,7 +29,7 @@ class ClientCombo(Gtk.ComboBox): FLATPAK_SANDBOX, ), ) - for client in clients: + for client in self.clients: self.client_store.append(client) self.set_model(self.client_store) renderer_text = Gtk.CellRendererText() @@ -37,6 +37,13 @@ class ClientCombo(Gtk.ComboBox): self.add_attribute(renderer_text, "text", 0) self.set_active(0) + def get_client_human_readable(self, cmd: str) -> str: + for client in self.clients: + human, command = client + if command == cmd: + return human + raise TypeError("Not a valid Steam client selection") + class ErrorPopover(Gtk.Popover): def __init__(self, relative_to: Gtk.Widget, position: Gtk.PositionType) -> None: super().__init__(relative_to=relative_to, position=position) diff --git a/dzgui/views/pages/options.py b/dzgui/views/pages/options.py index 93341ec..4d47d8b 100644 --- a/dzgui/views/pages/options.py +++ b/dzgui/views/pages/options.py @@ -371,10 +371,9 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore self.connect("key-press-event", self._on_keypress) - def get_client_name(self) -> str: + def get_client_name(self, cmd: str) -> str: model = self.client_combo.get_model() - ind = self.client_combo.get_active() - return str(model[ind][0]) + return self.client_combo.get_client_human_readable(cmd) def block_text_entry(self) -> None: self.steam_box.block_text_entry() From e6e0a3036d5d2e21fe29436f37b6cb8bedbcaa3b Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 9 Oct 2026 03:39:13 +0900 Subject: [PATCH 4/5] fix: drop unused assignment --- dzgui/views/pages/options.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dzgui/views/pages/options.py b/dzgui/views/pages/options.py index 4d47d8b..8eed52a 100644 --- a/dzgui/views/pages/options.py +++ b/dzgui/views/pages/options.py @@ -372,7 +372,6 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore self.connect("key-press-event", self._on_keypress) def get_client_name(self, cmd: str) -> str: - model = self.client_combo.get_model() return self.client_combo.get_client_human_readable(cmd) def block_text_entry(self) -> None: From b3b05918b7603c18002b487b5b64bafdc92dfec1 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 9 Oct 2026 06:45:59 +0900 Subject: [PATCH 5/5] chore: add test case --- tests/test_process_detection.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_process_detection.py b/tests/test_process_detection.py index 4643458..2e36063 100644 --- a/tests/test_process_detection.py +++ b/tests/test_process_detection.py @@ -52,6 +52,10 @@ class TestSteamDetection(unittest.TestCase): ): self.assertEqual(proc.is_steam_running(STEAM_CMD), running) + def test_native_steam_while_flatpak_is_running(self): + with patch.object(proc, "is_flatpak_steam_running", return_value=True): + self.assertEqual(proc.is_steam_running(STEAM_CMD), False) + def test_invalid_client(self): with self.assertRaises(TypeError): proc.is_steam_running("invalid-client")