Compare commits

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

No files matched your search

-1
View File
@@ -52,7 +52,6 @@
- Filter button tooltips - Filter button tooltips
- Expand/collapse changelog versions - Expand/collapse changelog versions
- Support local light/dark mode changes on system theme - Support local light/dark mode changes on system theme
- Server time calculator
### Changed ### Changed
- Conform to PEP 440 versioning for beta versions - Conform to PEP 440 versioning for beta versions
+8 -20
View File
@@ -255,28 +255,16 @@ class PeFileError(Exception):
pass pass
def parse_version_number(data: BinaryIO) -> FileVersion: def parse_version_number(data: BinaryIO) -> FileVersion:
# https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo # https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
""" minor = struct.unpack("<L", data.read(4))[0] >> 16 & 0xFFFF
Note: structure differs from standard VS_FIXEDFILEINFO major = struct.unpack("<L", data.read(4))[0] >> 0 & 0xFFFF
Major: lower 16 bits of first DWORD build = struct.unpack("<L", data.read(4))[0] >> 0 & 0xFFFF
Minor: upper 16 bits of first DWORD # NOTE: 0-pad revision number
Build: lower 16 bits of third DWORD revision = str(struct.unpack("<L", data.read(4))[0] >> 16 & 0xFFFF)
Revision: upper 16 bits of second DWORD, zero-padded offset = 3 - len(revision)
Lower 16 bits of revision DWORD appear unused. padded = "0" * offset + revision
""" return FileVersion(major, minor, build, padded)
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)
def seek_to_hex(address: str, data: BinaryIO) -> None: 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 dlc = strings.dlc_none
case 2: case 2:
dlc = strings.dlc_frostline dlc = strings.dlc_frostline
case 4: case 3:
dlc = strings.dlc_badlands dlc = strings.dlc_badlands
case _: case _:
dlc = strings.unspecified dlc = strings.unspecified
+3 -10
View File
@@ -10,8 +10,8 @@ from pathlib import Path
from typing import Any from typing import Any
from dzgui.api.steam import find_user_id_32 from dzgui.api.steam import find_user_id_32
from dzgui.const.constants import APP_NAME, APP_NAME_LOWER, GTK_APP_ID, IMAGES_PATH from dzgui.const.constants import APP_NAME, APP_NAME_LOWER, IMAGES_PATH
from dzgui.util.dirs import copy_dzgui_to_xdg_data, make_parents from dzgui.util.dirs import copy_dzgui_to_xdg_data
from dzgui.util.strings import unknown from dzgui.util.strings import unknown
logger = logging.getLogger(APP_NAME) logger = logging.getLogger(APP_NAME)
@@ -75,8 +75,6 @@ class Shortcuts:
shortcuts = vdf.binary_load(f) shortcuts = vdf.binary_load(f)
self.shortcuts = shortcuts self.shortcuts = shortcuts
logger.debug("Loaded shortcuts file") logger.debug("Loaded shortcuts file")
except FileNotFoundError:
self.shortcuts = {"shortcuts": {}}
except Exception as e: except Exception as e:
logger.critical(e) logger.critical(e)
raise e raise e
@@ -111,7 +109,6 @@ class Shortcuts:
for img in ("_hero", "p", "_logo"): for img in ("_hero", "p", "_logo"):
filename = str(self.bpid) + img filename = str(self.bpid) + img
dest = grid_path.joinpath(filename).with_suffix(".png") dest = grid_path.joinpath(filename).with_suffix(".png")
make_parents(dest)
b = images.joinpath(img).with_suffix(".png").read_bytes() b = images.joinpath(img).with_suffix(".png").read_bytes()
dest.write_bytes(b) dest.write_bytes(b)
@@ -158,8 +155,7 @@ class Shortcuts:
NEW_ENTRY["DevkitGameID"] = "" NEW_ENTRY["DevkitGameID"] = ""
NEW_ENTRY["DevkitOverrideAppID"] = 0 NEW_ENTRY["DevkitOverrideAppID"] = 0
NEW_ENTRY["LastPlayTime"] = 0 NEW_ENTRY["LastPlayTime"] = 0
NEW_ENTRY["FlatpakAppID"] = GTK_APP_ID NEW_ENTRY["FlatpakAppID"] = ""
NEW_ENTRY["sortas"] = "" NEW_ENTRY["sortas"] = ""
NEW_ENTRY["tags"] = {} NEW_ENTRY["tags"] = {}
return NEW_ENTRY return NEW_ENTRY
@@ -176,9 +172,6 @@ class Shortcuts:
try: try:
backup = self.shortcuts_path.with_suffix(".vdf.bak") backup = self.shortcuts_path.with_suffix(".vdf.bak")
shutil.copy(self.shortcuts_path, backup) shutil.copy(self.shortcuts_path, backup)
except Exception as e:
logger.critical(e)
try:
with open(self.shortcuts_path, "wb") as f: with open(self.shortcuts_path, "wb") as f:
vdf.binary_dump(self.shortcuts, f) vdf.binary_dump(self.shortcuts, f)
except Exception as e: except Exception as e:
+5 -46
View File
@@ -1,18 +1,16 @@
import logging import logging
import os import os
import psutil import psutil
import re
import requests import requests
import subprocess import subprocess
import vdf # type: ignore import vdf # type: ignore
from pathlib import Path from pathlib import Path
from requests.exceptions import HTTPError from typing import Any, Union
from typing import Any, Generator, Union
from warnings import deprecated from warnings import deprecated
from dzgui.api.acf import ACF from dzgui.api.acf import ACF
from dzgui.init.prereqs import has_any_client from dzgui.init.prereqs import has_steam_client
from dzgui.const.constants import ( from dzgui.const.constants import (
APPID_DAYZ, APPID_DAYZ,
APPID_DAYZ_EXP, APPID_DAYZ_EXP,
@@ -21,7 +19,6 @@ from dzgui.const.constants import (
DAYZ_LAUNCHER, DAYZ_LAUNCHER,
DEBIAN_STEAM_PATH, DEBIAN_STEAM_PATH,
DEFAULT_STEAM_PATH, DEFAULT_STEAM_PATH,
DOWNLOADS_PATH,
FLATPAK_STEAM_PATH, FLATPAK_STEAM_PATH,
LIBRARYFOLDERS_PATH, LIBRARYFOLDERS_PATH,
UBUNTU_STEAM_PATH, UBUNTU_STEAM_PATH,
@@ -35,7 +32,6 @@ from dzgui.const.endpoints import (
UNSUB_ENDPOINT, UNSUB_ENDPOINT,
) )
from dzgui.strings import wizard from dzgui.strings import wizard
from dzgui.strings.errors import api_validation_error, workshop_endpoint_error
from dzgui.util.bash import concat_bash_args from dzgui.util.bash import concat_bash_args
logger = logging.getLogger(APP_NAME) logger = logging.getLogger(APP_NAME)
@@ -61,7 +57,7 @@ class VDFLoadError(Exception):
def get_steam_paths() -> list[tuple[Path, str]]: def get_steam_paths() -> list[tuple[Path, str]]:
paths = [] paths = []
if has_any_client(): if has_steam_client():
HOME = Path.home() HOME = Path.home()
env = os.environ.get("XDG_DATA_HOME") env = os.environ.get("XDG_DATA_HOME")
if env is not None: if env is not None:
@@ -247,9 +243,9 @@ def find_user_id(path: Path) -> str | None:
last_user = str(next(iter(users))) last_user = str(next(iter(users)))
if len(users) == 1: if len(users) == 1:
return last_user return last_user
last_stamp = int(users[last_user]["timestamp"]) last_stamp = int(users[last_user]["Timestamp"])
for user in users: for user in users:
stamp = int(users[user]["timestamp"]) stamp = int(users[user]["Timestamp"])
if stamp > last_stamp: if stamp > last_stamp:
last_stamp = stamp last_stamp = stamp
last_user = user last_user = user
@@ -266,16 +262,6 @@ def find_user_id_32(path: Path) -> int:
return int(uid) & 0xFFFFFFFF return int(uid) & 0xFFFFFFFF
class SteamAPIKeyError(Exception):
def __init__(self, msg: str | None = None) -> None:
super().__init__(msg or api_validation_error)
class SteamWorkshopEndpointError(Exception):
def __init__(self, msg: str | None = None) -> None:
super().__init__(msg or workshop_endpoint_error)
def update_workshop(key: str, mod: int, endpoint: str) -> None: def update_workshop(key: str, mod: int, endpoint: str) -> None:
payload: dict[str, Union[int, str]] = { payload: dict[str, Union[int, str]] = {
"publishedfileid": mod, "publishedfileid": mod,
@@ -287,19 +273,8 @@ def update_workshop(key: str, mod: int, endpoint: str) -> None:
try: try:
res = requests.post(endpoint, params=payload, timeout=REQUEST_TIMEOUT) res = requests.post(endpoint, params=payload, timeout=REQUEST_TIMEOUT)
res.raise_for_status() 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: except Exception as e:
logger.critical(e) logger.critical(e)
raise e
def subscribe(key: str, mod: int) -> None: 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) 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: def get_app_path(folders_path: Path, appid: int) -> Path:
app_path = None app_path = None
+4 -30
View File
@@ -1,13 +1,10 @@
import logging import logging
import os import os
import shutil import shutil
from pathlib import Path
from typing import TYPE_CHECKING 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.const.enum import Preferences
from dzgui.config.query import lookup from dzgui.config.query import lookup
from dzgui.config.userprefs import UserPrefs from dzgui.config.userprefs import UserPrefs
@@ -18,7 +15,7 @@ from dzgui.init.migrate import (
migrate_cols_file, migrate_cols_file,
) )
from dzgui.init.flock import lock_acquire from dzgui.init.flock import lock_acquire
from dzgui.init.prereqs import has_any_client from dzgui.init.prereqs import has_steam_client
from dzgui.strings import boot from dzgui.strings import boot
from dzgui.util.deck import is_steam_deck, is_game_mode from dzgui.util.deck import is_steam_deck, is_game_mode
@@ -26,7 +23,7 @@ from dzgui.util.dirs import make_parents
from dzgui.util.localize import set_locale from dzgui.util.localize import set_locale
from dzgui.util.map_count import test_map_count from dzgui.util.map_count import test_map_count
from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS 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.base import App
from dzgui.views.dialogs.boot import BootWindow 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) 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: def load_gui(version: str, is_debug: bool) -> None:
lock = lock_acquire() # noqa lock = lock_acquire() # noqa
@@ -161,14 +136,13 @@ def load_gui(version: str, is_debug: bool) -> None:
# TODO: sudo escalation dialog # TODO: sudo escalation dialog
# count = get_map_count() # count = get_map_count()
# TODO: move into module # TODO: move into module
if has_any_client() is False: if has_steam_client() is False:
EarlyAlertDialog(init.requires_steam) EarlyAlertDialog(init.requires_steam)
bootwin = BootWindow(XDG, version) bootwin = BootWindow(XDG, version)
local_coords, latest_release = bootwin.get_results() local_coords, latest_release = bootwin.get_results()
use_miles = lookup(XDG.config, Preferences.DIST) use_miles = lookup(XDG.config, Preferences.DIST)
prefs = UserPrefs( prefs = UserPrefs(
is_steam_deck=_is_steam_deck, is_steam_deck=_is_steam_deck,
is_game_mode=_is_game_mode, is_game_mode=_is_game_mode,
+20
View File
@@ -4,6 +4,8 @@ from pathlib import Path
from dzgui.util._json import read_json from dzgui.util._json import read_json
from dzgui.const.enum import Preferences from dzgui.const.enum import Preferences
from dzgui.const.constants import STEAM_CMD, FLATPAK_RUN_CMD, FLATPAK_SANDBOX
class ConfigFileError(Exception): class ConfigFileError(Exception):
pass pass
@@ -38,5 +40,23 @@ def get_favorites(path: Path) -> list[str]:
return list(conf["ip_list"]) return list(conf["ip_list"])
# def is_in_favs(record: str, path: Path) -> bool:
# favs = get_favorites(path)
# if record in favs:
# return True
# return False
def enum_to_key(enum: Preferences) -> str: def enum_to_key(enum: Preferences) -> str:
return str(enum.dict["key"]) return str(enum.dict["key"])
def get_client_index(client: str) -> int:
if client == STEAM_CMD:
return 0
elif client == FLATPAK_RUN_CMD:
return 1
elif client == FLATPAK_SANDBOX:
return 2
else:
return 0
+2 -5
View File
@@ -15,7 +15,6 @@ DAYZ_BINARY = "DayZ_x64.exe"
DAYZ_LAUNCHER = "DayZLauncher.exe" DAYZ_LAUNCHER = "DayZLauncher.exe"
LIBRARYFOLDERS_PATH = "steamapps/libraryfolders.vdf" LIBRARYFOLDERS_PATH = "steamapps/libraryfolders.vdf"
DOWNLOADS_PATH = "steamapps/downloading"
WORKSHOP_PATH = "steamapps/workshop/content/" + str(APPID_DAYZ) WORKSHOP_PATH = "steamapps/workshop/content/" + str(APPID_DAYZ)
STEAM_CMD = "steam" STEAM_CMD = "steam"
@@ -37,7 +36,7 @@ HEX_GREEN = "#32CD32"
HEX_RED = "#FF0000" HEX_RED = "#FF0000"
HEX_ORANGE = "#FFAC1C" HEX_ORANGE = "#FFAC1C"
# TODO: normalize icon names # FIXME: normalize icon names
CALCULATOR = "accessories-calculator-symbolic" CALCULATOR = "accessories-calculator-symbolic"
CARET_DOWN = "go-down-symbolic" CARET_DOWN = "go-down-symbolic"
CARET_UP = "go-up-symbolic" CARET_UP = "go-up-symbolic"
@@ -85,11 +84,9 @@ HERO_PATH = "data/images/_hero.png"
CSS_PATH = "data/app.css" CSS_PATH = "data/app.css"
VDF_PATH = "steamapps/libraryfolders.vdf" VDF_PATH = "steamapps/libraryfolders.vdf"
DEFAULT_STEAM_PATH = ".local/share/Steam" DEFAULT_STEAM_PATH = ".local/share/Steam"
FLATPAK_ROOT_PATH = ".var/app/com.valvesoftware.Steam" FLATPAK_STEAM_PATH = ".var/app/com.valvesoftware.Steam/data/Steam"
FLATPAK_STEAM_PATH = FLATPAK_ROOT_PATH + "/data/Steam"
UBUNTU_STEAM_PATH = ".steam/steam" UBUNTU_STEAM_PATH = ".steam/steam"
DEBIAN_STEAM_PATH = ".steam/debian-installation" DEBIAN_STEAM_PATH = ".steam/debian-installation"
STEAM_ICONS_PATH = "public"
LOG_FILTERS = ("CRITICAL", "WARNING", "INFO", "DEBUG") LOG_FILTERS = ("CRITICAL", "WARNING", "INFO", "DEBUG")
+2 -4
View File
@@ -541,10 +541,8 @@ class Controller(GObject.GObject):
def update_status(self) -> None: def update_status(self) -> None:
self.mediator.preconnect.mark_finished() self.mediator.preconnect.mark_finished()
def get_client_name(self) -> str: def get_steam_client_name(self) -> str:
# NOTE: only possible values are "Steam" and "Flatpak" return self.mediator.options.get_client_name()
cmd = self.query_config(Preferences.CLIENT)
return str(cmd.split()[0].capitalize())
def present_window(self) -> None: def present_window(self) -> None:
self.mediator.window.present_with_time(Gdk.CURRENT_TIME) self.mediator.window.present_with_time(Gdk.CURRENT_TIME)
-1
View File
@@ -52,7 +52,6 @@
- Filter button tooltips - Filter button tooltips
- Expand/collapse changelog versions - Expand/collapse changelog versions
- Support local light/dark mode changes on system theme - Support local light/dark mode changes on system theme
- Server time calculator
### Changed ### Changed
- Conform to PEP 440 versioning for beta versions - 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.api.steam import get_app_path
from dzgui.config.query import lookup 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.const.enum import Preferences
from dzgui.strings.preconnect import resync
logger = logging.getLogger(APP_NAME) logger = logging.getLogger(APP_NAME)
class VDFSyncError(Exception):
pass
def is_dayz_installed(config: Path) -> None: def is_dayz_installed(config: Path) -> None:
try: try:
path = lookup(config, Preferences.DEFAULT) path = lookup(config, Preferences.DEFAULT)
get_app_path(Path(path), APPID_DAYZ) get_app_path(Path(path), APPID_DAYZ)
except Exception as e: except Exception as e:
logger.critical(e) logger.critical(e)
msg = resync.format(APPNAME_DAYZ) raise e
raise VDFSyncError(msg)
+7 -20
View File
@@ -7,32 +7,19 @@ from dzgui.const.constants import APP_NAME, FLATPAK_APPID, FLATPAK_CMD, STEAM_CM
logger = logging.getLogger(APP_NAME) logger = logging.getLogger(APP_NAME)
def has_flatpak_client() -> bool: def has_steam_client() -> bool:
if shutil.which(STEAM_CMD):
return True
if not shutil.which(FLATPAK_CMD):
return False
try: try:
res = subprocess.run( res = subprocess.run(
[FLATPAK_CMD, "list", "--columns=application"], [FLATPAK_CMD, "list"], capture_output=True, text=True, check=True
capture_output=True,
text=True,
check=True,
) )
if FLATPAK_APPID in res.stdout: if FLATPAK_APPID in res.stdout:
return True return True
else: else:
return False return False
except Exception as e: except subprocess.CalledProcessError as e:
logger.critical(e) logger.critical(e)
return False 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()
+13 -32
View File
@@ -10,61 +10,42 @@ from dzgui.const.constants import (
FLATPAK_CMD, FLATPAK_CMD,
FLATPAK_RUN_CMD, FLATPAK_RUN_CMD,
FLATPAK_SANDBOX, FLATPAK_SANDBOX,
FLATPAK_ROOT_PATH,
FLATPAK_STEAM_PATH,
) )
logger = logging.getLogger(APP_NAME) logger = logging.getLogger(APP_NAME)
def is_steam_running(cmd: str) -> bool: def is_steam_running(cmd: str) -> bool:
if cmd in (FLATPAK_RUN_CMD, FLATPAK_SANDBOX): 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() return is_flatpak_steam_running()
elif cmd == STEAM_CMD:
return is_native_steam_running()
else: else:
raise TypeError("Not a valid Steam client selection") raise TypeError("Not a valid Steam client selection")
# CHORE: test alternate clients
# TODO: has_flatpak_steam
def is_flatpak_steam_running() -> bool: def is_flatpak_steam_running() -> bool:
if has_cmd(FLATPAK_CMD) is False: if has_cmd(FLATPAK_CMD) is False:
return False return False
proc = subprocess.check_output( proc = subprocess.check_output([FLATPAK_CMD, "ps"], text=True)
[FLATPAK_CMD, "ps", "--columns=application"], text=True
)
lines = proc.splitlines() lines = proc.splitlines()
if FLATPAK_APPID in lines: if FLATPAK_APPID in lines:
return True return True
return False return False
def get_steam_procs() -> list[list[str]]: def is_running(proc: str) -> bool:
return [ for process in psutil.process_iter(["pid", "name"]):
p.cmdline() if process.info["name"] == proc:
for p in psutil.process_iter(["pid", "name"]) return True
if STEAM_CMD in p.info["name"] return False
]
def is_native_steam_running() -> bool:
if has_cmd(STEAM_CMD) is False:
return False
substrings = [FLATPAK_ROOT_PATH, "steam-runtime-launcher-service"]
steam_procs = get_steam_procs()
native_steam_procs = [
proc
for proc in steam_procs
if not any(sub in item for item in proc for sub in substrings)
]
return bool(native_steam_procs)
def has_cmd(cmd: str) -> bool: def has_cmd(cmd: str) -> bool:
if shutil.which(cmd) is not None: if shutil.which(cmd) is not None:
return True return True
return False return False
def path_to_steam_cmd(path: str) -> str:
cmd = FLATPAK_RUN_CMD if FLATPAK_STEAM_PATH in path else STEAM_CMD
return cmd
+12
View File
@@ -6,6 +6,9 @@ from typing import Any, TYPE_CHECKING
from dzgui.const.constants import ( from dzgui.const.constants import (
APP_NAME, APP_NAME,
FLATPAK_RUN_CMD,
FLATPAK_SANDBOX,
STEAM_CMD,
WINDOW_DEFAULT_X, WINDOW_DEFAULT_X,
WINDOW_DEFAULT_Y, WINDOW_DEFAULT_Y,
) )
@@ -126,6 +129,15 @@ class ConfigManager:
def enum_to_key(self, enum: Preferences) -> str: def enum_to_key(self, enum: Preferences) -> str:
return str(enum.dict["key"]) return str(enum.dict["key"])
def get_client_index(self, client: str) -> int:
if client == STEAM_CMD:
return 0
if client == FLATPAK_RUN_CMD:
return 1
if client == FLATPAK_SANDBOX:
return 2
return 0
def toggle_config(self, key: Preferences) -> None: def toggle_config(self, key: Preferences) -> None:
try: try:
real_key = self.enum_to_key(key) real_key = self.enum_to_key(key)
+4 -19
View File
@@ -14,7 +14,6 @@ from dzgui.api.shortcuts import Shortcuts
from dzgui.api.steam import ( from dzgui.api.steam import (
connect, connect,
connect_debug, connect_debug,
is_dayz_downloading,
get_app_allows_downloads, get_app_allows_downloads,
get_app_name, get_app_name,
get_needs_update, get_needs_update,
@@ -49,7 +48,7 @@ from dzgui.strings.dialogs import (
) )
from dzgui.strings import kb from dzgui.strings import kb
from dzgui.strings.server_mods import checkmark, resync 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.strings import dialog, server_timeout
from dzgui.util.symlink import rebuild_symlinks from dzgui.util.symlink import rebuild_symlinks
from dzgui.views.dialogs.generic import ExceptionDialog from dzgui.views.dialogs.generic import ExceptionDialog
@@ -85,7 +84,6 @@ class Prerequisites:
available_space: float available_space: float
passworded: bool passworded: bool
dayz_running: bool dayz_running: bool
download_paused: bool
steam_proc: SteamProcess steam_proc: SteamProcess
mods: list[list[str]] mods: list[list[str]]
game_mode: bool game_mode: bool
@@ -171,7 +169,6 @@ class ConnectionManager:
free_mib = format_mib(free) free_mib = format_mib(free)
dayz_running = is_dayz_running() dayz_running = is_dayz_running()
download_paused = is_dayz_downloading(steam_path, self.appid)
running_app = get_running_app() running_app = get_running_app()
if running_app is not None: if running_app is not None:
@@ -187,8 +184,8 @@ class ConnectionManager:
else: else:
allows_downloads = (True, "") allows_downloads = (True, "")
client_name = self.controller.get_steam_client_name()
client = self.controller.query_config(Preferences.CLIENT) client = self.controller.query_config(Preferences.CLIENT)
client_name = self.controller.get_client_name()
running = is_steam_running(client) running = is_steam_running(client)
steam_proc = SteamProcess(client_name, running) steam_proc = SteamProcess(client_name, running)
self.client = client self.client = client
@@ -213,7 +210,6 @@ class ConnectionManager:
available_space=free_mib, available_space=free_mib,
passworded=info.password_protected, passworded=info.password_protected,
dayz_running=dayz_running, dayz_running=dayz_running,
download_paused=download_paused,
steam_proc=steam_proc, steam_proc=steam_proc,
mods=remote_mods, mods=remote_mods,
game_mode=game_mode, game_mode=game_mode,
@@ -304,11 +300,6 @@ class ConnectionManager:
dialog = ServerDetailsDialog(self.controller, details) dialog = ServerDetailsDialog(self.controller, details)
dialog.run() dialog.run()
def _workshop_timeout(self, msg: str) -> None:
dialog = ExceptionDialog(self.controller, msg)
dialog.show_all()
dialog.run()
def _server_timeout(self) -> None: def _server_timeout(self) -> None:
dialog = ExceptionDialog(self.controller, server_timeout) dialog = ExceptionDialog(self.controller, server_timeout)
dialog.set_details_buffer(kb.DZG_006) dialog.set_details_buffer(kb.DZG_006)
@@ -360,14 +351,8 @@ class ConnectionManager:
for title, mod, stamp, size in self.missing_mods: for title, mod, stamp, size in self.missing_mods:
if self.controller.is_cancel_pending(): if self.controller.is_cancel_pending():
return return
try: subscribe(key, int(mod))
subscribe(key, int(mod)) time.sleep(API_RATE_LIMIT)
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
self.thread_man.update_dialog(waiting_for_directories) self.thread_man.update_dialog(waiting_for_directories)
for title, mod, stamp, size in self.missing_mods: 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_night: True,
strings.filter_full: show_full, strings.filter_full: show_full,
strings.filter_official: True, strings.filter_official: True,
strings.filter_modded: True, strings.filter_nonascii: False,
strings.filter_lowpop: True, strings.filter_lowpop: True,
strings.filter_unofficial: True, strings.filter_unofficial: True,
strings.filter_unmodded: True,
strings.filter_duplicate: False, strings.filter_duplicate: False,
strings.filter_nonascii: False, strings.filter_modded: True,
} }
# TODO: strings # TODO: strings
@@ -44,7 +43,6 @@ class FilterManager:
strings.filter_unofficial: "Third-party servers", strings.filter_unofficial: "Third-party servers",
strings.filter_duplicate: "Duplicates of existing servers (usually spoofed)", strings.filter_duplicate: "Duplicates of existing servers (usually spoofed)",
strings.filter_modded: "Servers with one or more mods", strings.filter_modded: "Servers with one or more mods",
strings.filter_unmodded: "Servers with no mods",
} }
self.active_keyword = "" self.active_keyword = ""
+1 -5
View File
@@ -122,11 +122,7 @@ class ModManager:
def unsub_atomic_mod(self, mod: str) -> None: def unsub_atomic_mod(self, mod: str) -> None:
config_man = self.controller.get_config_man() config_man = self.controller.get_config_man()
key = config_man.lookup(Preferences.STEAM) key = config_man.lookup(Preferences.STEAM)
try: unsubscribe(key, int(mod))
unsubscribe(key, int(mod))
except Exception as e:
logger.critical(e)
return
steam_path = Path(self.path) steam_path = Path(self.path)
app_path = PeFile.get_nested_app_path(steam_path, APPID_DAYZ) 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] rows = [row for row in rows if (row[4] / row[5] * 100) > 30]
case strings.filter_modded: case strings.filter_modded:
rows = [row for row in rows if not row[11]] 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 return rows
def filter_toggle_on(self, filters: tuple[str]) -> list: 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_popover = "This address is already in your Saved Servers"
add_tooltip = "Add to Saved Servers" add_tooltip="Add to Saved Servers"
connect_button = "Connect" connect_button="Connect"
connect_tooltip = "Connect to this server" connect_tooltip="Connect to this server"
connect_entry_placeholder = f"Enter IP:Query port (e.g., 192.168.1.1:{UDP_PORT})" connect_entry_placeholder="Enter IP:Query port (e.g., 192.168.1.1:27016)"
fav_heading = "Favorite server" fav_heading="Favorite server"
favs_empty = "None set. Right click a server and select 'Set favorite' to set." favs_empty="None set. Right click a server and select 'Set favorite' to set."
lan_heading = "LAN query port" lan_heading="LAN query port"
lan_default_button = f"Default port ({UDP_PORT})" lan_default_button="Default port (27016)"
lan_custom_button = "Custom port" lan_custom_button="Custom port"
lan_scan_button = "Scan" lan_scan_button="Scan"
lan_abort_tooltip = "Unless you have multiple DayZ servers on your LAN,\nleave this checked to get results faster" 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_checkbox = "Stop scanning on first hit"
lan_entry_placeholder = "Enter the query port (1-65535)" 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_entry_tooltip="Specify the port to search for DayZ servers on the local network"
lan_scan_tooltip = "Scan for servers" lan_scan_tooltip="Scan for servers"
-1
View File
@@ -4,4 +4,3 @@ api_validation_error = (
api_popover = "API key validation failed." api_popover = "API key validation failed."
dayz_not_found = "Could not find a valid DayZ installation." 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: " total_mods = "Total mods: "
up_to_date = "All mods are up to date." up_to_date = "All mods are up to date."
all_updated = "All mods updated." 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." required_space = " Need to download {0} MiB of mod updates."
placeholder_no_mods = "This server has no mods." 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." 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_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." version_missing = "Remote server is running the build '{0}', but it is not installed."
no_errors = "No errors."
# Warnings # 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" "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." "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." not_installed = "It looks like DZGUI was not previously set up; nothing to uninstall."
standalone_uninstall = ( standalone_uninstall = (
"It looks like you are running the standalone version of DZGUI.\n" "It looks like you are running the standalone version of DZGUI.\n"
"To finalize the uninstall process, invoke './dzgui self remove',\n" "To finalize the uninstall process, you can remove the 'dzgui' file."
"then remove the 'dzgui' file."
) )
system_uninstall = ( system_uninstall = (
"It looks like DZGUI was installed via your system package manager or directly from source code.\n" "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_official = "Official"
filter_unofficial = "Unofficial" filter_unofficial = "Unofficial"
filter_modded = "Modded" filter_modded = "Modded"
filter_unmodded = "Unmodded"
# maps # maps
all_maps = "All maps" all_maps = "All maps"
+5 -10
View File
@@ -2,12 +2,7 @@ import logging
from pathlib import Path from pathlib import Path
from dzgui.api.mods import get_local_mod_path, get_local_mod_ids, _hash from dzgui.api.mods import get_local_mod_path, get_local_mod_ids, _hash
from dzgui.const.constants import ( from dzgui.const.constants import APPID_DAYZ, APPID_DAYZ_EXP, APP_NAME, DAYZ_COMMUNITY_ROOT
APPID_DAYZ,
APPID_DAYZ_EXP,
APP_NAME,
DAYZ_COMMUNITY_ROOT,
)
from dzgui.const.enum import Preferences from dzgui.const.enum import Preferences
from dzgui.config.query import lookup from dzgui.config.query import lookup
@@ -25,7 +20,6 @@ def expunge_link(file: Path) -> None:
if str(file.stem)[:2] == "@C": if str(file.stem)[:2] == "@C":
file.unlink() file.unlink()
def rebuild_symlinks(config: Path) -> None: def rebuild_symlinks(config: Path) -> None:
# TODO: pass direct path as argument # TODO: pass direct path as argument
path = lookup(config, Preferences.DEFAULT) path = lookup(config, Preferences.DEFAULT)
@@ -69,9 +63,10 @@ def symlink_mission(steam_path: Path, target: str) -> str:
stem = path.name stem = path.name
parent = path.parent parent = path.parent
source = dayz_path.joinpath(DAYZ_COMMUNITY_ROOT) source = dayz_path.joinpath(DAYZ_COMMUNITY_ROOT)
if source.exists(): try:
source.unlink() source.symlink_to(parent)
source.symlink_to(parent) except Exception:
return ""
return f"{DAYZ_COMMUNITY_ROOT}/{stem}" return f"{DAYZ_COMMUNITY_ROOT}/{stem}"
+18 -1
View File
@@ -1,5 +1,6 @@
import logging import logging
import signal import signal
import warnings
from typing import TYPE_CHECKING, Literal, Self from typing import TYPE_CHECKING, Literal, Self
@@ -7,11 +8,12 @@ from dzgui.const.constants import (
APP_NAME, APP_NAME,
APP_NAME_LOWER, APP_NAME_LOWER,
GTK_APP_ID, GTK_APP_ID,
STEAM_ICON,
) )
from dzgui.const.enum import NotebookPage from dzgui.const.enum import NotebookPage
from dzgui.controllers.emitter import Emitter from dzgui.controllers.emitter import Emitter
from dzgui.controllers.mc import Controller 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.util.keys import is_ctrl_mask
from dzgui.views.components.connect_panel import ConnectPanel from dzgui.views.components.connect_panel import ConnectPanel
from dzgui.views.components.crumbs import Breadcrumbs from dzgui.views.components.crumbs import Breadcrumbs
@@ -45,6 +47,10 @@ if TYPE_CHECKING:
logger = logging.getLogger(APP_NAME) 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): class OuterWindow(Gtk.ApplicationWindow):
def __init__(self, app: Gtk.Application) -> None: def __init__(self, app: Gtk.Application) -> None:
@@ -331,4 +337,15 @@ class App(ColorAwareApp, Gtk.Application): # type: ignore
) -> None: ) -> None:
self.win.halt_proc_and_quit() self.win.halt_proc_and_quit()
MainController = Controller() 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(self.icon)
self.set_image_position(position) self.set_image_position(position)
self.set_focus_on_click(False) self.set_focus_on_click(False)
self.set_always_show_image(True)
def swap_icon(self, icon: str) -> None: def swap_icon(self, icon: str) -> None:
start = self.icon.get_margin_start() start = self.icon.get_margin_start()
+4 -34
View File
@@ -1,72 +1,42 @@
from typing import Self
from warnings import deprecated
from dzgui.const.constants import ERROR, FLATPAK_RUN_CMD, FLATPAK_SANDBOX, STEAM_CMD from dzgui.const.constants import ERROR, FLATPAK_RUN_CMD, FLATPAK_SANDBOX, STEAM_CMD
from dzgui.init.prereqs import has_steam_client, has_flatpak_client
from dzgui.strings import options from dzgui.strings import options
from dzgui.views.components.box import HBox from dzgui.views.components.box import HBox
from dzgui.views.components.buttons import Icon from dzgui.views.components.buttons import Icon
import gi import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa from gi.repository import Gtk # noqa
@deprecated("Client choice is determined at setup time")
class ClientCombo(Gtk.ComboBox): class ClientCombo(Gtk.ComboBox):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
has_steam = has_steam_client() self.client_store = Gtk.ListStore(str, str)
has_flatpak = has_flatpak_client() clients = (
self.client_store = Gtk.ListStore(str, str, bool)
self.clients = (
( (
options.steam_combo, options.steam_combo,
STEAM_CMD, STEAM_CMD,
has_steam,
), ),
( (
options.flatpak_combo, options.flatpak_combo,
FLATPAK_RUN_CMD, FLATPAK_RUN_CMD,
has_flatpak,
), ),
( (
options.flatpak_container_combo, options.flatpak_container_combo,
FLATPAK_SANDBOX, FLATPAK_SANDBOX,
has_flatpak,
), ),
) )
for client in self.clients: for client in clients:
self.client_store.append(client) self.client_store.append(client)
self.set_model(self.client_store) self.set_model(self.client_store)
renderer_text = Gtk.CellRendererText() renderer_text = Gtk.CellRendererText()
self.pack_start(renderer_text, True) self.pack_start(renderer_text, True)
self.add_attribute(renderer_text, "text", 0) self.add_attribute(renderer_text, "text", 0)
self.set_active(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): class ErrorPopover(Gtk.Popover):
def __init__(self, relative_to: Gtk.Widget, position: Gtk.PositionType) -> None: def __init__(self, relative_to: Gtk.Widget, position: Gtk.PositionType) -> None:
super().__init__(relative_to=relative_to, position=position) 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_top=30,
margin_bottom=30, margin_bottom=30,
) )
self.error_label = Gtk.Label(margin_start=15, margin_end=15) self.error_label = Gtk.Label()
# TODO: abstract class # TODO: abstract class
self.copy_button = ClipboardButton(None, lambda: self.error_label.get_text()) self.copy_button = ClipboardButton(None, lambda: self.error_label.get_text())
+16 -8
View File
@@ -17,7 +17,6 @@ from dzgui.const.constants import (
from dzgui.const.boilerplate import config_boilerplate from dzgui.const.boilerplate import config_boilerplate
from dzgui.const.endpoints import STEAM_API_SETUP from dzgui.const.endpoints import STEAM_API_SETUP
from dzgui.config import freedesktop from dzgui.config import freedesktop
from dzgui.init.proc import path_to_steam_cmd
from dzgui.init.migrate import migrate_legacy_conf from dzgui.init.migrate import migrate_legacy_conf
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
from dzgui.strings import wizard from dzgui.strings import wizard
@@ -26,6 +25,7 @@ from dzgui.util.open_links import open_link_by_url
from dzgui.util.css import add_class, load_css from dzgui.util.css import add_class, load_css
from dzgui.views.components.buttons import WebButton from dzgui.views.components.buttons import WebButton
from dzgui.views.components.entry import APIEntry from dzgui.views.components.entry import APIEntry
from dzgui.views.components.misc import ClientCombo
from dzgui.views.mixins.colorscheme import ColorAwareApp from dzgui.views.mixins.colorscheme import ColorAwareApp
import gi import gi
@@ -384,17 +384,26 @@ class PreferencesPage(EnumeratedWizardPage):
self.dist_box.add(self.radio_km) self.dist_box.add(self.radio_km)
self.dist_box.add(self.radio_miles) self.dist_box.add(self.radio_miles)
client_label = Gtk.Label(label=wizard.label_client, halign=Gtk.Align.START)
self.client_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
self.client_combo = ClientCombo()
self.client_box.add(client_label)
self.client_box.add(self.client_combo)
outer_box = Gtk.Box( outer_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER, spacing=10 orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER, spacing=10
) )
for el in self.name_box, self.dist_box: for el in self.name_box, self.client_box, self.dist_box:
outer_box.add(el) outer_box.add(el)
self.add_start(outer_box) self.add_start(outer_box)
def get_prefs(self) -> tuple[str, bool]: def get_prefs(self) -> tuple[str, bool, str]:
name = self.name_entry.get_text().strip() name = self.name_entry.get_text().strip()
use_miles = self.radio_miles.get_active() use_miles = self.radio_miles.get_active()
return name, use_miles model = self.client_combo.get_model()
ind = self.client_combo.get_active()
client = model[ind][1]
return name, use_miles, client
def _on_entry_changed(self, entry: Gtk.Entry) -> None: def _on_entry_changed(self, entry: Gtk.Entry) -> None:
text = entry.get_text() text = entry.get_text()
@@ -490,16 +499,15 @@ class Assistant(Gtk.Assistant):
self.config_values = self.page_migration.migrated_conf self.config_values = self.page_migration.migrated_conf
return self.get_final_page() return self.get_final_page()
case SteamPathPage(): case SteamPathPage():
path = page.get_path_from_radio() self.config_values["default_steam_path"] = page.get_path_from_radio()
self.config_values["default_steam_path"] = path
self.config_values["client"] = path_to_steam_cmd(path)
case SteamValidationPage(): case SteamValidationPage():
self.config_values["steam_api"] = page.get_api_key() self.config_values["steam_api"] = page.get_api_key()
case PreferencesPage(): case PreferencesPage():
# NOTE: collects config values before advancing to last page # NOTE: collects config values before advancing to last page
name, use_miles = self.page_prefs.get_prefs() name, use_miles, client = self.page_prefs.get_prefs()
self.config_values["name"] = name self.config_values["name"] = name
self.config_values["use_miles"] = use_miles self.config_values["use_miles"] = use_miles
self.config_values["client"] = client
self.page_shortcuts.set_steam_path( self.page_shortcuts.set_steam_path(
self.config_values["default_steam_path"] self.config_values["default_steam_path"]
) )
+32 -3
View File
@@ -25,7 +25,7 @@ from dzgui.views.components.eventbox import InfoEventBox
from dzgui.views.components.labels import LeftLabel from dzgui.views.components.labels import LeftLabel
from dzgui.views.components.buttons import SpinnerButton, WebButton from dzgui.views.components.buttons import SpinnerButton, WebButton
from dzgui.views.components.frame import HeadingFrame from dzgui.views.components.frame import HeadingFrame
from dzgui.views.components.misc import ErrorPopover from dzgui.views.components.misc import ClientCombo, ErrorPopover
from dzgui.views.dialogs.generic import ExceptionDialog from dzgui.views.dialogs.generic import ExceptionDialog
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
@@ -76,7 +76,7 @@ class SubmitField(Gtk.Box):
self.button = Gtk.Button(label=options.save_button) self.button = Gtk.Button(label=options.save_button)
self.button.connect("clicked", self._on_save_clicked) 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.connect("activate", self._on_field_activated)
self.entry.get_property("buffer").connect("deleted-text", self._on_text_deleted) self.entry.get_property("buffer").connect("deleted-text", self._on_text_deleted)
@@ -113,9 +113,13 @@ class SubmitField(Gtk.Box):
def _on_text_typed( def _on_text_typed(
self, self,
entry: Gtk.Entry, entry: Gtk.Entry,
text: str,
length: int,
pos: int,
) -> None: ) -> None:
text = entry.get_text() buffer = entry.get_property("buffer")
text = buffer.get_text() + text
state = self._is_valid_text(text) state = self._is_valid_text(text)
self.button.set_sensitive(state) self.button.set_sensitive(state)
@@ -281,6 +285,11 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
Preferences.WINDOW, Preferences.WINDOW,
) )
self.client_combo = ClientCombo()
self.client_combo.connect("changed", self._on_client_changed)
client_hbox = ShortHBox(self.client_combo)
self.distance_toggle = ToggleField( self.distance_toggle = ToggleField(
self.controller, strings.options.km, strings.options.mi, Preferences.DIST self.controller, strings.options.km, strings.options.mi, Preferences.DIST
) )
@@ -304,6 +313,7 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
self.start_tab_combo.connect("changed", self._on_start_tab_changed) self.start_tab_combo.connect("changed", self._on_start_tab_changed)
pref_rows = [ pref_rows = [
[LeftLabel(strings.options.client), client_hbox],
[LeftLabel(strings.options.window_size), self.fullscreen_toggle], [LeftLabel(strings.options.window_size), self.fullscreen_toggle],
[LeftLabel(strings.options.distance), self.distance_toggle], [LeftLabel(strings.options.distance), self.distance_toggle],
[LeftLabel(options.start_tab), start_tab_hbox], [LeftLabel(options.start_tab), start_tab_hbox],
@@ -361,11 +371,19 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
self.connect("key-press-event", self._on_keypress) 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 block_text_entry(self) -> None: def block_text_entry(self) -> None:
self.steam_box.block_text_entry() self.steam_box.block_text_entry()
# self.steam_entry.set_position(-1)
# self.steam_entry.set_can_focus(False)
def unblock_text_entry(self) -> None: def unblock_text_entry(self) -> None:
self.steam_box.unblock_text_entry() self.steam_box.unblock_text_entry()
# self.steam_entry.set_can_focus(True)
def _on_developers_clicked(self, button: Gtk.Button) -> None: def _on_developers_clicked(self, button: Gtk.Button) -> None:
self.controller.show_developers_page() self.controller.show_developers_page()
@@ -400,6 +418,13 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
index = enum.value index = enum.value
self.controller.update_config(Preferences.START_TAB, index) self.controller.update_config(Preferences.START_TAB, index)
def _on_client_changed(self, combo: Gtk.ComboBoxText) -> None:
_iter = combo.get_active_iter()
if _iter is None:
raise ValueError(f"No active iterator set on {combo}")
real_cmd = combo.get_model()[_iter][1]
self.controller.update_config(Preferences.CLIENT, real_cmd)
def populate_settings(self) -> None: def populate_settings(self) -> None:
prefs = self.controller.get_prefs() prefs = self.controller.get_prefs()
# NOTE: re-check in case file was removed by user between runs # NOTE: re-check in case file was removed by user between runs
@@ -445,6 +470,10 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
self.dayz_version_label.set_text(dayz_version) self.dayz_version_label.set_text(dayz_version)
self.dayz_exp_version_label.set_text(dayz_exp_version) self.dayz_exp_version_label.set_text(dayz_exp_version)
# TODO: bicolumn list store with no cell renderer on index 1, use raw command names
active_combo = query.get_client_index(config["client"])
self.client_combo.set_active(active_combo)
start_tab = self.controller.query_config(Preferences.START_TAB) start_tab = self.controller.query_config(Preferences.START_TAB)
self.start_tab_combo.set_active(start_tab) self.start_tab_combo.set_active(start_tab)
+2 -3
View File
@@ -176,7 +176,8 @@ class PreConnectionAssistant(Gtk.Box):
self.error_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.error_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.error_tree = MaskedTree(ERROR) 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_tree)
self.error_box.add(self.error_placeholder) self.error_box.add(self.error_placeholder)
self.error_frame = HeadingFrame.new_with_widget_and_label( self.error_frame = HeadingFrame.new_with_widget_and_label(
@@ -279,8 +280,6 @@ class PreConnectionAssistant(Gtk.Box):
warnings.append(preconnect.protected_server) warnings.append(preconnect.protected_server)
if prereqs.dayz_running is True: if prereqs.dayz_running is True:
warnings.append(preconnect.dayz_running) 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 allows_dl, running_app = prereqs.allows_downloads
if len(prereqs.mods) > 0 and allows_dl is False: 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: case Gdk.KEY_c:
self.controller.menu_action(ContextMenu.COPY_SERVER_IP, self) self.controller.menu_action(ContextMenu.COPY_SERVER_IP, self)
case Gdk.KEY_r: case Gdk.KEY_r:
# TODO: unimplemented, needs threading
self.controller.menu_action(ContextMenu.REFRESH_PLAYERS, self) self.controller.menu_action(ContextMenu.REFRESH_PLAYERS, self)
else: else:
match event.keyval: match event.keyval:
+1 -1
View File
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
authors = [ authors = [
{name = "aclist"} {name = "aclist"}
] ]
version = "7.0.0b28" version = "7.0.0b23"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
license-files = ["LICENSE"] license-files = ["LICENSE"]
readme = "README.md" readme = "README.md"
+1 -1
View File
@@ -8,6 +8,6 @@
"WantsOfflineMode" "0" "WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0" "SkipOfflineModeWarning" "0"
"AutoLogin" "1" "AutoLogin" "1"
"timestamp" "0" "Timestamp" "0"
} }
} }
+2 -2
View File
@@ -8,7 +8,7 @@
"WantsOfflineMode" "0" "WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0" "SkipOfflineModeWarning" "0"
"AutoLogin" "1" "AutoLogin" "1"
"timestamp" "1787904411" "Timestamp" "1787904411"
} }
"1" "1"
{ {
@@ -18,6 +18,6 @@
"WantsOfflineMode" "0" "WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0" "SkipOfflineModeWarning" "0"
"AutoLogin" "1" "AutoLogin" "1"
"timestamp" "1787904647" "Timestamp" "1787904647"
} }
} }
+2 -2
View File
@@ -8,7 +8,7 @@
"WantsOfflineMode" "0" "WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0" "SkipOfflineModeWarning" "0"
"AutoLogin" "1" "AutoLogin" "1"
"timestamp" "1797904411" "Timestamp" "1797904411"
} }
"1" "1"
{ {
@@ -18,6 +18,6 @@
"WantsOfflineMode" "0" "WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0" "SkipOfflineModeWarning" "0"
"AutoLogin" "1" "AutoLogin" "1"
"timestamp" "1787904647" "Timestamp" "1787904647"
} }
} }
+1 -1
View File
@@ -9,6 +9,6 @@
"SkipOfflineModeWarning" "0" "SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1" "AllowAutoLogin" "1"
"MostRecent" "1" "MostRecent" "1"
"timestamp" "0" "Timestamp" "0"
} }
} }
+2 -2
View File
@@ -9,7 +9,7 @@
"SkipOfflineModeWarning" "0" "SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1" "AllowAutoLogin" "1"
"MostRecent" "0" "MostRecent" "0"
"timestamp" "0" "Timestamp" "0"
} }
"1" "1"
{ {
@@ -20,6 +20,6 @@
"SkipOfflineModeWarning" "0" "SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1" "AllowAutoLogin" "1"
"MostRecent" "1" "MostRecent" "1"
"timestamp" "10" "Timestamp" "10"
} }
} }
-19
View File
@@ -1,19 +0,0 @@
import pytest
from dzgui.const.constants import FLATPAK_RUN_CMD, STEAM_CMD
from dzgui.controllers.mc import Controller
@pytest.mark.parametrize(
"value, expected", [(FLATPAK_RUN_CMD, "Flatpak"), (STEAM_CMD, "Steam")]
)
def test_human_name(monkeypatch, value, expected) -> None:
def mock_query_config(*args) -> str:
return value
monkeypatch.setattr(
"dzgui.controllers.mc.Controller.query_config", mock_query_config
)
controller = Controller()
client = controller.get_client_name()
assert client == expected
+4 -3
View File
@@ -10,7 +10,7 @@ gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, GLib # noqa from gi.repository import Gtk, Gio, GLib # noqa
class DummyWidget(ColorAwareApp, Gtk.Button): class TestWidget(ColorAwareApp, Gtk.Button):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
@@ -22,10 +22,11 @@ class DummyWidget(ColorAwareApp, Gtk.Button):
@pytest.fixture @pytest.fixture
def widget() -> DummyWidget: def widget() -> TestWidget:
return DummyWidget() return TestWidget()
@pytest.mark.integration
@pytest.mark.parametrize("key, expect", [("default", False), ("prefer-dark", True)]) @pytest.mark.parametrize("key, expect", [("default", False), ("prefer-dark", True)])
def test_light_mode(widget, key: str, expect: bool) -> None: def test_light_mode(widget, key: str, expect: bool) -> None:
local_pref = subprocess.run( local_pref = subprocess.run(
-9
View File
@@ -3,7 +3,6 @@ import pytest
from pathlib import Path from pathlib import Path
from dzgui.api.steam import find_user_id from dzgui.api.steam import find_user_id
from dzgui.const.constants import DEFAULT_STEAM_PATH
from tests.fixtures import fixture_path from tests.fixtures import fixture_path
pytestmark = pytest.mark.apitest 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) monkeypatch.setattr("dzgui.api.steam.find_loginusers", mock_loginusers)
uid = find_user_id(Path("")) uid = find_user_id(Path(""))
assert int(uid) == expect 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
-76
View File
@@ -1,76 +0,0 @@
import unittest
from unittest.mock import patch
from dzgui.const.constants import (
FLATPAK_APPID,
FLATPAK_ROOT_PATH,
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 output, expected in (
(
[
[FLATPAK_ROOT_PATH],
"/home/user/.local/share/Steam/ubuntu12_64/steamwebhelper",
],
True,
),
([[FLATPAK_ROOT_PATH]], False),
([[FLATPAK_ROOT_PATH], ["steam-runtime-launcher-service"]], False),
([["/home/user/.local/share/Steam/ubuntu12_64/steamwebhelper"]], True),
(
[
["/home/user/.local/share/Steam/ubuntu12_64/steamwebhelper"],
"steam-runtime-launcher-service",
],
True,
),
):
with (
self.subTest(running=output),
patch.object(proc, "get_steam_procs", return_value=output),
):
self.assertEqual(proc.is_native_steam_running(), expected)
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: class DummyEmitter:
def __init__(self) -> None: pass
pass
def connect(self, _, __) -> None:
pass
@pytest.fixture(params=[("05:00", 10.0), ("15:30", 5.0)]) @pytest.fixture(params=[("05:00", 10.0), ("15:30", 5.0)])