mirror of
https://github.com/aclist/dztui.git
synced 2026-11-03 00:22:04 +01:00
Compare commits
36
Commits
6fdf5547dc
...
7.0.0b18
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c07a77b63 | ||
|
|
91773c933c | ||
|
|
6a30ed44fc | ||
|
|
bbaec35a5f | ||
|
|
c46477f0a7 | ||
|
|
f47446a2e1 | ||
|
|
cdb311cb1f | ||
|
|
11cdcb62b2 | ||
|
|
1913f69e7b | ||
|
|
8c2cea0fdc | ||
|
|
8c29b9b35a | ||
|
|
d8239f1c34 | ||
|
|
8c3ec4512a | ||
|
|
1a18c8ff02 | ||
|
|
92c23ec609 | ||
|
|
e9d4054e76 | ||
|
|
1c9e026f70 | ||
|
|
eb076b7dcd | ||
|
|
8629e02670 | ||
|
|
3ba38f30ad | ||
|
|
7c37fd8b6e | ||
|
|
1a065123bb | ||
|
|
2586306323 | ||
|
|
1ccb99d935 | ||
|
|
054fe968d1 | ||
|
|
1760e6e928 | ||
|
|
a2a39e9927 | ||
|
|
e664f68624 | ||
|
|
2e41856348 | ||
|
|
27d444ea6f | ||
|
|
26a2362172 | ||
|
|
50cf347164 | ||
|
|
dd69bc88b1 | ||
|
|
5c84b5ae0a | ||
|
|
e92df8c062 | ||
|
|
494311e8df |
No files matched your search
+29
-28
@@ -1,6 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
## Added
|
||||
## [7.0.0] Unreleased
|
||||
### Added
|
||||
- Setup wizard
|
||||
- Changelog text wrapping and formatting
|
||||
- Changelog ships with source
|
||||
@@ -44,7 +45,7 @@
|
||||
- Generate Steam shortcuts and cover art
|
||||
- Warn user if background downloads are disabled
|
||||
|
||||
## Changed
|
||||
### Changed
|
||||
- Conform to PEP 440 versioning for beta versions
|
||||
- Reduce padding on keys button
|
||||
- Boldface breadcrumbs
|
||||
@@ -61,58 +62,58 @@
|
||||
- Enable LAN page Empty/Full filters on first run of app
|
||||
- Propagate subscribed mods to Steam client
|
||||
|
||||
## Dropped
|
||||
### Dropped
|
||||
- Debug mode
|
||||
- Branch switching
|
||||
- Manual mod install mode (describe rationale)
|
||||
- Force update mods
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Longstanding issue with left clicks not registering as tree selection changes after spamming keyboard input
|
||||
- Center server title text on server dialogs
|
||||
- Rare segfaults when changing maps (threading)
|
||||
- Moved dialogs out of threads
|
||||
|
||||
## Unreleased
|
||||
### Unreleased
|
||||
- Load offline mods
|
||||
- Local documentation
|
||||
- Raw debug command in context menu
|
||||
|
||||
## Developer-facing
|
||||
### Developer-facing
|
||||
- Add pyproject.toml file
|
||||
- Show deprecation warnings
|
||||
- Options -> Dev page
|
||||
|
||||
## [6.0.5] 2026-05-17
|
||||
## Changed
|
||||
### Changed
|
||||
- Update symlink hash logic to align with DZGUI 7 beta
|
||||
- Update A2S helper to latest commit
|
||||
|
||||
## [6.0.4] 2026-04-13
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Update A2S module checksums to support servers with missing description field
|
||||
|
||||
## [6.0.3] 2026-04-12
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Use GLibUnix namespace where available when setting up SIGINT callback
|
||||
|
||||
## [6.0.2] 2026-01-26
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Explicitly use Python 3.13 when calling subprocesses
|
||||
- Raise error correctly from module
|
||||
- Do not spawn dialogs from inside of thread when fetching prereqs
|
||||
## Changed
|
||||
### Changed
|
||||
- Optimize time complexity of startup checks
|
||||
- Optimize coordinate calculation by using local records first
|
||||
- Rename some internal functions
|
||||
- Wrap entire startup process in dialog
|
||||
|
||||
## [6.0.1] 2026-01-16
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Explicitly use Python 3.13 when calling subprocesses
|
||||
|
||||
## [6.0.0] 2026-01-14
|
||||
## Added
|
||||
### Added
|
||||
- Support DayZ Experimental
|
||||
- Ping display on server tables
|
||||
- Save descriptive text notes on a per-server basis
|
||||
@@ -138,7 +139,7 @@
|
||||
- Commandline usage help text (GaryBlackbourne)
|
||||
- More descriptive help dialogs when initial dependencies are missing
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Script failing to start when remote endpoints are unavailable (GaryBlackbourne)
|
||||
- Key stickiness when quickly navigating through entries in tables
|
||||
- Servers returning malformed A2S_INFO blocking server browser from loading
|
||||
@@ -177,7 +178,7 @@
|
||||
- Path discovery during first-time setup when parsing filepaths with whitespaces
|
||||
- First-time setup dialog continuously triggering when DayZ install path had whitespaces in it
|
||||
|
||||
## Changed
|
||||
### Changed
|
||||
- Require Python 3.13
|
||||
- Reduced global API cooldown from 60s to 30s
|
||||
- Clarify dialog messages when DayZ path could not be found
|
||||
@@ -189,50 +190,50 @@
|
||||
- Rewrote distance calculation module (GaryBlackbourne)
|
||||
- Changed preferred client setting from radio toggle to combobox
|
||||
|
||||
## Dropped
|
||||
### Dropped
|
||||
- Ping readout in statusbar
|
||||
- Extraneous information from right statusbar
|
||||
|
||||
## [5.8.3] 2026-01-04
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Normalize checksum numbers and dates
|
||||
|
||||
## [5.8.2] 2026-01-04
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Fix regression in symlink routine introduced by previous hotfix
|
||||
|
||||
## [5.8.1] 2025-12-27
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Path discovery during first-time setup when parsing filepaths with whitespaces
|
||||
- First-time setup dialog continuously triggering when DayZ install path had whitespaces in it
|
||||
|
||||
## [5.8.0] 2025-07-06
|
||||
## Added
|
||||
### Added
|
||||
- Filter servers by official/unofficial status
|
||||
- Automatically fetch geolocation records
|
||||
## Changed
|
||||
### Changed
|
||||
- Updated internal versioning of helper files
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Corrected erroneous 2024 date in prior changelog entries
|
||||
|
||||
## [5.7.0] 2025-04-17
|
||||
## Added
|
||||
### Added
|
||||
- Save application dimensions when quitting and restore on subsequent boot
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Issues with the window exceeding the bounds of the screen when displaying some table contexts
|
||||
## Changed
|
||||
### Changed
|
||||
- More direct server ping query method that should return marginally more accurate times
|
||||
|
||||
## [5.6.7] 2025-04-04
|
||||
## Dropped
|
||||
### Dropped
|
||||
- Removed extraneous pre-boot API checks that could cause error messages to be printed if the user had not set up an API key yet
|
||||
|
||||
## [5.6.6] 2025-03-16
|
||||
## Changed
|
||||
### Changed
|
||||
- Update IP database records for 2025-03
|
||||
|
||||
## [5.6.5] 2025-03-04
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Livonia server results being dropped from batch queries
|
||||
|
||||
## [5.6.4] 2025-02-10
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ If you are a developer, you can skip to the end.
|
||||
# Requesting help
|
||||
|
||||
If you encounter a problem with DZGUI, you can submit tickets on the GitHub
|
||||
(issue tracker)[https://github.com/aclist/dztui/issues] under the
|
||||
[issue tracker](https://github.com/aclist/dztui/issues) under the
|
||||
`troubleshooting` tag.
|
||||
|
||||
# How can I help the project?
|
||||
@@ -24,7 +24,7 @@ accordingly.
|
||||
|
||||
## Submitting a ticket
|
||||
|
||||
Navigate to the GitHub (issue tracker)[https://github.com/aclist/dztui/issues].
|
||||
Navigate to the GitHub [issue tracker](https://github.com/aclist/dztui/issues).
|
||||
From there, follow the onscreen prompts. You will be asked questions such as:
|
||||
|
||||
- What version are you using?
|
||||
|
||||
@@ -86,7 +86,6 @@ class WorkshopACF(ACF):
|
||||
def __init__(self, file: Path) -> None:
|
||||
super().__init__(file)
|
||||
|
||||
# TODO: break into workshop parser and generic acf parser
|
||||
def unpack(self, d: dict, lines: list[Any] = []) -> str:
|
||||
t1 = "AppWorkshop"
|
||||
t2 = ("WorkshopItemsInstalled", "WorkshopItemDetails")
|
||||
|
||||
+27
-12
@@ -12,6 +12,7 @@ from typing import Any
|
||||
from dzgui.api.steam import find_user_id_32
|
||||
from dzgui.const.constants import APP_NAME, APP_NAME_LOWER, IMAGES_PATH
|
||||
from dzgui.util.dirs import copy_dzgui_to_xdg_data
|
||||
from dzgui.util.strings import unknown
|
||||
|
||||
logger = logging.getLogger(APP_NAME)
|
||||
|
||||
@@ -46,6 +47,25 @@ class Shortcuts:
|
||||
wrapped_exe = f'"{exe}"'
|
||||
return appname + wrapped_exe
|
||||
|
||||
def find_appname_by_unsigned_id(self, appid: int) -> str:
|
||||
# NOTE: bitmask signed int back to 32-bit CRC
|
||||
# varying client versions treat case sensitivity differently
|
||||
try:
|
||||
for key in self.shortcuts["shortcuts"].keys():
|
||||
if self.shortcuts["shortcuts"][key]["appid"] & 0xFFFFFFFF == appid:
|
||||
try:
|
||||
return str(self.shortcuts["shortcuts"][key]["appname"])
|
||||
except Exception as e:
|
||||
logger.debug(e)
|
||||
try:
|
||||
return str(self.shortcuts["shortcuts"][key]["AppName"])
|
||||
except Exception as e:
|
||||
logger.debug(e)
|
||||
return unknown
|
||||
except Exception as e:
|
||||
logger.debug(e)
|
||||
return unknown
|
||||
|
||||
def get_shortcuts(self) -> Any:
|
||||
return self.shortcuts
|
||||
|
||||
@@ -59,7 +79,7 @@ class Shortcuts:
|
||||
logger.critical(e)
|
||||
raise e
|
||||
|
||||
def find_shortcuts_path(self, steam_path: Path) -> None:
|
||||
def find_shortcuts_path(self, steam_path: Path) -> Path:
|
||||
uid = find_user_id_32(steam_path)
|
||||
self.user_config_path = steam_path.joinpath(f"userdata/{uid}/config")
|
||||
return self.user_config_path.joinpath("shortcuts.vdf")
|
||||
@@ -111,22 +131,18 @@ class Shortcuts:
|
||||
https://developer.valvesoftware.com/wiki/Add_Non-Steam_Game
|
||||
https://developer.valvesoftware.com/wiki/Steam_Library_Shortcuts
|
||||
|
||||
Wiki variously lists keys with title case and lowercase, but keys actually do
|
||||
not seem to be case-sensitive. Some entries generated by Steam do not match the wiki
|
||||
|
||||
Keys are entered into the dictionary in a linear insertion order
|
||||
|
||||
# TODO: try to replicate key case sensitivity as it is created by Steam with generic shortcut
|
||||
Key case is not internally consistent and varies between client versions
|
||||
Most keys use Pascal case, but some do not
|
||||
|
||||
appid: signed int CRC
|
||||
exe: absolute path to the executable, must be wrapped in literal quotes
|
||||
Exe: absolute path to the executable, must be wrapped in literal quotes
|
||||
StartDir: directory the executable starts in, generally the parent
|
||||
"""
|
||||
|
||||
NEW_ENTRY: dict[str, Any] = {}
|
||||
NEW_ENTRY["appid"] = meta.appid
|
||||
NEW_ENTRY["appname"] = meta.appname
|
||||
NEW_ENTRY["exe"] = f'"{meta.exe_path}"'
|
||||
NEW_ENTRY["AppName"] = meta.appname
|
||||
NEW_ENTRY["Exe"] = f'"{meta.exe_path}"'
|
||||
NEW_ENTRY["StartDir"] = f"{meta.start_dir}"
|
||||
NEW_ENTRY["icon"] = meta.icon
|
||||
NEW_ENTRY["ShortcutPath"] = ""
|
||||
@@ -134,7 +150,7 @@ class Shortcuts:
|
||||
NEW_ENTRY["IsHidden"] = 0
|
||||
NEW_ENTRY["AllowDesktopConfig"] = 1
|
||||
NEW_ENTRY["AllowOverlay"] = 1
|
||||
NEW_ENTRY["openvr"] = 0
|
||||
NEW_ENTRY["OpenVR"] = 0
|
||||
NEW_ENTRY["Devkit"] = 0
|
||||
NEW_ENTRY["DevkitGameID"] = ""
|
||||
NEW_ENTRY["DevkitOverrideAppID"] = 0
|
||||
@@ -162,7 +178,6 @@ def add_steam_shortcut(steam_path: Path, exe_path: Path) -> None:
|
||||
images = Path(str(traversable))
|
||||
icon = images.joinpath("icon.png")
|
||||
|
||||
# TEST: assert that record (start dir and exe combo) is in shortcuts after creation
|
||||
shortcuts = Shortcuts(Path(steam_path))
|
||||
shortcuts.add_shortcut(APP_NAME, start_dir, exe_path, icon)
|
||||
shortcuts.save_shortcuts()
|
||||
|
||||
+50
-26
@@ -15,6 +15,7 @@ from dzgui.const.constants import (
|
||||
APPID_DAYZ,
|
||||
APPID_DAYZ_EXP,
|
||||
APP_NAME,
|
||||
DAYZ_BINARY,
|
||||
DEBIAN_STEAM_PATH,
|
||||
DEFAULT_STEAM_PATH,
|
||||
FLATPAK_STEAM_PATH,
|
||||
@@ -31,7 +32,6 @@ from dzgui.const.endpoints import (
|
||||
)
|
||||
from dzgui.strings import wizard
|
||||
from dzgui.util.bash import concat_bash_args
|
||||
from dzgui.util.strings import unknown
|
||||
|
||||
logger = logging.getLogger(APP_NAME)
|
||||
|
||||
@@ -205,20 +205,28 @@ def launch_offline(
|
||||
proc = subprocess.run([*client_args, *params])
|
||||
return proc.returncode
|
||||
|
||||
def find_loginusers(path: Path) -> Path:
|
||||
return path / "config" / "loginusers.vdf"
|
||||
|
||||
def find_user_id(path: Path) -> str | None:
|
||||
resolved_path = path / "config" / "loginusers.vdf"
|
||||
resolved_path = find_loginusers(path)
|
||||
try:
|
||||
with open(resolved_path, "r") as f:
|
||||
v = vdf.load(f)
|
||||
# NOTE: beta client
|
||||
# /package/beta
|
||||
if len(v["users"]) == 1:
|
||||
return str(list(v["users"].keys())[0])
|
||||
for user in v["users"]:
|
||||
if v["users"][user]["MostRecent"] == "1":
|
||||
return str(user)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warn(e)
|
||||
logger.debug(e)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
def find_user_id_32(path: Path) -> int:
|
||||
uid = find_user_id(path)
|
||||
if uid is None:
|
||||
@@ -301,28 +309,34 @@ def get_running_app() -> int | None:
|
||||
PROC_NAME = "steam"
|
||||
SUBPROC_NAME = "reaper"
|
||||
FLAG = "AppId"
|
||||
|
||||
for proc in psutil.process_iter():
|
||||
if proc.name() == PROC_NAME:
|
||||
subprocs = proc.children()
|
||||
filtered = (proc for proc in subprocs if proc.name() == SUBPROC_NAME)
|
||||
try:
|
||||
proc = next(filtered)
|
||||
except StopIteration:
|
||||
return None
|
||||
args = proc.cmdline()
|
||||
appid = (row for row in args if FLAG in row)
|
||||
try:
|
||||
return int(next(appid).split("=")[1])
|
||||
except StopIteration:
|
||||
return None
|
||||
# NOTE: may cause conflicts if multiple apps are running
|
||||
try:
|
||||
for proc in psutil.process_iter():
|
||||
if proc.name() == PROC_NAME:
|
||||
subprocs = proc.children()
|
||||
filtered = (proc for proc in subprocs if proc.name() == SUBPROC_NAME)
|
||||
try:
|
||||
proc = next(filtered)
|
||||
except StopIteration:
|
||||
return None
|
||||
args = proc.cmdline()
|
||||
appid = (row for row in args if FLAG in row)
|
||||
try:
|
||||
return int(next(appid).split("=")[1])
|
||||
except StopIteration:
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.debug(e)
|
||||
return None
|
||||
|
||||
|
||||
def get_app_allows_downloads(path: Path, appid: int) -> bool:
|
||||
root_path = get_app_path(path, appid)
|
||||
acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf")
|
||||
flag = ACF(acf).get_allows_downloads()
|
||||
try:
|
||||
root_path = get_app_path(path, appid)
|
||||
acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf")
|
||||
flag = ACF(acf).get_allows_downloads()
|
||||
except AppNotInstalledError:
|
||||
flag = 0
|
||||
match flag:
|
||||
# NOTE: adheres to global client setting
|
||||
case 0:
|
||||
@@ -337,9 +351,11 @@ def get_app_allows_downloads(path: Path, appid: int) -> bool:
|
||||
case _:
|
||||
return True
|
||||
|
||||
|
||||
def get_config(path: Path) -> Path:
|
||||
return path.joinpath("config/config.vdf")
|
||||
|
||||
|
||||
def get_client_allows_downloads(path: Path) -> bool:
|
||||
config = get_config(path)
|
||||
try:
|
||||
@@ -360,8 +376,16 @@ def get_client_allows_downloads(path: Path) -> bool:
|
||||
|
||||
|
||||
def is_dayz_running() -> bool:
|
||||
appid = get_running_app()
|
||||
return appid in (APPID_DAYZ, APPID_DAYZ_EXP)
|
||||
"""Subprocesses spawned from Steam will not show up in regular process tree"""
|
||||
procs = []
|
||||
substring = DAYZ_BINARY
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
procs.append(proc.cmdline())
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
continue
|
||||
return any(substring in item for sublist in procs for item in sublist)
|
||||
|
||||
|
||||
def get_app_path(folders_path: Path, appid: int) -> Path:
|
||||
@@ -391,13 +415,13 @@ def get_app_path(folders_path: Path, appid: int) -> Path:
|
||||
return Path(app_path)
|
||||
|
||||
|
||||
def get_app_name(appid: int) -> str:
|
||||
def get_app_name(appid: int) -> str | None:
|
||||
payload = {"appids": [appid]}
|
||||
res = requests.get(APP_DETAILS, params=payload)
|
||||
if res.status_code != 200:
|
||||
return unknown
|
||||
return None
|
||||
try:
|
||||
return str(res.json()[str(appid)]["data"]["name"])
|
||||
except Exception as e:
|
||||
logger.debug(e)
|
||||
return unknown
|
||||
return None
|
||||
+29
-28
@@ -1,6 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
## Added
|
||||
## [7.0.0] Unreleased
|
||||
### Added
|
||||
- Setup wizard
|
||||
- Changelog text wrapping and formatting
|
||||
- Changelog ships with source
|
||||
@@ -44,7 +45,7 @@
|
||||
- Generate Steam shortcuts and cover art
|
||||
- Warn user if background downloads are disabled
|
||||
|
||||
## Changed
|
||||
### Changed
|
||||
- Conform to PEP 440 versioning for beta versions
|
||||
- Reduce padding on keys button
|
||||
- Boldface breadcrumbs
|
||||
@@ -61,58 +62,58 @@
|
||||
- Enable LAN page Empty/Full filters on first run of app
|
||||
- Propagate subscribed mods to Steam client
|
||||
|
||||
## Dropped
|
||||
### Dropped
|
||||
- Debug mode
|
||||
- Branch switching
|
||||
- Manual mod install mode (describe rationale)
|
||||
- Force update mods
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Longstanding issue with left clicks not registering as tree selection changes after spamming keyboard input
|
||||
- Center server title text on server dialogs
|
||||
- Rare segfaults when changing maps (threading)
|
||||
- Moved dialogs out of threads
|
||||
|
||||
## Unreleased
|
||||
### Unreleased
|
||||
- Load offline mods
|
||||
- Local documentation
|
||||
- Raw debug command in context menu
|
||||
|
||||
## Developer-facing
|
||||
### Developer-facing
|
||||
- Add pyproject.toml file
|
||||
- Show deprecation warnings
|
||||
- Options -> Dev page
|
||||
|
||||
## [6.0.5] 2026-05-17
|
||||
## Changed
|
||||
### Changed
|
||||
- Update symlink hash logic to align with DZGUI 7 beta
|
||||
- Update A2S helper to latest commit
|
||||
|
||||
## [6.0.4] 2026-04-13
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Update A2S module checksums to support servers with missing description field
|
||||
|
||||
## [6.0.3] 2026-04-12
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Use GLibUnix namespace where available when setting up SIGINT callback
|
||||
|
||||
## [6.0.2] 2026-01-26
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Explicitly use Python 3.13 when calling subprocesses
|
||||
- Raise error correctly from module
|
||||
- Do not spawn dialogs from inside of thread when fetching prereqs
|
||||
## Changed
|
||||
### Changed
|
||||
- Optimize time complexity of startup checks
|
||||
- Optimize coordinate calculation by using local records first
|
||||
- Rename some internal functions
|
||||
- Wrap entire startup process in dialog
|
||||
|
||||
## [6.0.1] 2026-01-16
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Explicitly use Python 3.13 when calling subprocesses
|
||||
|
||||
## [6.0.0] 2026-01-14
|
||||
## Added
|
||||
### Added
|
||||
- Support DayZ Experimental
|
||||
- Ping display on server tables
|
||||
- Save descriptive text notes on a per-server basis
|
||||
@@ -138,7 +139,7 @@
|
||||
- Commandline usage help text (GaryBlackbourne)
|
||||
- More descriptive help dialogs when initial dependencies are missing
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Script failing to start when remote endpoints are unavailable (GaryBlackbourne)
|
||||
- Key stickiness when quickly navigating through entries in tables
|
||||
- Servers returning malformed A2S_INFO blocking server browser from loading
|
||||
@@ -177,7 +178,7 @@
|
||||
- Path discovery during first-time setup when parsing filepaths with whitespaces
|
||||
- First-time setup dialog continuously triggering when DayZ install path had whitespaces in it
|
||||
|
||||
## Changed
|
||||
### Changed
|
||||
- Require Python 3.13
|
||||
- Reduced global API cooldown from 60s to 30s
|
||||
- Clarify dialog messages when DayZ path could not be found
|
||||
@@ -189,50 +190,50 @@
|
||||
- Rewrote distance calculation module (GaryBlackbourne)
|
||||
- Changed preferred client setting from radio toggle to combobox
|
||||
|
||||
## Dropped
|
||||
### Dropped
|
||||
- Ping readout in statusbar
|
||||
- Extraneous information from right statusbar
|
||||
|
||||
## [5.8.3] 2026-01-04
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Normalize checksum numbers and dates
|
||||
|
||||
## [5.8.2] 2026-01-04
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Fix regression in symlink routine introduced by previous hotfix
|
||||
|
||||
## [5.8.1] 2025-12-27
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Path discovery during first-time setup when parsing filepaths with whitespaces
|
||||
- First-time setup dialog continuously triggering when DayZ install path had whitespaces in it
|
||||
|
||||
## [5.8.0] 2025-07-06
|
||||
## Added
|
||||
### Added
|
||||
- Filter servers by official/unofficial status
|
||||
- Automatically fetch geolocation records
|
||||
## Changed
|
||||
### Changed
|
||||
- Updated internal versioning of helper files
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Corrected erroneous 2024 date in prior changelog entries
|
||||
|
||||
## [5.7.0] 2025-04-17
|
||||
## Added
|
||||
### Added
|
||||
- Save application dimensions when quitting and restore on subsequent boot
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Issues with the window exceeding the bounds of the screen when displaying some table contexts
|
||||
## Changed
|
||||
### Changed
|
||||
- More direct server ping query method that should return marginally more accurate times
|
||||
|
||||
## [5.6.7] 2025-04-04
|
||||
## Dropped
|
||||
### Dropped
|
||||
- Removed extraneous pre-boot API checks that could cause error messages to be printed if the user had not set up an API key yet
|
||||
|
||||
## [5.6.6] 2025-03-16
|
||||
## Changed
|
||||
### Changed
|
||||
- Update IP database records for 2025-03
|
||||
|
||||
## [5.6.5] 2025-03-04
|
||||
## Fixed
|
||||
### Fixed
|
||||
- Livonia server results being dropped from batch queries
|
||||
|
||||
## [5.6.4] 2025-02-10
|
||||
|
||||
@@ -55,8 +55,11 @@ class ConfigManager:
|
||||
self.update_config(Preferences.IP_LIST, ips)
|
||||
|
||||
def update_history_file(self, fqip: str) -> None:
|
||||
with open(self.prefs.paths.history, "r") as f:
|
||||
ips = [line.rstrip() for line in f.readlines()]
|
||||
try:
|
||||
with open(self.prefs.paths.history, "r") as f:
|
||||
ips = [line.rstrip() for line in f.readlines()]
|
||||
except FileNotFoundError:
|
||||
ips = []
|
||||
|
||||
seen = set()
|
||||
ips.append(fqip)
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
import dzgui.api.pefile as PeFile
|
||||
import dzgui.api.servers as Servers
|
||||
from dzgui.api.shortcuts import Shortcuts
|
||||
|
||||
from dzgui.api.steam import (
|
||||
connect,
|
||||
@@ -175,6 +176,12 @@ class ConnectionManager:
|
||||
if running_app is not None:
|
||||
allows_dl = get_app_allows_downloads(steam_path, running_app)
|
||||
running_app_name = get_app_name(running_app)
|
||||
# NOTE: for out-of-range appid, assume NSG.
|
||||
# dzgui.api.steam.get_app_name() will return None
|
||||
if running_app_name is None:
|
||||
s = Shortcuts(steam_path)
|
||||
# NOTE: returns "Unknown" if unparseable
|
||||
running_app_name = s.find_appname_by_unsigned_id(running_app)
|
||||
allows_downloads = (allows_dl, running_app_name)
|
||||
else:
|
||||
allows_downloads = (True, "")
|
||||
|
||||
@@ -484,8 +484,6 @@ class Assistant(Gtk.Assistant):
|
||||
):
|
||||
continue
|
||||
# NOTE: disabled for now on system-provided packages
|
||||
# TODO: drop, for testing purposes
|
||||
os.environ["PYAPP"] = str(Path.home().joinpath("dzgui/dzgui"))
|
||||
if isinstance(page, ShortcutCreationPage) and os.getenv("PYAPP") is None:
|
||||
continue
|
||||
self._add_page(page, page.get_page_type())
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import re
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from importlib import resources
|
||||
@@ -32,15 +33,63 @@ class Changelog(HelpMenuMixin, ScrollableMixin, Gtk.ScrolledWindow): # type: ig
|
||||
|
||||
# TODO: should long text be wrapped?
|
||||
self.controller = controller
|
||||
formatted = format_pango(changelog)
|
||||
self.changelog_label = Gtk.Label(valign=Gtk.Align.START, margin=15)
|
||||
self.changelog_label.set_markup(formatted)
|
||||
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
self.box.add(self.changelog_label)
|
||||
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5, margin_top=10)
|
||||
self.add(self.box)
|
||||
|
||||
self.connect("key-press-event", self._on_keypress)
|
||||
self.connect("key-press-event", self._on_esc_keypress)
|
||||
|
||||
changes = self._parse(changelog)
|
||||
self._generate_nodes(changes)
|
||||
self.show_all()
|
||||
|
||||
def grab_content_area(self) -> None:
|
||||
self.grab_focus()
|
||||
|
||||
def _parse_version(self, version: str) -> tuple[str, str]:
|
||||
pattern = r"(^##\s\[)(.+)(\] )(.+$)"
|
||||
match = re.match(pattern, version)
|
||||
if match:
|
||||
version = match.group(2)
|
||||
date = match.group(4)
|
||||
return version, date
|
||||
raise ValueError("Changelog parse error")
|
||||
|
||||
|
||||
def _generate_nodes(self, releases: list[tuple[str, list[str]]]) -> None:
|
||||
for header, changes in releases:
|
||||
for ind in (0, -1):
|
||||
if changes[ind] == "":
|
||||
del changes[ind]
|
||||
text = "\n".join(changes)
|
||||
formatted = format_pango(text)
|
||||
|
||||
label = Gtk.Label(valign=Gtk.Align.START, margin=15, halign=Gtk.Align.START)
|
||||
label.set_markup(formatted)
|
||||
|
||||
version, date = self._parse_version(header)
|
||||
button_text = f"{version} ({date})"
|
||||
button = Gtk.Button(label=button_text, margin_start=5)
|
||||
|
||||
expander = Gtk.Expander()
|
||||
expander.set_label_widget(button)
|
||||
expander.add(label)
|
||||
self.box.add(expander)
|
||||
|
||||
|
||||
def _parse(self, changelog: str) -> list[tuple[str, list[str]]]:
|
||||
release = ""
|
||||
releases: list[tuple[str, list[str]]] = []
|
||||
release_notes: list[str] = []
|
||||
for line in changelog.splitlines():
|
||||
if line.startswith("# "):
|
||||
continue
|
||||
if line.startswith("## "):
|
||||
if release != "":
|
||||
releases.append((release, release_notes))
|
||||
release_notes = []
|
||||
release = ""
|
||||
release = line
|
||||
continue
|
||||
release_notes.append(line.rstrip())
|
||||
return releases
|
||||
@@ -273,7 +273,7 @@ class PreConnectionAssistant(Gtk.Box):
|
||||
)
|
||||
|
||||
allows_dl, running_app = prereqs.allows_downloads
|
||||
if allows_dl is False:
|
||||
if len(prereqs.mods) > 0 and allows_dl is False:
|
||||
msg = (
|
||||
f"The game '{running_app}' is currently running in Steam, but background downloads are not enabled.\n"
|
||||
"Either stop the game first, or update your global Steam settings or the game's local settings.\n"
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
|
||||
authors = [
|
||||
{name = "aclist"}
|
||||
]
|
||||
version = "7.0.0b15"
|
||||
version = "7.0.0b18"
|
||||
license = "GPL-3.0-or-later"
|
||||
license-files = ["LICENSE"]
|
||||
readme = "README.md"
|
||||
@@ -76,7 +76,7 @@ build-backend = "setuptools.build_meta"
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
extend-exclude = ["*lib/a2s"]
|
||||
lint.unfixable = ["F401"]
|
||||
lint.unfixable = ["F401", "I001"]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
|
||||
@@ -12,6 +12,7 @@ This file includes the combined licenses to the following components:
|
||||
- psutil
|
||||
- PyGObject
|
||||
- python-a2s
|
||||
- vdf
|
||||
|
||||
When shipped as a binary installer, these components are collectively bundled
|
||||
as a turnkey application and used to run the DZGUI utility.\n\n
|
||||
|
||||
Vendored
File renamed without changes.
Vendored
File renamed without changes.
+13
@@ -0,0 +1,13 @@
|
||||
"users"
|
||||
{
|
||||
"0"
|
||||
{
|
||||
"AccountName" "STEAMUSER"
|
||||
"PersonaName" "STEAMUSER"
|
||||
"RememberPassword" "0"
|
||||
"WantsOfflineMode" "0"
|
||||
"SkipOfflineModeWarning" "0"
|
||||
"AutoLogin" "1"
|
||||
"Timestamp" "0"
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
"users"
|
||||
{
|
||||
"0"
|
||||
{
|
||||
"AccountName" "STEAMUSER"
|
||||
"PersonaName" "STEAMUSER"
|
||||
"RememberPassword" "0"
|
||||
"WantsOfflineMode" "0"
|
||||
"SkipOfflineModeWarning" "0"
|
||||
"AllowAutoLogin" "1"
|
||||
"MostRecent" "1"
|
||||
"Timestamp" "0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
"users"
|
||||
{
|
||||
"0"
|
||||
{
|
||||
"AccountName" "STEAMUSER"
|
||||
"PersonaName" "STEAMUSER"
|
||||
"RememberPassword" "0"
|
||||
"WantsOfflineMode" "0"
|
||||
"SkipOfflineModeWarning" "0"
|
||||
"AllowAutoLogin" "1"
|
||||
"MostRecent" "0"
|
||||
"Timestamp" "0"
|
||||
}
|
||||
"1"
|
||||
{
|
||||
"AccountName" "STEAMUSER2"
|
||||
"PersonaName" "STEAMUSER2"
|
||||
"RememberPassword" "0"
|
||||
"WantsOfflineMode" "0"
|
||||
"SkipOfflineModeWarning" "0"
|
||||
"AllowAutoLogin" "1"
|
||||
"MostRecent" "1"
|
||||
"Timestamp" "0"
|
||||
}
|
||||
}
|
||||
File renamed without changes.
Vendored
BIN
Binary file not shown.
File renamed without changes.
@@ -0,0 +1,25 @@
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from dzgui.api.steam import find_user_id
|
||||
from tests.fixtures import fixture_path
|
||||
|
||||
pytestmark = pytest.mark.apitest
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture, expect",
|
||||
[
|
||||
("api/loginusers_legacy_client.vdf", 0),
|
||||
("api/loginusers_legacy_client_multiple.vdf", 1),
|
||||
("api/loginusers_beta_client.vdf", 0),
|
||||
],
|
||||
)
|
||||
def test_loginusers(monkeypatch, fixture: str, expect: int) -> None:
|
||||
def mock_loginusers(path: Path) -> Path:
|
||||
return fixture_path(fixture)
|
||||
|
||||
monkeypatch.setattr("dzgui.api.steam.find_loginusers", mock_loginusers)
|
||||
uid = find_user_id(Path(""))
|
||||
assert int(uid) == expect
|
||||
+26
-12
@@ -1,8 +1,14 @@
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
import dzgui.api.pefile as PeFile
|
||||
from dzgui.api.steam import VDFLoadError, AppNotInstalledError, AppMovedError, get_app_path
|
||||
from dzgui.api.steam import (
|
||||
VDFLoadError,
|
||||
AppNotInstalledError,
|
||||
AppMovedError,
|
||||
get_app_path
|
||||
)
|
||||
|
||||
from dzgui.config.query import get_config
|
||||
from dzgui.config.xdg import get_xdg_paths, parse_filepaths
|
||||
@@ -10,12 +16,7 @@ from dzgui.const.constants import APPID_DAYZ
|
||||
|
||||
from tests.fixtures import fixture_path
|
||||
|
||||
pytestmark = pytest.mark.pefile
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def second_drive():
|
||||
return fixture_path("in_library_on_second_drive.vdf")
|
||||
pytestmark = pytest.mark.apitest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -26,6 +27,8 @@ def default_steam_path():
|
||||
return conf["default_steam_path"]
|
||||
|
||||
|
||||
# TODO: split into separate test file
|
||||
# FIXME: use fixtures
|
||||
def test_pefile_length(default_steam_path):
|
||||
try:
|
||||
pe_file_path = PeFile.get_pefile_path(Path(default_steam_path), APPID_DAYZ)
|
||||
@@ -52,15 +55,26 @@ def test_invalid_pefile_path():
|
||||
("malformed.vdf", VDFLoadError),
|
||||
],
|
||||
)
|
||||
def test_not_in_library(fixture, exception):
|
||||
fixture = fixture_path(fixture)
|
||||
def test_not_in_library(monkeypatch, fixture, exception):
|
||||
import dzgui.api.steam as steam
|
||||
monkeypatch.setattr(steam, "LIBRARYFOLDERS_PATH", fixture)
|
||||
|
||||
folder_path = Path(fixture_path("api"))
|
||||
with pytest.raises(exception):
|
||||
try:
|
||||
get_app_path(fixture, APPID_DAYZ)
|
||||
get_app_path(folder_path, APPID_DAYZ)
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
|
||||
def test_on_second_drive(second_drive):
|
||||
path = get_app_path(second_drive, APPID_DAYZ)
|
||||
@pytest.fixture
|
||||
def second_drive():
|
||||
return "in_library_on_second_drive.vdf"
|
||||
|
||||
|
||||
def test_on_second_drive(monkeypatch, second_drive):
|
||||
import dzgui.api.steam as steam
|
||||
monkeypatch.setattr(steam, "LIBRARYFOLDERS_PATH", second_drive)
|
||||
folder_path = Path(fixture_path("api"))
|
||||
path = get_app_path(folder_path, APPID_DAYZ)
|
||||
assert path == Path("/tmp")
|
||||
@@ -0,0 +1,67 @@
|
||||
import pytest
|
||||
import tempfile
|
||||
|
||||
from pathlib import Path
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
from dzgui.api.shortcuts import Shortcuts
|
||||
from tests.fixtures import fixture_path
|
||||
|
||||
pytestmark = pytest.mark.apitest
|
||||
|
||||
|
||||
def mock_find_shortcuts(self, steam_path: Path) -> Path:
|
||||
return Path(fixture_path("api/no_shortcuts.vdf"))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def patch_api() -> None:
|
||||
mp = MonkeyPatch()
|
||||
mp.setattr("dzgui.api.shortcuts.Shortcuts.find_shortcuts_path", mock_find_shortcuts)
|
||||
yield
|
||||
mp.undo()
|
||||
|
||||
|
||||
def test_no_shortcuts(monkeypatch) -> None:
|
||||
s = Shortcuts(Path(""))
|
||||
assert len(s.shortcuts["shortcuts"]) == 0
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dummy_app() -> None:
|
||||
d = {
|
||||
"AppName": "TEST APP",
|
||||
"StartDir": "TEST_DIR",
|
||||
"Exe": "TEST_DIR/TEST_EXE.EXE",
|
||||
"icon": "IMAGES_DIR/TEST_IMAGE.PNG",
|
||||
}
|
||||
return d
|
||||
|
||||
|
||||
def test_wrap_exe(dummy_app) -> None:
|
||||
s = Shortcuts(Path(""))
|
||||
s.add_shortcut(*dummy_app.values())
|
||||
assert s.shortcuts["shortcuts"]["0"]["Exe"][0] == '"'
|
||||
assert s.shortcuts["shortcuts"]["0"]["Exe"][-1] == '"'
|
||||
|
||||
|
||||
def test_add_shortcut(dummy_app) -> None:
|
||||
s = Shortcuts(Path(""))
|
||||
s.add_shortcut(*dummy_app.values())
|
||||
new = s.shortcuts["shortcuts"]
|
||||
ind = str(len(new) - 1)
|
||||
for k, v in dummy_app.items():
|
||||
if k == "Exe":
|
||||
v = f'"{v}"'
|
||||
assert new[ind][k] == v
|
||||
|
||||
|
||||
def test_save_shortcut(dummy_app) -> None:
|
||||
s = Shortcuts(Path(""))
|
||||
s.add_shortcut(*dummy_app.values())
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
tmp = Path(f.name)
|
||||
s.shortcuts_path = tmp
|
||||
s.save_shortcuts()
|
||||
s._load_shortcuts(tmp)
|
||||
assert len(s.shortcuts["shortcuts"]) == 1
|
||||
Reference in new issue
Block a user