mirror of
https://github.com/aclist/dztui.git
synced 2026-10-25 12:12:31 +01:00
Merge branch 'dzgui7' into change/flatpak-sens
Mirror to Codeberg / mirror-to-codeberg (push) Canceled after 0s
Mirror to Codeberg / mirror-to-codeberg (push) Canceled after 0s
This commit is contained in:
19 files changed
+108
-27
No files matched your search
+20
-8
@@ -255,16 +255,28 @@ 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
|
"""
|
||||||
major = struct.unpack("<L", data.read(4))[0] >> 0 & 0xFFFF
|
Note: structure differs from standard VS_FIXEDFILEINFO
|
||||||
build = struct.unpack("<L", data.read(4))[0] >> 0 & 0xFFFF
|
Major: lower 16 bits of first DWORD
|
||||||
# NOTE: 0-pad revision number
|
Minor: upper 16 bits of first DWORD
|
||||||
revision = str(struct.unpack("<L", data.read(4))[0] >> 16 & 0xFFFF)
|
Build: lower 16 bits of third DWORD
|
||||||
offset = 3 - len(revision)
|
Revision: upper 16 bits of second DWORD, zero-padded
|
||||||
padded = "0" * offset + revision
|
Lower 16 bits of revision DWORD appear unused.
|
||||||
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:
|
||||||
|
|||||||
@@ -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 3:
|
case 4:
|
||||||
dlc = strings.dlc_badlands
|
dlc = strings.dlc_badlands
|
||||||
case _:
|
case _:
|
||||||
dlc = strings.unspecified
|
dlc = strings.unspecified
|
||||||
|
|||||||
+21
-3
@@ -1,13 +1,14 @@
|
|||||||
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, 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
|
||||||
@@ -20,6 +21,7 @@ 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,
|
||||||
@@ -245,9 +247,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
|
||||||
@@ -439,6 +441,22 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ 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"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ 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,
|
||||||
@@ -84,6 +85,7 @@ 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
|
||||||
@@ -169,6 +171,7 @@ 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:
|
||||||
@@ -210,6 +213,7 @@ 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,
|
||||||
|
|||||||
@@ -22,11 +22,12 @@ 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_nonascii: False,
|
strings.filter_modded: True,
|
||||||
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_modded: True,
|
strings.filter_nonascii: False,
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: strings
|
# TODO: strings
|
||||||
@@ -43,6 +44,7 @@ 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 = ""
|
||||||
|
|||||||
@@ -243,6 +243,8 @@ 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:
|
||||||
|
|||||||
@@ -47,3 +47,7 @@ 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."
|
||||||
|
)
|
||||||
@@ -156,6 +156,7 @@ 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"
|
||||||
|
|||||||
@@ -279,6 +279,8 @@ 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
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
|
|||||||
authors = [
|
authors = [
|
||||||
{name = "aclist"}
|
{name = "aclist"}
|
||||||
]
|
]
|
||||||
version = "7.0.0b26"
|
version = "7.0.0b28"
|
||||||
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
@@ -8,6 +8,6 @@
|
|||||||
"WantsOfflineMode" "0"
|
"WantsOfflineMode" "0"
|
||||||
"SkipOfflineModeWarning" "0"
|
"SkipOfflineModeWarning" "0"
|
||||||
"AutoLogin" "1"
|
"AutoLogin" "1"
|
||||||
"Timestamp" "0"
|
"timestamp" "0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
@@ -9,6 +9,6 @@
|
|||||||
"SkipOfflineModeWarning" "0"
|
"SkipOfflineModeWarning" "0"
|
||||||
"AllowAutoLogin" "1"
|
"AllowAutoLogin" "1"
|
||||||
"MostRecent" "1"
|
"MostRecent" "1"
|
||||||
"Timestamp" "0"
|
"timestamp" "0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 TestWidget(ColorAwareApp, Gtk.Button):
|
class DummyWidget(ColorAwareApp, Gtk.Button):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@@ -22,11 +22,10 @@ class TestWidget(ColorAwareApp, Gtk.Button):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def widget() -> TestWidget:
|
def widget() -> DummyWidget:
|
||||||
return TestWidget()
|
return DummyWidget()
|
||||||
|
|
||||||
|
|
||||||
@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(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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
|
||||||
@@ -25,3 +26,11 @@ 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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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
|
||||||
Reference in new issue
Block a user