Compare commits

..
Author SHA1 Message Date
aclist 4eec00e8e7 chore: trigger checks 2026-10-15 00:36:09 +09:00
aclist 09ef3e446a change: set flatpak option sensitivity dynamically 2026-10-15 00:35:02 +09:00
12 changed files with 60 additions and 94 deletions

No files matched your search

+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
+5 -23
View File
@@ -1,18 +1,17 @@
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 requests.exceptions import HTTPError
from typing import Any, Generator, Union from typing import Any, 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_steam_client from dzgui.init.prereqs import has_any_client
from dzgui.const.constants import ( from dzgui.const.constants import (
APPID_DAYZ, APPID_DAYZ,
APPID_DAYZ_EXP, APPID_DAYZ_EXP,
@@ -21,7 +20,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,
@@ -61,7 +59,7 @@ class VDFLoadError(Exception):
def get_steam_paths() -> list[tuple[Path, str]]: def get_steam_paths() -> list[tuple[Path, str]]:
paths = [] paths = []
if has_steam_client(): if has_any_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:
@@ -267,12 +265,12 @@ def find_user_id_32(path: Path) -> int:
class SteamAPIKeyError(Exception): class SteamAPIKeyError(Exception):
def __init__(self, msg: str | None = None): def __init__(self, msg: str | None = None) -> None:
super().__init__(msg or api_validation_error) super().__init__(msg or api_validation_error)
class SteamWorkshopEndpointError(Exception): class SteamWorkshopEndpointError(Exception):
def __init__(self, msg: str | None = None): def __init__(self, msg: str | None = None) -> None:
super().__init__(msg or workshop_endpoint_error) super().__init__(msg or workshop_endpoint_error)
@@ -441,22 +439,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
+2 -2
View File
@@ -15,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_steam_client from dzgui.init.prereqs import has_any_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
@@ -136,7 +136,7 @@ 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_steam_client() is False: if has_any_client() is False:
EarlyAlertDialog(init.requires_steam) EarlyAlertDialog(init.requires_steam)
bootwin = BootWindow(XDG, version) bootwin = BootWindow(XDG, version)
-1
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"
+20 -7
View File
@@ -7,19 +7,32 @@ 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_steam_client() -> bool: def has_flatpak_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"], capture_output=True, text=True, check=True [FLATPAK_CMD, "list", "--columns=application"],
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 subprocess.CalledProcessError as e: except Exception 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()
-4
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,
@@ -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:
@@ -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,
-4
View File
@@ -47,7 +47,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."
)
+23 -2
View File
@@ -3,6 +3,8 @@ 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
from typing import Self
from dzgui.init.prereqs import has_steam_client, has_flatpak_client
import gi import gi
@@ -14,36 +16,55 @@ class ClientCombo(Gtk.ComboBox):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self.client_store = Gtk.ListStore(str, str) has_steam = has_steam_client()
has_flatpak = has_flatpak_client()
self.client_store = Gtk.ListStore(str, str, bool)
self.clients = ( 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 self.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: def get_client_human_readable(self, cmd: str) -> str:
for client in self.clients: for client in self.clients:
human, command = client human, command, sens = client
if command == cmd: if command == cmd:
return human return human
raise TypeError("Not a valid Steam client selection") 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)
-2
View File
@@ -279,8 +279,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 -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.0b26"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
license-files = ["LICENSE"] license-files = ["LICENSE"]
readme = "README.md" readme = "README.md"
-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