Compare commits

...
5 Commits
Author SHA1 Message Date
aclist 0c6421fa63 chore: update freedesktop comment
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
2026-07-21 16:30:07 +09:00
aclist a4f9a67da1 fix: use drop-in mixin to set page progress state 2026-07-21 15:01:11 +09:00
aclist 5c07a77b63 Merge pull request #409 from aclist/fix/beta-client-vdf
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
fix: handle alternate client versions when parsing vdf
2026-07-15 11:29:54 +09:00
aclist 91773c933c chore: bump version 2026-07-15 11:26:01 +09:00
aclist 6a30ed44fc fix: handle alternate client versions when parsing vdf 2026-07-15 11:25:47 +09:00
11 changed files with 125 additions and 30 deletions

No files matched your search

+21 -13
View File
@@ -49,9 +49,21 @@ class Shortcuts:
def find_appname_by_unsigned_id(self, appid: int) -> str:
# NOTE: bitmask signed int back to 32-bit CRC
for key in self.shortcuts["shortcuts"].keys():
if self.shortcuts["shortcuts"][key]["appid"] & 0xFFFFFFFF == appid:
return str(self.shortcuts["shortcuts"][key]["appname"])
# 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:
@@ -119,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"] = ""
@@ -142,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
+9 -2
View File
@@ -205,21 +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:
+1 -1
View File
@@ -21,7 +21,7 @@ def write_desktop_file(exe_path: Path) -> Path:
Terminal=false
Exec={exe_path}
Name=DZGUI
Comment=dzgui
Comment=DayZ server browser and mod manager
Icon={icon}
Categories=Game"""
+2 -1
View File
@@ -162,7 +162,8 @@ class APIEntry(Gtk.Box):
self.entry.connect("icon-release", self._on_icon_release)
self.entry.connect("activate", self._on_field_activated)
self.submit = Gtk.Button(label="Submit")
# TODO: strings
self.submit = Gtk.Button(label="Validate")
self.submit.set_sensitive(False)
self.submit.connect("clicked", self._on_submit)
+9 -7
View File
@@ -49,6 +49,11 @@ class PageNum(Enum):
SHORTCUTS = 7
FINAL = 8
class OptionalPageMixin:
"""Marks optional pages as advanceable"""
def _on_map(self, page: "ScrolledWizardPage") -> None:
EMITTER.emit("step_complete")
class DescriptionArea(Gtk.Box):
def __init__(self, text: str):
@@ -91,6 +96,7 @@ class ScrolledWizardPage(Gtk.ScrolledWindow):
margin_top=50,
spacing=20,
)
self.add(self.box)
self.prog = Progress()
self.box.pack_end(self.prog, expand=False, fill=False, padding=0)
@@ -215,7 +221,7 @@ class APIValidationPage(ScrolledWizardPage):
self.spinner.stop()
class BMValidationPage(APIValidationPage):
class BMValidationPage(OptionalPageMixin, APIValidationPage):
def __init__(self) -> None:
super().__init__(
enum=PageNum.BM_API,
@@ -224,6 +230,7 @@ class BMValidationPage(APIValidationPage):
link=BM_API_SETUP,
func=self._validate,
)
self.connect("map", self._on_map)
@call_on_thread("", show_dialog=False)
def _validate(self, key: str) -> None:
@@ -570,8 +577,6 @@ class Assistant(Gtk.Assistant):
# NOTE: disable forward action
# TODO: use page enums
if page == self.page5:
return
if page != self.page1:
EMITTER.emit("step_pending")
@@ -599,7 +604,7 @@ class CheckboxWithLabel(Gtk.Box):
self.button.set_active(state)
class ShortcutCreationPage(ScrolledWizardPage):
class ShortcutCreationPage(OptionalPageMixin, ScrolledWizardPage):
def __init__(self, shortcut: Path) -> None:
super().__init__(
enum=PageNum.SHORTCUTS,
@@ -642,9 +647,6 @@ class ShortcutCreationPage(ScrolledWizardPage):
self.desktop_checkbox.set_active(state)
self.desktop_checkbox.set_sensitive(state)
def _on_map(self, page: "ScrolledWizardPage") -> None:
EMITTER.emit("step_complete")
def set_steam_path(self, path: Path) -> None:
self.steam_path = path
+1 -1
View File
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
authors = [
{name = "aclist"}
]
version = "7.0.0b17"
version = "7.0.0b18"
license = "GPL-3.0-or-later"
license-files = ["LICENSE"]
readme = "README.md"
+13
View File
@@ -0,0 +1,13 @@
"users"
{
"0"
{
"AccountName" "STEAMUSER"
"PersonaName" "STEAMUSER"
"RememberPassword" "0"
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AutoLogin" "1"
"Timestamp" "0"
}
}
+14
View File
@@ -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"
}
}
+25
View File
@@ -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
+5 -5
View File
@@ -30,9 +30,9 @@ def test_no_shortcuts(monkeypatch) -> None:
@pytest.fixture
def dummy_app() -> None:
d = {
"appname": "TEST APP",
"AppName": "TEST APP",
"StartDir": "TEST_DIR",
"exe": "TEST_DIR/TEST_EXE.EXE",
"Exe": "TEST_DIR/TEST_EXE.EXE",
"icon": "IMAGES_DIR/TEST_IMAGE.PNG",
}
return d
@@ -41,8 +41,8 @@ def dummy_app() -> None:
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] == '"'
assert s.shortcuts["shortcuts"]["0"]["Exe"][0] == '"'
assert s.shortcuts["shortcuts"]["0"]["Exe"][-1] == '"'
def test_add_shortcut(dummy_app) -> None:
@@ -51,7 +51,7 @@ def test_add_shortcut(dummy_app) -> None:
new = s.shortcuts["shortcuts"]
ind = str(len(new) - 1)
for k, v in dummy_app.items():
if k == "exe":
if k == "Exe":
v = f'"{v}"'
assert new[ind][k] == v