Compare commits

..
Author SHA1 Message Date
aclist 4eec00e8e7 chore: trigger checks 2026-10-15 00:36:09 +09:00
aclist 09ef3e446a change: set flatpak option sensitivity dynamically 2026-10-15 00:35:02 +09:00
aclist ebffbe39f2 fix: use more stable GObject signal 2026-10-15 00:23:11 +09:00
aclist 191776ce74 fix: use correct logger method 2026-10-14 23:11:29 +09:00
aclist 7ae34747e9 fix: add missing dummy method to test 2026-10-14 23:07:22 +09:00
aclist 1555ba2d64 chore: drop no longer relevant filter 2026-10-14 23:07:00 +09:00
aclist 4bac804fd7 fix: duplicate warnings (#480) 2026-10-14 23:06:41 +09:00
aclist 748e1870da Merge pull request #479 from sylvaingirardbe/fix/478-flatpak-steam-detection
Mirror to Codeberg / mirror-to-codeberg (push) Canceled after 0s
fix: detect running Flatpak Steam correctly
2026-10-14 23:03:43 +09:00
aclist cd4c6fdfb2 Merge pull request #481 from aclist/fix/workshop-403
fix: handle HTTPErrors from workshop (#477)
2026-10-14 23:01:48 +09:00
aclist b3b05918b7 chore: add test case 2026-10-09 06:45:59 +09:00
aclist e6e0a3036d fix: drop unused assignment 2026-10-09 04:03:36 +09:00
aclist 8c44a6e255 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.
2026-10-09 04:03:17 +09:00
aclist 7fc1264b65 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.
2026-10-09 04:02:58 +09:00
Sylvain Girard f748b09ba7 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.
2026-10-08 15:58:51 +02:00
12 changed files with 141 additions and 43 deletions

No files matched your search

+4 -4
View File
@@ -11,7 +11,7 @@ from typing import Any, Union
from warnings import deprecated
from dzgui.api.acf import ACF
from dzgui.init.prereqs import has_steam_client
from dzgui.init.prereqs import has_any_client
from dzgui.const.constants import (
APPID_DAYZ,
APPID_DAYZ_EXP,
@@ -59,7 +59,7 @@ class VDFLoadError(Exception):
def get_steam_paths() -> list[tuple[Path, str]]:
paths = []
if has_steam_client():
if has_any_client():
HOME = Path.home()
env = os.environ.get("XDG_DATA_HOME")
if env is not None:
@@ -265,12 +265,12 @@ def find_user_id_32(path: Path) -> int:
class SteamAPIKeyError(Exception):
def __init__(self, msg=None):
def __init__(self, msg: str | None = None) -> None:
super().__init__(msg or api_validation_error)
class SteamWorkshopEndpointError(Exception):
def __init__(self, msg=None):
def __init__(self, msg: str | None = None) -> None:
super().__init__(msg or workshop_endpoint_error)
+2 -2
View File
@@ -15,7 +15,7 @@ from dzgui.init.migrate import (
migrate_cols_file,
)
from dzgui.init.flock import lock_acquire
from dzgui.init.prereqs import has_steam_client
from dzgui.init.prereqs import has_any_client
from dzgui.strings import boot
from dzgui.util.deck import is_steam_deck, is_game_mode
@@ -136,7 +136,7 @@ def load_gui(version: str, is_debug: bool) -> None:
# TODO: sudo escalation dialog
# count = get_map_count()
# TODO: move into module
if has_steam_client() is False:
if has_any_client() is False:
EarlyAlertDialog(init.requires_steam)
bootwin = BootWindow(XDG, version)
+2 -2
View File
@@ -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)
+20 -7
View File
@@ -7,19 +7,32 @@ from dzgui.const.constants import APP_NAME, FLATPAK_APPID, FLATPAK_CMD, STEAM_CM
logger = logging.getLogger(APP_NAME)
def has_steam_client() -> bool:
if shutil.which(STEAM_CMD):
return True
if not shutil.which(FLATPAK_CMD):
return False
def has_flatpak_client() -> bool:
try:
res = subprocess.run(
[FLATPAK_CMD, "list"], capture_output=True, text=True, check=True
[FLATPAK_CMD, "list", "--columns=application"],
capture_output=True,
text=True,
check=True,
)
if FLATPAK_APPID in res.stdout:
return True
else:
return False
except subprocess.CalledProcessError as e:
except Exception as e:
logger.critical(e)
return False
def has_steam_client() -> bool:
if shutil.which(STEAM_CMD):
return True
return False
def has_any_client() -> bool:
if shutil.which(STEAM_CMD):
return True
if not shutil.which(FLATPAK_CMD):
return False
return has_flatpak_client()
+9 -4
View File
@@ -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 == FLATPAK_RUN_CMD or FLATPAK_SANDBOX:
return is_flatpak_steam_running()
else:
raise TypeError("Not a valid Steam client selection")
@@ -31,7 +34,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
+1 -1
View File
@@ -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
+1 -8
View File
@@ -1,6 +1,5 @@
import logging
import signal
import warnings
from typing import TYPE_CHECKING, Literal, Self
@@ -47,10 +46,6 @@ if TYPE_CHECKING:
logger = logging.getLogger(APP_NAME)
# TODO: drop
# https://bugzilla.gnome.org/show_bug.cgi?id=708676
warnings.filterwarnings("ignore", ".*g_value_get_int", Warning)
class OuterWindow(Gtk.ApplicationWindow):
def __init__(self, app: Gtk.Application) -> None:
@@ -344,8 +339,6 @@ try:
theme = Gtk.IconTheme.get_default()
icons = theme.list_icons(None)
if STEAM_ICON not in icons:
logger.warn(strings.steam_icon_missing)
warnings.warn(strings.steam_icon_missing, stacklevel=2)
logger.warning(strings.steam_icon_missing)
except Exception as e:
logger.warning(e)
warnings.warn(strings.gtk_theme_missing, stacklevel=2)
+31 -3
View File
@@ -3,6 +3,8 @@ from dzgui.strings import options
from dzgui.views.components.box import HBox
from dzgui.views.components.buttons import Icon
from typing import Self
from dzgui.init.prereqs import has_steam_client, has_flatpak_client
import gi
@@ -14,29 +16,55 @@ class ClientCombo(Gtk.ComboBox):
def __init__(self) -> None:
super().__init__()
self.client_store = Gtk.ListStore(str, str)
clients = (
has_steam = has_steam_client()
has_flatpak = has_flatpak_client()
self.client_store = Gtk.ListStore(str, str, bool)
self.clients = (
(
options.steam_combo,
STEAM_CMD,
has_steam,
),
(
options.flatpak_combo,
FLATPAK_RUN_CMD,
has_flatpak,
),
(
options.flatpak_container_combo,
FLATPAK_SANDBOX,
has_flatpak,
),
)
for client in clients:
for client in self.clients:
self.client_store.append(client)
self.set_model(self.client_store)
renderer_text = Gtk.CellRendererText()
self.pack_start(renderer_text, True)
self.add_attribute(renderer_text, "text", 0)
self.set_active(0)
self.set_cell_data_func(self.get_cells()[0], self.data_func)
def data_func(
self,
combo: Self,
cell: Gtk.CellRendererText,
model: Gtk.ListStore,
_iter: Gtk.TreeIter,
) -> None:
sens = model[_iter][2]
cell.set_property("sensitive", sens)
def get_client_human_readable(self, cmd: str) -> str:
for client in self.clients:
human, command, sens = 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)
+4 -10
View File
@@ -76,7 +76,7 @@ class SubmitField(Gtk.Box):
self.button = Gtk.Button(label=options.save_button)
self.button.connect("clicked", self._on_save_clicked)
self.entry.connect("insert-text", self._on_text_typed)
self.entry.connect("changed", self._on_text_typed)
self.entry.connect("activate", self._on_field_activated)
self.entry.get_property("buffer").connect("deleted-text", self._on_text_deleted)
@@ -113,13 +113,9 @@ class SubmitField(Gtk.Box):
def _on_text_typed(
self,
entry: Gtk.Entry,
text: str,
length: int,
pos: int,
) -> None:
buffer = entry.get_property("buffer")
text = buffer.get_text() + text
text = entry.get_text()
state = self._is_valid_text(text)
self.button.set_sensitive(state)
@@ -371,10 +367,8 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
self.connect("key-press-event", self._on_keypress)
def get_client_name(self) -> str:
model = self.client_combo.get_model()
ind = self.client_combo.get_active()
return str(model[ind][0])
def get_client_name(self, cmd: str) -> str:
return self.client_combo.get_client_human_readable(cmd)
def block_text_entry(self) -> None:
self.steam_box.block_text_entry()
+1 -1
View File
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
authors = [
{name = "aclist"}
]
version = "7.0.0b25"
version = "7.0.0b26"
license = "GPL-3.0-or-later"
license-files = ["LICENSE"]
readme = "README.md"
+61
View File
@@ -0,0 +1,61 @@
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_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")
+5 -1
View File
@@ -14,7 +14,11 @@ from dzgui.views.dialogs.calc import (
class DummyEmitter:
pass
def __init__(self) -> None:
pass
def connect(self, _, __) -> None:
pass
@pytest.fixture(params=[("05:00", 10.0), ("15:30", 5.0)])