Compare commits

..
Author SHA1 Message Date
aclist 1d971765ab Merge c15b77d470 into 5014402e39 2026-09-07 20:07:47 +00:00
38 changed files with 101 additions and 317 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
+8 -20
View File
@@ -255,28 +255,16 @@ class PeFileError(Exception):
pass
def parse_version_number(data: BinaryIO) -> FileVersion:
# https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
"""
Note: structure differs from standard VS_FIXEDFILEINFO
Major: lower 16 bits of first DWORD
Minor: upper 16 bits of first DWORD
Build: lower 16 bits of third DWORD
Revision: upper 16 bits of second DWORD, zero-padded
Lower 16 bits of revision DWORD appear unused.
"""
major_minor = struct.unpack("<I", data.read(4))[0]
revision_pair = struct.unpack("<I", data.read(4))[0]
build_dword = struct.unpack("<I", data.read(4))[0]
major = major_minor & 0xFFFF
minor = (major_minor >> 16) & 0xFFFF
build = build_dword & 0xFFFF
revision = (revision_pair >> 16) & 0xFFFF
revision = f"{revision:03d}"
return FileVersion(major, minor, build, revision)
minor = struct.unpack("<L", data.read(4))[0] >> 16 & 0xFFFF
major = struct.unpack("<L", data.read(4))[0] >> 0 & 0xFFFF
build = struct.unpack("<L", data.read(4))[0] >> 0 & 0xFFFF
# NOTE: 0-pad revision number
revision = str(struct.unpack("<L", data.read(4))[0] >> 16 & 0xFFFF)
offset = 3 - len(revision)
padded = "0" * offset + revision
return FileVersion(major, minor, build, padded)
def seek_to_hex(address: str, data: BinaryIO) -> None:
+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
+3 -44
View File
@@ -1,14 +1,12 @@
import logging
import os
import psutil
import re
import requests
import subprocess
import vdf # type: ignore
from pathlib import Path
from requests.exceptions import HTTPError
from typing import Any, Generator, Union
from typing import Any, Union
from warnings import deprecated
from dzgui.api.acf import ACF
@@ -21,7 +19,6 @@ from dzgui.const.constants import (
DAYZ_LAUNCHER,
DEBIAN_STEAM_PATH,
DEFAULT_STEAM_PATH,
DOWNLOADS_PATH,
FLATPAK_STEAM_PATH,
LIBRARYFOLDERS_PATH,
UBUNTU_STEAM_PATH,
@@ -35,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)
@@ -247,9 +243,9 @@ def find_user_id(path: Path) -> str | None:
last_user = str(next(iter(users)))
if len(users) == 1:
return last_user
last_stamp = int(users[last_user]["timestamp"])
last_stamp = int(users[last_user]["Timestamp"])
for user in users:
stamp = int(users[user]["timestamp"])
stamp = int(users[user]["Timestamp"])
if stamp > last_stamp:
last_stamp = stamp
last_user = user
@@ -266,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,
@@ -287,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:
@@ -441,22 +416,6 @@ def is_dayz_running() -> bool:
return any(s in item for sublist in procs for item in sublist for s in substring)
def get_pending_downloads(steam_path: Path, appid: int) -> Generator[Path, None, None]:
root_path = get_app_path(steam_path, appid)
downloads_dir = root_path / DOWNLOADS_PATH
files = downloads_dir.iterdir()
return files
def is_dayz_downloading(steam_path: Path, appid: int) -> bool:
files = get_pending_downloads(steam_path, appid)
reg = re.compile(rf"^state_(?:{APPID_DAYZ_EXP}|{APPID_DAYZ})_.*\.patch$")
pending = [f for f in files if reg.match(f.name)]
if len(pending) > 0:
return True
return False
def get_app_path(folders_path: Path, appid: int) -> Path:
app_path = None
+2 -28
View File
@@ -1,13 +1,10 @@
import logging
import os
import shutil
from pathlib import Path
from typing import TYPE_CHECKING
from warnings import deprecated
from dzgui.const.constants import APP_NAME, STEAM_ICON, STEAM_ICONS_PATH
from dzgui.const.constants import APP_NAME
from dzgui.const.enum import Preferences
from dzgui.config.query import lookup
from dzgui.config.userprefs import UserPrefs
@@ -26,7 +23,7 @@ from dzgui.util.dirs import make_parents
from dzgui.util.localize import set_locale
from dzgui.util.map_count import test_map_count
from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS
from dzgui.util.strings import init, steam_icon_missing
from dzgui.util.strings import init
from dzgui.views.base import App
from dzgui.views.dialogs.boot import BootWindow
@@ -99,28 +96,6 @@ def copy_bare_configs(config: "Path", resolution: "Path") -> tuple[bool, bool]:
return (config_changed, state_changed)
@deprecated("Currently unused")
def load_steam_icon(config: Path) -> None:
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa
default = lookup(config, Preferences.DEFAULT)
icon_path = Path(default).joinpath(STEAM_ICONS_PATH)
try:
theme = Gtk.IconTheme.get_default()
paths = theme.get_search_path()
theme.set_search_path(paths + [str(icon_path)])
paths = theme.get_search_path()
icons = theme.list_icons(None)
if STEAM_ICON not in icons:
logger.warning(steam_icon_missing)
except Exception as e:
print(e)
logger.warning(e)
def load_gui(version: str, is_debug: bool) -> None:
lock = lock_acquire() # noqa
@@ -168,7 +143,6 @@ def load_gui(version: str, is_debug: bool) -> None:
local_coords, latest_release = bootwin.get_results()
use_miles = lookup(XDG.config, Preferences.DIST)
prefs = UserPrefs(
is_steam_deck=_is_steam_deck,
is_game_mode=_is_game_mode,
+1 -3
View File
@@ -15,7 +15,6 @@ DAYZ_BINARY = "DayZ_x64.exe"
DAYZ_LAUNCHER = "DayZLauncher.exe"
LIBRARYFOLDERS_PATH = "steamapps/libraryfolders.vdf"
DOWNLOADS_PATH = "steamapps/downloading"
WORKSHOP_PATH = "steamapps/workshop/content/" + str(APPID_DAYZ)
STEAM_CMD = "steam"
@@ -37,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"
@@ -88,7 +87,6 @@ DEFAULT_STEAM_PATH = ".local/share/Steam"
FLATPAK_STEAM_PATH = ".var/app/com.valvesoftware.Steam/data/Steam"
UBUNTU_STEAM_PATH = ".steam/steam"
DEBIAN_STEAM_PATH = ".steam/debian-installation"
STEAM_ICONS_PATH = "public"
LOG_FILTERS = ("CRITICAL", "WARNING", "INFO", "DEBUG")
+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 -19
View File
@@ -14,7 +14,6 @@ from dzgui.api.shortcuts import Shortcuts
from dzgui.api.steam import (
connect,
connect_debug,
is_dayz_downloading,
get_app_allows_downloads,
get_app_name,
get_needs_update,
@@ -49,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
@@ -85,7 +84,6 @@ class Prerequisites:
available_space: float
passworded: bool
dayz_running: bool
download_paused: bool
steam_proc: SteamProcess
mods: list[list[str]]
game_mode: bool
@@ -171,7 +169,6 @@ class ConnectionManager:
free_mib = format_mib(free)
dayz_running = is_dayz_running()
download_paused = is_dayz_downloading(steam_path, self.appid)
running_app = get_running_app()
if running_app is not None:
@@ -187,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
@@ -213,7 +210,6 @@ class ConnectionManager:
available_space=free_mib,
passworded=info.password_protected,
dayz_running=dayz_running,
download_paused=download_paused,
steam_proc=steam_proc,
mods=remote_mods,
game_mode=game_mode,
@@ -304,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)
@@ -360,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:
+2 -4
View File
@@ -22,12 +22,11 @@ class FilterManager:
strings.filter_night: True,
strings.filter_full: show_full,
strings.filter_official: True,
strings.filter_modded: True,
strings.filter_nonascii: False,
strings.filter_lowpop: True,
strings.filter_unofficial: True,
strings.filter_unmodded: True,
strings.filter_duplicate: False,
strings.filter_nonascii: False,
strings.filter_modded: True,
}
# TODO: strings
@@ -44,7 +43,6 @@ class FilterManager:
strings.filter_unofficial: "Third-party servers",
strings.filter_duplicate: "Duplicates of existing servers (usually spoofed)",
strings.filter_modded: "Servers with one or more mods",
strings.filter_unmodded: "Servers with no mods",
}
self.active_keyword = ""
+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)
-2
View File
@@ -243,8 +243,6 @@ class ProxyModelManager:
rows = [row for row in rows if (row[4] / row[5] * 100) > 30]
case strings.filter_modded:
rows = [row for row in rows if not row[11]]
case strings.filter_unmodded:
rows = [row for row in rows if row[11]]
return rows
def filter_toggle_on(self, filters: tuple[str]) -> list:
+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 -5
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
@@ -47,7 +48,3 @@ running_app = (
"Either stop the game first, or update your global Steam settings or the game's local settings.\n"
"Otherwise, mods may be queued for download but never update."
)
downloads_paused = (
"It looks like DayZ may have a pending update on Steam that was paused.\n"
"To enqueue mods for download, unpause any pending DayZ update and allow it to complete."
)
+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"
-1
View File
@@ -156,7 +156,6 @@ filter_duplicate = "Duplicate"
filter_official = "Official"
filter_unofficial = "Unofficial"
filter_modded = "Modded"
filter_unmodded = "Unmodded"
# maps
all_maps = "All maps"
+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}"
+18 -1
View File
@@ -1,5 +1,6 @@
import logging
import signal
import warnings
from typing import TYPE_CHECKING, Literal, Self
@@ -7,11 +8,12 @@ from dzgui.const.constants import (
APP_NAME,
APP_NAME_LOWER,
GTK_APP_ID,
STEAM_ICON,
)
from dzgui.const.enum import NotebookPage
from dzgui.controllers.emitter import Emitter
from dzgui.controllers.mc import Controller
from dzgui.util import css
from dzgui.util import css, strings
from dzgui.util.keys import is_ctrl_mask
from dzgui.views.components.connect_panel import ConnectPanel
from dzgui.views.components.crumbs import Breadcrumbs
@@ -45,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:
@@ -331,4 +337,15 @@ class App(ColorAwareApp, Gtk.Application): # type: ignore
) -> None:
self.win.halt_proc_and_quit()
MainController = Controller()
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)
-1
View File
@@ -65,7 +65,6 @@ class IconButton(Gtk.Button):
self.set_image(self.icon)
self.set_image_position(position)
self.set_focus_on_click(False)
self.set_always_show_image(True)
def swap_icon(self, icon: str) -> None:
start = self.icon.get_margin_start()
+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 -3
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(
@@ -279,8 +280,6 @@ class PreConnectionAssistant(Gtk.Box):
warnings.append(preconnect.protected_server)
if prereqs.dayz_running is True:
warnings.append(preconnect.dayz_running)
if len(prereqs.mods) > 0 and prereqs.download_paused:
warnings.append(preconnect.downloads_paused)
allows_dl, running_app = prereqs.allows_downloads
if len(prereqs.mods) > 0 and allows_dl is False:
+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.0b28"
version = "7.0.0b23"
license = "GPL-3.0-or-later"
license-files = ["LICENSE"]
readme = "README.md"
+1 -1
View File
@@ -8,6 +8,6 @@
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AutoLogin" "1"
"timestamp" "0"
"Timestamp" "0"
}
}
+2 -2
View File
@@ -8,7 +8,7 @@
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AutoLogin" "1"
"timestamp" "1787904411"
"Timestamp" "1787904411"
}
"1"
{
@@ -18,6 +18,6 @@
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AutoLogin" "1"
"timestamp" "1787904647"
"Timestamp" "1787904647"
}
}
+2 -2
View File
@@ -8,7 +8,7 @@
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AutoLogin" "1"
"timestamp" "1797904411"
"Timestamp" "1797904411"
}
"1"
{
@@ -18,6 +18,6 @@
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AutoLogin" "1"
"timestamp" "1787904647"
"Timestamp" "1787904647"
}
}
+1 -1
View File
@@ -9,6 +9,6 @@
"SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1"
"MostRecent" "1"
"timestamp" "0"
"Timestamp" "0"
}
}
+2 -2
View File
@@ -9,7 +9,7 @@
"SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1"
"MostRecent" "0"
"timestamp" "0"
"Timestamp" "0"
}
"1"
{
@@ -20,6 +20,6 @@
"SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1"
"MostRecent" "1"
"timestamp" "10"
"Timestamp" "10"
}
}
+4 -3
View File
@@ -10,7 +10,7 @@ gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, GLib # noqa
class DummyWidget(ColorAwareApp, Gtk.Button):
class TestWidget(ColorAwareApp, Gtk.Button):
def __init__(self) -> None:
super().__init__()
@@ -22,10 +22,11 @@ class DummyWidget(ColorAwareApp, Gtk.Button):
@pytest.fixture
def widget() -> DummyWidget:
return DummyWidget()
def widget() -> TestWidget:
return TestWidget()
@pytest.mark.integration
@pytest.mark.parametrize("key, expect", [("default", False), ("prefer-dark", True)])
def test_light_mode(widget, key: str, expect: bool) -> None:
local_pref = subprocess.run(
-9
View File
@@ -3,7 +3,6 @@ import pytest
from pathlib import Path
from dzgui.api.steam import find_user_id
from dzgui.const.constants import DEFAULT_STEAM_PATH
from tests.fixtures import fixture_path
pytestmark = pytest.mark.apitest
@@ -26,11 +25,3 @@ def test_loginusers(monkeypatch, fixture: str, expect: int) -> None:
monkeypatch.setattr("dzgui.api.steam.find_loginusers", mock_loginusers)
uid = find_user_id(Path(""))
assert int(uid) == expect
@pytest.mark.realconfig
def test_live_loginusers() -> None:
"""Added on 2026-10-17 following changes to VDF keys"""
steam = Path.home().joinpath(DEFAULT_STEAM_PATH)
uid = find_user_id(steam)
assert uid is not None
-27
View File
@@ -1,27 +0,0 @@
import pytest
from pathlib import Path
from dzgui.api.steam import is_dayz_downloading
@pytest.mark.parametrize(
"file_list, expect",
[
(["state_221100_1111.patch"], True),
(["state_730_1111.patch"], False),
(["state_1024020_1111.patch"], True),
(["221100"], False),
(["221100", "state_221100_1111.patch"], True),
(["state_221100_1111.patch", "state_1024020_1111.patch"], True),
(["state_221100_1111"], False),
],
)
def test_dayz_patch_pending(monkeypatch, file_list, expect) -> None:
def mock_get_pending_downloads(steam_path: Path, appid: int) -> None:
files = [Path(file) for file in file_list]
return files
monkeypatch.setattr(
"dzgui.api.steam.get_pending_downloads", mock_get_pending_downloads
)
assert is_dayz_downloading(Path("/tmp"), Path("/tmp")) == expect
-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)])