From 881847cee9c49b86bb4bfb69ff70dac847a9b13c Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 02:40:13 +0900 Subject: [PATCH 01/18] feat: disallow updates if binary path is not writeable --- dzgui/views/components/right_panel.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/dzgui/views/components/right_panel.py b/dzgui/views/components/right_panel.py index d8d1d21..9bc6e72 100644 --- a/dzgui/views/components/right_panel.py +++ b/dzgui/views/components/right_panel.py @@ -1,6 +1,7 @@ import logging import os +from pathlib import Path from typing import Literal, TYPE_CHECKING from dzgui.const.constants import ( @@ -13,6 +14,7 @@ from dzgui.const.constants import ( from dzgui.const.enum import ServerTab from dzgui.managers.update import UpdateManager from dzgui.util.clip import copy_clipboard +from dzgui.util.file import is_writeable from dzgui.views.components.buttonbox import ButtonBox from dzgui.views.components.filter_panel import FilterPanel from dzgui.views.components.mod_panel import ModSelectionPanel @@ -87,11 +89,13 @@ class RightPanel(Gtk.Box): ) exe_path = os.getenv("PYAPP") if exe_path and update is not None: - self.update_button.set_halign(Gtk.Align.END) - self.gutter_box.add(self.update_button) - self.update_button.connect( - "clicked", self._on_update_button_clicked, exe_path, update - ) + if is_writeable(Path(exe_path)) is True: + print("path is writeable") + self.update_button.set_halign(Gtk.Align.END) + self.gutter_box.add(self.update_button) + self.update_button.connect( + "clicked", self._on_update_button_clicked, exe_path, update + ) self.gutter_box.add(eb) self.pack_start(self.gutter_box, NO_EXPAND, FILL, NO_PADDING) @@ -108,7 +112,7 @@ class RightPanel(Gtk.Box): return self.filters_vbox.set_sensitive(False) # TODO: unless it is lan page - #self.refresh_button.set_sensitive(False) + # self.refresh_button.set_sensitive(False) def _on_lan_page_init(self, emitter: "Emitter") -> None: self.filters_vbox.set_sensitive(False) From 24d3bc820937c69dd7c8b6534245b7294441a7ad Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 02:41:26 +0900 Subject: [PATCH 02/18] chore: drop comment --- dzgui/data/CHANGELOG.md | 1 + dzgui/views/components/right_panel.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/dzgui/data/CHANGELOG.md b/dzgui/data/CHANGELOG.md index 223e197..4a1b254 100644 --- a/dzgui/data/CHANGELOG.md +++ b/dzgui/data/CHANGELOG.md @@ -51,6 +51,7 @@ - Suppress log messages from imported modules - Embed Workshop link in Options menu - Disable overlay scrollbars on server tables +- Reduce size of geolocation DB on disk (~100MB) ## Dropped - Debug mode diff --git a/dzgui/views/components/right_panel.py b/dzgui/views/components/right_panel.py index 9bc6e72..2e93488 100644 --- a/dzgui/views/components/right_panel.py +++ b/dzgui/views/components/right_panel.py @@ -90,7 +90,6 @@ class RightPanel(Gtk.Box): exe_path = os.getenv("PYAPP") if exe_path and update is not None: if is_writeable(Path(exe_path)) is True: - print("path is writeable") self.update_button.set_halign(Gtk.Align.END) self.gutter_box.add(self.update_button) self.update_button.connect( From 15d928026a1547abfe383cbfeaa8afa3544a9d92 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 02:41:41 +0900 Subject: [PATCH 03/18] feat: add file module --- dzgui/util/file.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 dzgui/util/file.py diff --git a/dzgui/util/file.py b/dzgui/util/file.py new file mode 100644 index 0000000..b655336 --- /dev/null +++ b/dzgui/util/file.py @@ -0,0 +1,15 @@ +from datetime import datetime +from pathlib import Path + + +def is_writeable(path: "Path") -> bool: + now = int(datetime.now().timestamp()) + suffix = ".dzgtmp" + file = str(now) + suffix + filepath = Path(str(path) + "_" + file) + try: + filepath.touch() + filepath.unlink() + return True + except OSError: + return False From fd9eb337148452beda9b340864cc5f074c09ac7f Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 03:34:11 +0900 Subject: [PATCH 04/18] feat: load direct to menu --- dzgui/api/servers.py | 6 ++++++ dzgui/api/steam.py | 17 +++++++++++++++++ dzgui/controllers/mc.py | 3 +++ dzgui/managers/connection.py | 33 ++++++++++++++++++++++++++------- dzgui/strings/preconnect.py | 1 + dzgui/views/pages/preconnect.py | 14 +++++++++++++- 6 files changed, 66 insertions(+), 8 deletions(-) diff --git a/dzgui/api/servers.py b/dzgui/api/servers.py index c73ff67..fce937b 100644 --- a/dzgui/api/servers.py +++ b/dzgui/api/servers.py @@ -61,6 +61,8 @@ class Details: @dataclass(slots=True) class Record: + # TODO: investigate whether this enum is still being used; + # can this be frozen? """ The gameport field is manipulated by the RowType.CONN_BY_IP method """ @@ -509,6 +511,10 @@ def fqip_to_record(addr: str) -> Optional[Record]: return Record(r[0], int(r[1]), int(r[2])) +def record_to_fqip(record: Record) -> str: + return f"{record.ip}:{record.gameport}:{record.qport}" + + def response_to_fqip(res: dict) -> str: ip = res["addr"].split(":")[0] gameport = res["gameport"] diff --git a/dzgui/api/steam.py b/dzgui/api/steam.py index 5bb1736..fc1f6fd 100644 --- a/dzgui/api/steam.py +++ b/dzgui/api/steam.py @@ -146,6 +146,23 @@ def connect(client: str, addr: str, appid: int, name: str, mods: list[str]) -> i return proc.returncode +def load_to_menu(client: str, addr: str, appid: int, name: str, mods: list[str]) -> int: + """Loads to the menu screen with the selected mods; used for server restarts""" + concat = concat_mods(mods) + client_args = concat_bash_args(client) + params = [ + "-applaunch", + str(appid), + "-nolauncher", + "-nosplash", + "-skipintro", + f"-name={name}", + f"-mod={concat}", + ] + proc = subprocess.run([*client_args, *params]) + return proc.returncode + + def find_user_id(path: Path) -> str | None: resolved_path = path / "config" / "loginusers.vdf" try: diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index 0628502..ae24e66 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -508,6 +508,9 @@ class Controller(GObject.GObject): ind = self.config_man.get_start_tab() self.get_servers().notebook.set_current_page(ind) + def update_and_load_to_menu(self, raise_window: bool) -> None: + self.connection_man.update_and_connect(raise_window, menu_only=True) + def update_and_connect(self, raise_window: bool) -> None: self.connection_man.update_and_connect(raise_window) diff --git a/dzgui/managers/connection.py b/dzgui/managers/connection.py index a03992d..593729f 100644 --- a/dzgui/managers/connection.py +++ b/dzgui/managers/connection.py @@ -15,6 +15,7 @@ from dzgui.api.steam import ( enqueue_mod, get_remote_signatures, get_needs_update, + load_to_menu, ) from dzgui.api.mods import ( @@ -74,6 +75,7 @@ class Prerequisites: steam_proc: SteamProcess mods: list[list[str]] game_mode: bool + is_last_server: bool class ConnectionManager: @@ -172,6 +174,8 @@ class ConnectionManager: game_mode = prefs.is_game_mode + is_last = self.is_last_server() + prereqs = Prerequisites( name=info.server_name, appid=info.game_id, @@ -186,11 +190,23 @@ class ConnectionManager: steam_proc=steam_proc, mods=remote_mods, game_mode=game_mode, + is_last_server=is_last, ) func = StoredFunc(self.controller.open_connection_assistant, prereqs) self.thread_man.set_cleanup_func(func, destroy_first=True) + def is_last_server(self) -> bool: + prefs = self.controller.get_prefs() + history = prefs.paths.history + try: + lines = history.read_text().splitlines() + last = lines[-1] + current = Servers.record_to_fqip(self.record) + return last == current + except Exception: + return False + @call_on_thread(dialog.querying) def query_details(self, record: Servers.Record) -> None: details = Servers.get_details(record) @@ -245,11 +261,14 @@ class ConnectionManager: dialog = ExceptionDialog(self.controller, server_timeout) dialog.run() - def _connect_steam(self) -> None: + def _connect_steam(self, menu_only: bool) -> None: addr = f"{self.record.ip}:{self.record.gameport}" playername = self.controller.query_config(Preferences.NAME) client = self.controller.query_config(Preferences.CLIENT) - rc = connect(client, addr, self.appid, playername, self.remote_mod_ids) + if menu_only: + rc = load_to_menu(client, addr, self.appid, playername, self.remote_mod_ids) + else: + rc = connect(client, addr, self.appid, playername, self.remote_mod_ids) if rc != 0: # TODO: log/pop the error func = StoredFunc(self.controller.update_status) @@ -274,7 +293,7 @@ class ConnectionManager: self.controller.add_to_history(self.history) self.controller.open_page(NotebookPage.SERVERS) - def _update_mods(self, raise_window: bool) -> None: + def _update_mods(self, raise_window: bool, menu_only: bool = False) -> None: # NOTE: fast enqueue all mods in auto mode prefs = self.controller.get_prefs() @@ -312,11 +331,11 @@ class ConnectionManager: update_signatures(self.missing_mods, prefs.paths.version) # TODO: just push steam path directly rebuild_symlinks(prefs.paths.config) - self._connect_steam() + self._connect_steam(menu_only) @call_on_thread(waiting_for_mods, show_cancel=True) - def update_and_connect(self, raise_window: bool) -> None: + def update_and_connect(self, raise_window: bool, menu_only: bool = False) -> None: if len(self.missing_mods) > 0: - self._update_mods(raise_window) + self._update_mods(raise_window, menu_only) else: - self._connect_steam() + self._connect_steam(menu_only) diff --git a/dzgui/strings/preconnect.py b/dzgui/strings/preconnect.py index 17c8a5e..ef45783 100644 --- a/dzgui/strings/preconnect.py +++ b/dzgui/strings/preconnect.py @@ -1,5 +1,6 @@ update_mods = "Update mods and connect" connect = "Connect" +connect_last = "Load to menu screen" back = "Back" cancel = "Cancel" warnings = "Warnings" diff --git a/dzgui/views/pages/preconnect.py b/dzgui/views/pages/preconnect.py index 3f5d0ca..fea0dcb 100644 --- a/dzgui/views/pages/preconnect.py +++ b/dzgui/views/pages/preconnect.py @@ -106,6 +106,9 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): label=preconnect.back, halign=Gtk.Align.END, hexpand=True ) self.ok = Gtk.Button(label=preconnect.update_mods, halign=Gtk.Align.END) + self.connect_last = Gtk.Button( + label=preconnect.connect_last, halign=Gtk.Align.END + ) # TODO: abstract self.raise_window = Gtk.CheckButton( @@ -126,13 +129,14 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): spacing=5, ) box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) - for button in self.back, self.ok: + for button in self.back, self.ok, self.connect_last: box.add(button) for el in self.raise_window, box: self.button_box.add(el) self.back.connect("clicked", self._on_back_clicked) self.ok.connect("clicked", self._on_ok_clicked) + self.connect_last.connect("clicked", self._on_connect_last_clicked) self.title = Gtk.Label(label="") add_class(self.title, "preconnect-heading") @@ -213,6 +217,9 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): if event.keyval == Gdk.KEY_u: self.ok.emit("clicked") + def _on_connect_last_clicked(self, button: Gtk.Button) -> None: + self.controller.update_and_load_to_menu(self.raise_window.get_active()) + def _on_ok_clicked(self, button: Gtk.Button) -> None: self.controller.update_and_connect(self.raise_window.get_active()) @@ -308,6 +315,11 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): prefix = preconnect.total_mods self.mod_count.set_text(f"{prefix}{str(total_mods)}.{suffix}") + if prereqs.is_last_server: + self.connect_last.show() + else: + self.connect_last.hide() + self._process_warnings(prereqs) if self.tree.is_visible(): self.tree.grab_focus() From 2c9b36ba84181e176c166a9b024d03d6e5587dd8 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 05:24:23 +0900 Subject: [PATCH 05/18] fix: append history queue (WIP) --- dzgui/api/pefile.py | 3 +-- dzgui/controllers/mc.py | 7 +++++-- dzgui/managers/config.py | 13 +++++++++---- dzgui/managers/connection.py | 2 +- dzgui/model/proxy_model.py | 5 +++-- dzgui/model/servers.py | 18 ++++++++++-------- 6 files changed, 29 insertions(+), 19 deletions(-) diff --git a/dzgui/api/pefile.py b/dzgui/api/pefile.py index d9498eb..3688cdb 100644 --- a/dzgui/api/pefile.py +++ b/dzgui/api/pefile.py @@ -456,6 +456,5 @@ def get_pretty_version(steam_path: Path, appid: int) -> str | None: vers = get_dayz_version(pe_file_path) dayz_version = dayz_version_to_str(vers) return dayz_version - except Exception as e: - print(e) + except Exception: return None diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index ae24e66..53a8b14 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -409,9 +409,12 @@ class Controller(GObject.GObject): saved_tree = self.get_servers().get_saved() ServerModelManager(self, saved_tree).remove_by_record(record) - def add_to_history(self, record: dict[str, Any]) -> None: + def add_to_history(self, row: dict[str, Any], record: "Record") -> None: tv = self.get_servers().get_recent() - ServerModelManager(self, tv).add_to_history(record) + ServerModelManager(self, tv).add_to_history((row, record)) + + # def append_to_history(self, record: "Record") -> None: + # self.config_man.append_to_history_file(record) def remove_from_history(self, record: "Record") -> None: # NOTE: remove action is only possible from history tree context menu, diff --git a/dzgui/managers/config.py b/dzgui/managers/config.py index e550e19..5bdcc7f 100644 --- a/dzgui/managers/config.py +++ b/dzgui/managers/config.py @@ -1,6 +1,7 @@ import logging import traceback +from collections import deque from typing import Any, TYPE_CHECKING from dzgui.const.constants import ( @@ -52,11 +53,15 @@ class ConfigManager: ips.append(record) self.update_config(Preferences.IP_LIST, ips) - def update_history_file(self, records: list[Any]) -> None: + def update_history_file(self, fqip: str) -> None: + with open(self.prefs.paths.history, "r") as f: + lines = [line.rstrip() for line in f.readlines()] + lines.append(fqip) + lines = set(lines) + dq = deque(lines, maxlen=10) with open(self.prefs.paths.history, "w") as f: - for record in records: - addr = f"{record[7]}:{record[8]}" - f.write(f"{addr}\n") + for record in dq: + f.write(f"{record}\n") def remove_saved_server(self, record: str) -> None: ips = self.lookup(Preferences.IP_LIST) diff --git a/dzgui/managers/connection.py b/dzgui/managers/connection.py index 593729f..a8f5cb2 100644 --- a/dzgui/managers/connection.py +++ b/dzgui/managers/connection.py @@ -290,7 +290,7 @@ class ConnectionManager: self.thread_man.set_cleanup_func(func) def _add_to_history_and_return(self) -> None: - self.controller.add_to_history(self.history) + self.controller.add_to_history(self.history, self.record) self.controller.open_page(NotebookPage.SERVERS) def _update_mods(self, raise_window: bool, menu_only: bool = False) -> None: diff --git a/dzgui/model/proxy_model.py b/dzgui/model/proxy_model.py index 9639789..378d7dc 100644 --- a/dzgui/model/proxy_model.py +++ b/dzgui/model/proxy_model.py @@ -85,8 +85,9 @@ class ProxyModelManager: if found is False: self.control_model.append(history) - if len(self.control_model) == 11: - del self.control_model[0] + + # if len(self.control_model) == 11: + # del self.control_model[0] self.filter(FilterMode.INITIAL, skip_cache=True) diff --git a/dzgui/model/servers.py b/dzgui/model/servers.py index ad75b15..9726b92 100644 --- a/dzgui/model/servers.py +++ b/dzgui/model/servers.py @@ -23,7 +23,7 @@ gi.require_version("Gtk", "3.0") from gi.repository import Gtk # noqa E402 if TYPE_CHECKING: - from dzgui.api.servers import A2SInfo + from dzgui.api.servers import A2SInfo, Record from dzgui.controllers.mc import Controller from dzgui.model.proxy_model import ProxyModelManager from dzgui.views.trees.tree_servers import ServerTreeView @@ -225,15 +225,12 @@ class ServerModelManager: ) = None, ) -> None: proxy_man = self._get_proxy_man() - config_man = self.controller.get_config_man() if rows is not None: records = rows else: control_model = proxy_man.get_control() records = control_model - config_man.update_history_file(records) - config_man.update_history_file(records) self._sort_unique_maps(records) proxy = proxy_man.get_proxy_model() self.tv.set_model(proxy) @@ -249,15 +246,20 @@ class ServerModelManager: self.emitter.emit("load_maps", store) # self.emitter.emit("servers_loaded_init") - def add_to_history(self, record: dict[str, Any]) -> None: + # TODO: dataclass for record rows; check for other dict annotations + def add_to_history(self, data: tuple[dict[str, Any], "Record"]) -> None: + row, record = data proxy_man = self._get_proxy_man() - rows = Servers.parse_json([record]) + rows = Servers.parse_json([row]) try: proxy_man.append_row_to_history(rows[0]) + self.update_history() except Exception: self.update_history(rows) - return - self.update_history() + + fqip = Servers.record_to_fqip(record) + config_man = self.controller.get_config_man() + config_man.update_history_file(fqip) def remove_from_history(self, record: Servers.Record) -> None: proxy_man = self._get_proxy_man() From 1ea30270ced655a52e4cc9590a6565e427f1d688 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 05:47:59 +0900 Subject: [PATCH 06/18] fix: break after 10th record --- dzgui/managers/config.py | 20 ++++++++++++++----- dzgui/managers/connection.py | 38 ++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/dzgui/managers/config.py b/dzgui/managers/config.py index 5bdcc7f..d0ab9e8 100644 --- a/dzgui/managers/config.py +++ b/dzgui/managers/config.py @@ -55,12 +55,22 @@ class ConfigManager: def update_history_file(self, fqip: str) -> None: with open(self.prefs.paths.history, "r") as f: - lines = [line.rstrip() for line in f.readlines()] - lines.append(fqip) - lines = set(lines) - dq = deque(lines, maxlen=10) + ips = [line.rstrip() for line in f.readlines()] + + seen = set() + ips.append(fqip) + # NOTE: Preserve linear order of records while deduplicating + unique = deque(maxlen=10) + for i in range(len(ips) - 1, -1, -1): + if ips[i] not in seen: + seen.add(ips[i]) + unique.appendleft(ips[i]) + # NOTE: appendleft will push 11th item off right edge + if len(unique) == 10: + break + with open(self.prefs.paths.history, "w") as f: - for record in dq: + for record in unique: f.write(f"{record}\n") def remove_saved_server(self, record: str) -> None: diff --git a/dzgui/managers/connection.py b/dzgui/managers/connection.py index a8f5cb2..2521485 100644 --- a/dzgui/managers/connection.py +++ b/dzgui/managers/connection.py @@ -265,26 +265,26 @@ class ConnectionManager: addr = f"{self.record.ip}:{self.record.gameport}" playername = self.controller.query_config(Preferences.NAME) client = self.controller.query_config(Preferences.CLIENT) - if menu_only: - rc = load_to_menu(client, addr, self.appid, playername, self.remote_mod_ids) - else: - rc = connect(client, addr, self.appid, playername, self.remote_mod_ids) - if rc != 0: - # TODO: log/pop the error - func = StoredFunc(self.controller.update_status) - self.thread_man.set_cleanup_func(func) - return + #if menu_only: + # rc = load_to_menu(client, addr, self.appid, playername, self.remote_mod_ids) + #else: + # rc = connect(client, addr, self.appid, playername, self.remote_mod_ids) + #if rc != 0: + # # TODO: log/pop the error + # func = StoredFunc(self.controller.update_status) + # self.thread_man.set_cleanup_func(func) + # return - self.thread_man.show_cancel(False) - self.thread_man.update_dialog(waiting_for_launch) - while True: - # TODO: check cancel event - if self.controller.get_exit_event().is_set(): - # TODO: some facility to also close spawned steam process - return - if is_dayz_running(): - break - time.sleep(1) + #self.thread_man.show_cancel(False) + #self.thread_man.update_dialog(waiting_for_launch) + #while True: + # # TODO: check cancel event + # if self.controller.get_exit_event().is_set(): + # # TODO: some facility to also close spawned steam process + # return + # if is_dayz_running(): + # break + # time.sleep(1) func = StoredFunc(self._add_to_history_and_return) self.thread_man.set_cleanup_func(func) From 67bae3c3e034e3c3091b9f70b35678ef86e617f2 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 05:48:25 +0900 Subject: [PATCH 07/18] chore: uncomment connection logic --- dzgui/managers/connection.py | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/dzgui/managers/connection.py b/dzgui/managers/connection.py index 2521485..a8f5cb2 100644 --- a/dzgui/managers/connection.py +++ b/dzgui/managers/connection.py @@ -265,26 +265,26 @@ class ConnectionManager: addr = f"{self.record.ip}:{self.record.gameport}" playername = self.controller.query_config(Preferences.NAME) client = self.controller.query_config(Preferences.CLIENT) - #if menu_only: - # rc = load_to_menu(client, addr, self.appid, playername, self.remote_mod_ids) - #else: - # rc = connect(client, addr, self.appid, playername, self.remote_mod_ids) - #if rc != 0: - # # TODO: log/pop the error - # func = StoredFunc(self.controller.update_status) - # self.thread_man.set_cleanup_func(func) - # return + if menu_only: + rc = load_to_menu(client, addr, self.appid, playername, self.remote_mod_ids) + else: + rc = connect(client, addr, self.appid, playername, self.remote_mod_ids) + if rc != 0: + # TODO: log/pop the error + func = StoredFunc(self.controller.update_status) + self.thread_man.set_cleanup_func(func) + return - #self.thread_man.show_cancel(False) - #self.thread_man.update_dialog(waiting_for_launch) - #while True: - # # TODO: check cancel event - # if self.controller.get_exit_event().is_set(): - # # TODO: some facility to also close spawned steam process - # return - # if is_dayz_running(): - # break - # time.sleep(1) + self.thread_man.show_cancel(False) + self.thread_man.update_dialog(waiting_for_launch) + while True: + # TODO: check cancel event + if self.controller.get_exit_event().is_set(): + # TODO: some facility to also close spawned steam process + return + if is_dayz_running(): + break + time.sleep(1) func = StoredFunc(self._add_to_history_and_return) self.thread_man.set_cleanup_func(func) From ea3631072ba9946ff549b5a25c5a00db901a06cb Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 05:49:16 +0900 Subject: [PATCH 08/18] chore: add typehint ignore --- dzgui/managers/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dzgui/managers/config.py b/dzgui/managers/config.py index d0ab9e8..419f5b0 100644 --- a/dzgui/managers/config.py +++ b/dzgui/managers/config.py @@ -60,7 +60,7 @@ class ConfigManager: seen = set() ips.append(fqip) # NOTE: Preserve linear order of records while deduplicating - unique = deque(maxlen=10) + unique = deque(maxlen=10) # type: ignore for i in range(len(ips) - 1, -1, -1): if ips[i] not in seen: seen.add(ips[i]) From 7b4382b178bdde7b2cd1f893e39fa2d7500ddc1c Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 05:52:46 +0900 Subject: [PATCH 09/18] chore: bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6635ddc..41d7062 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux" authors = [ {name = "aclist"} ] -version = "7.0.0b7" +version = "7.0.0b8" license = "GPL-3.0-or-later" license-files = ["LICENSE"] readme = "README.md" From 9af2145ee8bd0b2b68ae08fd9a1198f4ac56cc54 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 29 May 2026 00:41:24 +0900 Subject: [PATCH 10/18] fix: use dedicated LAN error message --- dzgui/model/servers.py | 13 ++++++++++++- dzgui/strings/dialogs.py | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/dzgui/model/servers.py b/dzgui/model/servers.py index 9726b92..a336409 100644 --- a/dzgui/model/servers.py +++ b/dzgui/model/servers.py @@ -14,6 +14,7 @@ from dzgui.const.constants import ( ) from dzgui.const.enum import FilterMode, Preferences, ServerTab from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager +from dzgui.strings import dialogs from dzgui.util.strings import api_warn_msg, dialog from dzgui.views.dialogs.generic import ExceptionDialog @@ -130,7 +131,7 @@ class ServerModelManager: servers = [] ports = range(1, 256) - failure_func = StoredFunc(self._cleanup_on_failure) + failure_func = StoredFunc(self._cleanup_on_lan_failure) event = threading.Event() with ThreadPoolExecutor() as executor: @@ -405,6 +406,16 @@ class ServerModelManager: if self.first_iteration: self._update_maps() + def _cleanup_on_lan_failure(self, show_dialog: bool = True) -> None: + if self.preserve_on_fail is False: + self.tv.set_model(None) + filter_man = self.tv.get_filter_man() + filter_man.set_unique_maps([]) + + if show_dialog: + dialog = ExceptionDialog(self.controller, dialogs.load_error_lan) + dialog.run() + def _cleanup_on_failure(self, show_dialog: bool = True) -> None: # TODO: disable map, keyword, and filter widgets if model is None # signal driven (servers_empty, servers_failed_to_load) diff --git a/dzgui/strings/dialogs.py b/dzgui/strings/dialogs.py index d7587a1..db6900a 100644 --- a/dzgui/strings/dialogs.py +++ b/dzgui/strings/dialogs.py @@ -4,3 +4,5 @@ waiting_for_mods = "Waiting for Steam to update mods" fetching_update = "Fetching update" failed_to_update = "Failed to update DZGUI executable" update_success = "Updated successfully. Please exit and relaunch." + +load_error_lan = "Failed to find any servers on your network.\nCheck the server query port or your firewall settings." From 3dc58c1a691a8086a03a4894f685dc52e0b4ab92 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 29 May 2026 00:41:47 +0900 Subject: [PATCH 11/18] fix: abort if deps could not be installed in cpython distribution --- scripts/prebuild.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/prebuild.py b/scripts/prebuild.py index 239cfbb..b0c1c0b 100644 --- a/scripts/prebuild.py +++ b/scripts/prebuild.py @@ -53,7 +53,8 @@ def untar(file: str, glob: str, renamed: str) -> None: def install_deps() -> None: pip = build_dir.joinpath(f"{CPYTHON_BUILD}/bin/pip") - subprocess.run([pip, "install", root]) + proc = subprocess.run([pip, "install", root]) + return proc.returncode def repackage() -> None: @@ -66,7 +67,9 @@ def repackage() -> None: def rebuild_cpython() -> None: if cpython_dir.is_dir() is False: get_cpython() - install_deps() + code = install_deps() + if code != 0: + return repackage() return get_packaged_version() From 302a43e65ec3de817702635bacb81f720f82222d Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 29 May 2026 00:42:43 +0900 Subject: [PATCH 12/18] change: enable empty/full filters on first run of LAN page --- dzgui/views/pages/servers.py | 8 +++++++- dzgui/views/trees/tree_servers.py | 9 +++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/dzgui/views/pages/servers.py b/dzgui/views/pages/servers.py index 651dfe5..5bbfa7c 100644 --- a/dzgui/views/pages/servers.py +++ b/dzgui/views/pages/servers.py @@ -47,7 +47,13 @@ class ServerNotebook(Gtk.ScrolledWindow): self.recent = ServerTreeView( controller, ServerTab.RECENT, ContextMenuGroup.RECENT ) - self.lan = ServerTreeView(controller, ServerTab.LAN, ContextMenuGroup.SCAN_LAN) + self.lan = ServerTreeView( + controller, + ServerTab.LAN, + ContextMenuGroup.SCAN_LAN, + show_empty=True, + show_full=True, + ) tabs = [ (self.browser, server_labels.browser), diff --git a/dzgui/views/trees/tree_servers.py b/dzgui/views/trees/tree_servers.py index ac2b2e3..12eaf8c 100644 --- a/dzgui/views/trees/tree_servers.py +++ b/dzgui/views/trees/tree_servers.py @@ -38,7 +38,12 @@ QUEUE_CHECK_DELAY = 200 class ServerTreeView(ContextMixin, TreeView): # type: ignore def __init__( - self, controller: "Controller", enum: ServerTab, menu: ContextMenuGroup + self, + controller: "Controller", + enum: ServerTab, + menu: ContextMenuGroup, + show_empty: bool = False, + show_full: bool = False, ) -> None: super().__init__(controller, menu=menu) @@ -48,7 +53,7 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore self.loaded = False - self.filter_man = FilterManager() + self.filter_man = FilterManager(show_empty, show_full) self.proxy_man = ProxyModelManager(self.filter_man) model = self.proxy_man.get_proxy_model() self.set_model(model) From 86e2249a23ae6baa09d5dce0064a3fc44b1b3e87 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 29 May 2026 00:43:01 +0900 Subject: [PATCH 13/18] change: enable empty/full filters on first run of LAN page --- dzgui/managers/filter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dzgui/managers/filter.py b/dzgui/managers/filter.py index 52ef6a0..92adf55 100644 --- a/dzgui/managers/filter.py +++ b/dzgui/managers/filter.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: class FilterManager: - def __init__(self) -> None: + def __init__(self, show_empty: bool = False, show_full: bool = False) -> None: self.map_store = ModelFactory().make_map_store() @@ -17,10 +17,10 @@ class FilterManager: self.default_filters = { strings.filter_1pp: True, strings.filter_day: True, - strings.filter_empty: False, + strings.filter_empty: show_empty, strings.filter_3pp: True, strings.filter_night: True, - strings.filter_full: False, + strings.filter_full: show_full, strings.filter_lowpop: True, strings.filter_nonascii: False, strings.filter_duplicate: False, From 1212c091105a983a77ad13c00aaaf896840d7808 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 29 May 2026 00:43:34 +0900 Subject: [PATCH 14/18] feat: add tooltip to load menu button --- dzgui/strings/preconnect.py | 3 +++ dzgui/views/pages/preconnect.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dzgui/strings/preconnect.py b/dzgui/strings/preconnect.py index ef45783..7b18939 100644 --- a/dzgui/strings/preconnect.py +++ b/dzgui/strings/preconnect.py @@ -1,6 +1,9 @@ update_mods = "Update mods and connect" connect = "Connect" connect_last = "Load to menu screen" +connect_last_tooltip = ( + "Launch DayZ with these mods (if any) and\ngo to the menu screen without connecting." +) back = "Back" cancel = "Cancel" warnings = "Warnings" diff --git a/dzgui/views/pages/preconnect.py b/dzgui/views/pages/preconnect.py index fea0dcb..eb9459c 100644 --- a/dzgui/views/pages/preconnect.py +++ b/dzgui/views/pages/preconnect.py @@ -107,7 +107,7 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): ) self.ok = Gtk.Button(label=preconnect.update_mods, halign=Gtk.Align.END) self.connect_last = Gtk.Button( - label=preconnect.connect_last, halign=Gtk.Align.END + label=preconnect.connect_last, halign=Gtk.Align.END, tooltip_text=preconnect.connect_last_tooltip ) # TODO: abstract From c0c3ee8c64675c106b1c92f973b7b9abb3eb1504 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 29 May 2026 00:44:00 +0900 Subject: [PATCH 15/18] fix: catch error if no GTK theme is available (headless installs) --- dzgui/views/base.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/dzgui/views/base.py b/dzgui/views/base.py index e046b65..f9b7a00 100644 --- a/dzgui/views/base.py +++ b/dzgui/views/base.py @@ -323,8 +323,12 @@ class App(Gtk.Application): MainController = Controller() -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) +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) +except Exception as e: + logger.warning(e) + warnings.warn(strings.gtk_theme_missing, stacklevel=2) From 387930b147ac98a5690fdadd37f082e364408a36 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 29 May 2026 00:44:17 +0900 Subject: [PATCH 16/18] chore: update strings file --- dzgui/util/strings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dzgui/util/strings.py b/dzgui/util/strings.py index 1d76209..bc6904c 100644 --- a/dzgui/util/strings.py +++ b/dzgui/util/strings.py @@ -611,6 +611,7 @@ atomic_buttons = AtomicButton( keys_tooltip="Toggles the keybindings dialog", ) +gtk_theme_missing = "No GTK theme provided" steam_icon_missing = "Steam icon not found in IconTheme" missing_changelog = "Error: failed to read changelog" From 3851b76ca0e60f01365c030e7a75c38bb2832960 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 29 May 2026 00:44:35 +0900 Subject: [PATCH 17/18] docs: update changelog --- CHANGELOG.md | 3 ++- dzgui/data/CHANGELOG.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a1b254..6bc7eb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - Preconnect warnings/failsafes like filesize - Save filters per server context between sessions - Preboot progress dialog +- Choose to jump into splash screen instead of server ## Changed @@ -52,6 +53,7 @@ - Embed Workshop link in Options menu - Disable overlay scrollbars on server tables - Reduce size of geolocation DB on disk (~100MB) +- Enable LAN page Empty/Full filters on first run of app ## Dropped - Debug mode @@ -67,7 +69,6 @@ ## Unreleased - Load offline mods -- Choose to jump into splash screen instead of server - Local documentation - Raw debug command in context menu diff --git a/dzgui/data/CHANGELOG.md b/dzgui/data/CHANGELOG.md index 4a1b254..6bc7eb1 100644 --- a/dzgui/data/CHANGELOG.md +++ b/dzgui/data/CHANGELOG.md @@ -36,6 +36,7 @@ - Preconnect warnings/failsafes like filesize - Save filters per server context between sessions - Preboot progress dialog +- Choose to jump into splash screen instead of server ## Changed @@ -52,6 +53,7 @@ - Embed Workshop link in Options menu - Disable overlay scrollbars on server tables - Reduce size of geolocation DB on disk (~100MB) +- Enable LAN page Empty/Full filters on first run of app ## Dropped - Debug mode @@ -67,7 +69,6 @@ ## Unreleased - Load offline mods -- Choose to jump into splash screen instead of server - Local documentation - Raw debug command in context menu From 3a5c061dbf0354aaad9f6c7c106bfbe38964f417 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Fri, 29 May 2026 01:03:29 +0900 Subject: [PATCH 18/18] chore: update docstring --- dzgui/api/steam.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dzgui/api/steam.py b/dzgui/api/steam.py index fc1f6fd..0fa126d 100644 --- a/dzgui/api/steam.py +++ b/dzgui/api/steam.py @@ -147,7 +147,7 @@ def connect(client: str, addr: str, appid: int, name: str, mods: list[str]) -> i def load_to_menu(client: str, addr: str, appid: int, name: str, mods: list[str]) -> int: - """Loads to the menu screen with the selected mods; used for server restarts""" + """Loads to the menu screen with the selected mods; used for AFK/pre-joining""" concat = concat_mods(mods) client_args = concat_bash_args(client) params = [