Compare commits

..
1 Commits
Author SHA1 Message Date
aclist 1d971765ab Merge c15b77d470 into 5014402e39 2026-09-07 20:07:47 +00:00
24 changed files with 64 additions and 181 deletions

No files matched your search

-1
View File
@@ -52,7 +52,6 @@
- Filter button tooltips
- Expand/collapse changelog versions
- Support local light/dark mode changes on system theme
- Server time calculator
### Changed
- Conform to PEP 440 versioning for beta versions
+1 -1
View File
@@ -346,7 +346,7 @@ def get_details(record: Record) -> Details | None:
dlc = strings.dlc_none
case 2:
dlc = strings.dlc_frostline
case 4:
case 3:
dlc = strings.dlc_badlands
case _:
dlc = strings.unspecified
-23
View File
@@ -6,7 +6,6 @@ import subprocess
import vdf # type: ignore
from pathlib import Path
from requests.exceptions import HTTPError
from typing import Any, Union
from warnings import deprecated
@@ -33,7 +32,6 @@ from dzgui.const.endpoints import (
UNSUB_ENDPOINT,
)
from dzgui.strings import wizard
from dzgui.strings.errors import api_validation_error, workshop_endpoint_error
from dzgui.util.bash import concat_bash_args
logger = logging.getLogger(APP_NAME)
@@ -264,16 +262,6 @@ def find_user_id_32(path: Path) -> int:
return int(uid) & 0xFFFFFFFF
class SteamAPIKeyError(Exception):
def __init__(self, msg: str | None = None):
super().__init__(msg or api_validation_error)
class SteamWorkshopEndpointError(Exception):
def __init__(self, msg: str | None = None):
super().__init__(msg or workshop_endpoint_error)
def update_workshop(key: str, mod: int, endpoint: str) -> None:
payload: dict[str, Union[int, str]] = {
"publishedfileid": mod,
@@ -285,19 +273,8 @@ def update_workshop(key: str, mod: int, endpoint: str) -> None:
try:
res = requests.post(endpoint, params=payload, timeout=REQUEST_TIMEOUT)
res.raise_for_status()
except HTTPError as e:
logger.critical(e)
match res.status_code:
case 403:
raise SteamAPIKeyError()
case 404:
raise SteamWorkshopEndpointError()
case _:
# TODO: apply redaction filter
raise e
except Exception as e:
logger.critical(e)
raise e
def subscribe(key: str, mod: int) -> None:
+1 -1
View File
@@ -36,7 +36,7 @@ HEX_GREEN = "#32CD32"
HEX_RED = "#FF0000"
HEX_ORANGE = "#FFAC1C"
# TODO: normalize icon names
# FIXME: normalize icon names
CALCULATOR = "accessories-calculator-symbolic"
CARET_DOWN = "go-down-symbolic"
CARET_UP = "go-up-symbolic"
+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, cmd: str) -> str:
return self.mediator.options.get_client_name(cmd)
def get_steam_client_name(self) -> str:
return self.mediator.options.get_client_name()
def present_window(self) -> None:
self.mediator.window.present_with_time(Gdk.CURRENT_TIME)
-1
View File
@@ -52,7 +52,6 @@
- Filter button tooltips
- Expand/collapse changelog versions
- Support local light/dark mode changes on system theme
- Server time calculator
### Changed
- Conform to PEP 440 versioning for beta versions
+2 -9
View File
@@ -4,24 +4,17 @@ from pathlib import Path
from dzgui.api.steam import get_app_path
from dzgui.config.query import lookup
from dzgui.const.constants import APPID_DAYZ, APP_NAME, APPNAME_DAYZ
from dzgui.const.constants import APPID_DAYZ, APP_NAME
from dzgui.const.enum import Preferences
from dzgui.strings.preconnect import resync
logger = logging.getLogger(APP_NAME)
class VDFSyncError(Exception):
pass
def is_dayz_installed(config: Path) -> None:
try:
path = lookup(config, Preferences.DEFAULT)
get_app_path(Path(path), APPID_DAYZ)
except Exception as e:
logger.critical(e)
msg = resync.format(APPNAME_DAYZ)
raise VDFSyncError(msg)
raise e
+4 -9
View File
@@ -16,15 +16,12 @@ logger = logging.getLogger(APP_NAME)
def is_steam_running(cmd: str) -> bool:
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 cmd == STEAM_CMD:
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")
@@ -34,9 +31,7 @@ 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", "--columns=application"], text=True
)
proc = subprocess.check_output([FLATPAK_CMD, "ps"], text=True)
lines = proc.splitlines()
if FLATPAK_APPID in lines:
return True
+4 -15
View File
@@ -48,7 +48,7 @@ from dzgui.strings.dialogs import (
)
from dzgui.strings import kb
from dzgui.strings.server_mods import checkmark, resync
from dzgui.util.format import format_exception, format_mib
from dzgui.util.format import format_mib
from dzgui.util.strings import dialog, server_timeout
from dzgui.util.symlink import rebuild_symlinks
from dzgui.views.dialogs.generic import ExceptionDialog
@@ -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
@@ -300,11 +300,6 @@ class ConnectionManager:
dialog = ServerDetailsDialog(self.controller, details)
dialog.run()
def _workshop_timeout(self, msg: str) -> None:
dialog = ExceptionDialog(self.controller, msg)
dialog.show_all()
dialog.run()
def _server_timeout(self) -> None:
dialog = ExceptionDialog(self.controller, server_timeout)
dialog.set_details_buffer(kb.DZG_006)
@@ -356,14 +351,8 @@ class ConnectionManager:
for title, mod, stamp, size in self.missing_mods:
if self.controller.is_cancel_pending():
return
try:
subscribe(key, int(mod))
time.sleep(API_RATE_LIMIT)
except Exception as e:
msg = format_exception(e)
func = StoredFunc(self._workshop_timeout, msg)
self.thread_man.set_cleanup_func(func, destroy_first=True)
return
subscribe(key, int(mod))
time.sleep(API_RATE_LIMIT)
self.thread_man.update_dialog(waiting_for_directories)
for title, mod, stamp, size in self.missing_mods:
+1 -5
View File
@@ -122,11 +122,7 @@ class ModManager:
def unsub_atomic_mod(self, mod: str) -> None:
config_man = self.controller.get_config_man()
key = config_man.lookup(Preferences.STEAM)
try:
unsubscribe(key, int(mod))
except Exception as e:
logger.critical(e)
return
unsubscribe(key, int(mod))
steam_path = Path(self.path)
app_path = PeFile.get_nested_app_path(steam_path, APPID_DAYZ)
+15 -17
View File
@@ -1,22 +1,20 @@
from dzgui.const.constants import UDP_PORT
add_button = "Add"
add_con_heading = "Add/connect"
add_button="Add"
add_con_heading="Add/connect"
add_popover = "This address is already in your Saved Servers"
add_tooltip = "Add to Saved Servers"
connect_button = "Connect"
connect_tooltip = "Connect to this server"
connect_entry_placeholder = f"Enter IP:Query port (e.g., 192.168.1.1:{UDP_PORT})"
add_tooltip="Add to Saved Servers"
connect_button="Connect"
connect_tooltip="Connect to this server"
connect_entry_placeholder="Enter IP:Query port (e.g., 192.168.1.1:27016)"
fav_heading = "Favorite server"
favs_empty = "None set. Right click a server and select 'Set favorite' to set."
fav_heading="Favorite server"
favs_empty="None set. Right click a server and select 'Set favorite' to set."
lan_heading = "LAN query port"
lan_default_button = f"Default port ({UDP_PORT})"
lan_custom_button = "Custom port"
lan_scan_button = "Scan"
lan_heading="LAN query port"
lan_default_button="Default port (27016)"
lan_custom_button="Custom port"
lan_scan_button="Scan"
lan_abort_tooltip = "Unless you have multiple DayZ servers on your LAN,\nleave this checked to get results faster"
lan_checkbox = "Stop scanning on first hit"
lan_entry_placeholder = "Enter the query port (1-65535)"
lan_entry_tooltip = "Specify the port to search for DayZ servers on the local network"
lan_scan_tooltip = "Scan for servers"
lan_entry_placeholder="Enter the query port (1-65535)"
lan_entry_tooltip="Specify the port to search for DayZ servers on the local network"
lan_scan_tooltip="Scan for servers"
-1
View File
@@ -4,4 +4,3 @@ api_validation_error = (
api_popover = "API key validation failed."
dayz_not_found = "Could not find a valid DayZ installation."
workshop_endpoint_error = "Steam Workshop service unavailable, try again later."
+2 -1
View File
@@ -11,6 +11,8 @@ mods = "Mods"
total_mods = "Total mods: "
up_to_date = "All mods are up to date."
all_updated = "All mods updated."
"If you recently installed {build} or moved it to a different drive, "
"restart Steam to allow these changes to synchronize, then try again."
required_space = " Need to download {0} MiB of mod updates."
placeholder_no_mods = "This server has no mods."
@@ -34,7 +36,6 @@ steam_not_running = "'{0}' is set as the default Steam client, but it is either
use_desktop_mode = "Use Desktop Mode to download mods on Steam Deck."
version_mismatch = "Local client version '{0}' does not match remote version '{1}'."
version_missing = "Remote server is running the build '{0}', but it is not installed."
no_errors = "No errors."
# Warnings
+1 -2
View File
@@ -30,8 +30,7 @@ path_remove_prefix = "The following path will be removed:"
not_installed = "It looks like DZGUI was not previously set up; nothing to uninstall."
standalone_uninstall = (
"It looks like you are running the standalone version of DZGUI.\n"
"To finalize the uninstall process, invoke './dzgui self remove',\n"
"then remove the 'dzgui' file."
"To finalize the uninstall process, you can remove the 'dzgui' file."
)
system_uninstall = (
"It looks like DZGUI was installed via your system package manager or directly from source code.\n"
+5 -10
View File
@@ -2,12 +2,7 @@ import logging
from pathlib import Path
from dzgui.api.mods import get_local_mod_path, get_local_mod_ids, _hash
from dzgui.const.constants import (
APPID_DAYZ,
APPID_DAYZ_EXP,
APP_NAME,
DAYZ_COMMUNITY_ROOT,
)
from dzgui.const.constants import APPID_DAYZ, APPID_DAYZ_EXP, APP_NAME, DAYZ_COMMUNITY_ROOT
from dzgui.const.enum import Preferences
from dzgui.config.query import lookup
@@ -25,7 +20,6 @@ def expunge_link(file: Path) -> None:
if str(file.stem)[:2] == "@C":
file.unlink()
def rebuild_symlinks(config: Path) -> None:
# TODO: pass direct path as argument
path = lookup(config, Preferences.DEFAULT)
@@ -69,9 +63,10 @@ def symlink_mission(steam_path: Path, target: str) -> str:
stem = path.name
parent = path.parent
source = dayz_path.joinpath(DAYZ_COMMUNITY_ROOT)
if source.exists():
source.unlink()
source.symlink_to(parent)
try:
source.symlink_to(parent)
except Exception:
return ""
return f"{DAYZ_COMMUNITY_ROOT}/{stem}"
+8 -1
View File
@@ -1,5 +1,6 @@
import logging
import signal
import warnings
from typing import TYPE_CHECKING, Literal, Self
@@ -46,6 +47,10 @@ 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:
@@ -339,6 +344,8 @@ try:
theme = Gtk.IconTheme.get_default()
icons = theme.list_icons(None)
if STEAM_ICON not in icons:
logger.warning(strings.steam_icon_missing)
logger.warn(strings.steam_icon_missing)
warnings.warn(strings.steam_icon_missing, stacklevel=2)
except Exception as e:
logger.warning(e)
warnings.warn(strings.gtk_theme_missing, stacklevel=2)
+2 -9
View File
@@ -15,7 +15,7 @@ class ClientCombo(Gtk.ComboBox):
super().__init__()
self.client_store = Gtk.ListStore(str, str)
self.clients = (
clients = (
(
options.steam_combo,
STEAM_CMD,
@@ -29,7 +29,7 @@ class ClientCombo(Gtk.ComboBox):
FLATPAK_SANDBOX,
),
)
for client in self.clients:
for client in clients:
self.client_store.append(client)
self.set_model(self.client_store)
renderer_text = Gtk.CellRendererText()
@@ -37,13 +37,6 @@ 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)
+1 -1
View File
@@ -102,7 +102,7 @@ class BootDialog(ColorAwareApp, Gtk.Dialog): # type: ignore
margin_top=30,
margin_bottom=30,
)
self.error_label = Gtk.Label(margin_start=15, margin_end=15)
self.error_label = Gtk.Label()
# TODO: abstract class
self.copy_button = ClipboardButton(None, lambda: self.error_label.get_text())
+10 -4
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("changed", self._on_text_typed)
self.entry.connect("insert-text", 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,9 +113,13 @@ class SubmitField(Gtk.Box):
def _on_text_typed(
self,
entry: Gtk.Entry,
text: str,
length: int,
pos: int,
) -> None:
text = entry.get_text()
buffer = entry.get_property("buffer")
text = buffer.get_text() + text
state = self._is_valid_text(text)
self.button.set_sensitive(state)
@@ -367,8 +371,10 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
self.connect("key-press-event", self._on_keypress)
def get_client_name(self, cmd: str) -> str:
return self.client_combo.get_client_human_readable(cmd)
def get_client_name(self) -> str:
model = self.client_combo.get_model()
ind = self.client_combo.get_active()
return str(model[ind][0])
def block_text_entry(self) -> None:
self.steam_box.block_text_entry()
+2 -1
View File
@@ -176,7 +176,8 @@ class PreConnectionAssistant(Gtk.Box):
self.error_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.error_tree = MaskedTree(ERROR)
self.error_placeholder = Placeholder(preconnect.no_errors)
# TODO: strings
self.error_placeholder = Placeholder("No errors.")
self.error_box.add(self.error_tree)
self.error_box.add(self.error_placeholder)
self.error_frame = HeadingFrame.new_with_widget_and_label(
+1
View File
@@ -252,6 +252,7 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore
case Gdk.KEY_c:
self.controller.menu_action(ContextMenu.COPY_SERVER_IP, self)
case Gdk.KEY_r:
# TODO: unimplemented, needs threading
self.controller.menu_action(ContextMenu.REFRESH_PLAYERS, self)
else:
match event.keyval:
+1 -1
View File
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
authors = [
{name = "aclist"}
]
version = "7.0.0b27"
version = "7.0.0b23"
license = "GPL-3.0-or-later"
license-files = ["LICENSE"]
readme = "README.md"
-61
View File
@@ -1,61 +0,0 @@
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")
+1 -5
View File
@@ -14,11 +14,7 @@ from dzgui.views.dialogs.calc import (
class DummyEmitter:
def __init__(self) -> None:
pass
def connect(self, _, __) -> None:
pass
pass
@pytest.fixture(params=[("05:00", 10.0), ("15:30", 5.0)])