mirror of
https://github.com/aclist/dztui.git
synced 2026-10-25 12:12:31 +01:00
Compare commits
32
Commits
7.0.0b25
...
3c52072eb5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c52072eb5 | ||
|
|
bf69936891 | ||
|
|
fa180b750a | ||
|
|
96d50ffafa | ||
|
|
6c801ffec5 | ||
|
|
5eb70a1eb8 | ||
|
|
a3fff40f8f | ||
|
|
343630d2da | ||
|
|
d63c7ce3ec | ||
|
|
f7ad357e6f | ||
|
|
035a01fe34 | ||
|
|
7dd14804aa | ||
|
|
1cc1ba2864 | ||
|
|
8c4d0aecdc | ||
|
|
616f18452a | ||
|
|
1064f7a9d6 | ||
|
|
b0e85f0e35 | ||
|
|
4d02604bcf | ||
|
|
ebffbe39f2 | ||
|
|
61860f6aee | ||
|
|
191776ce74 | ||
|
|
7ae34747e9 | ||
|
|
1555ba2d64 | ||
|
|
4bac804fd7 | ||
|
|
748e1870da | ||
|
|
cd4c6fdfb2 | ||
|
|
1752018331 | ||
|
|
b3b05918b7 | ||
|
|
e6e0a3036d | ||
|
|
8c44a6e255 | ||
|
|
7fc1264b65 | ||
|
|
f748b09ba7 |
No files matched your search
+20
-8
@@ -255,16 +255,28 @@ class PeFileError(Exception):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def parse_version_number(data: BinaryIO) -> FileVersion:
|
||||
# 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
|
||||
build = struct.unpack("<L", data.read(4))[0] >> 0 & 0xFFFF
|
||||
# NOTE: 0-pad revision number
|
||||
revision = str(struct.unpack("<L", data.read(4))[0] >> 16 & 0xFFFF)
|
||||
offset = 3 - len(revision)
|
||||
padded = "0" * offset + revision
|
||||
return FileVersion(major, minor, build, padded)
|
||||
"""
|
||||
Note: structure differs from standard VS_FIXEDFILEINFO
|
||||
Major: lower 16 bits of first DWORD
|
||||
Minor: upper 16 bits of first DWORD
|
||||
Build: lower 16 bits of third DWORD
|
||||
Revision: upper 16 bits of second DWORD, zero-padded
|
||||
Lower 16 bits of revision DWORD appear unused.
|
||||
"""
|
||||
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:
|
||||
|
||||
@@ -346,7 +346,7 @@ def get_details(record: Record) -> Details | None:
|
||||
dlc = strings.dlc_none
|
||||
case 2:
|
||||
dlc = strings.dlc_frostline
|
||||
case 3:
|
||||
case 4:
|
||||
dlc = strings.dlc_badlands
|
||||
case _:
|
||||
dlc = strings.unspecified
|
||||
|
||||
+44
-3
@@ -1,12 +1,14 @@
|
||||
import logging
|
||||
import os
|
||||
import psutil
|
||||
import re
|
||||
import requests
|
||||
import subprocess
|
||||
import vdf # type: ignore
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Union
|
||||
from requests.exceptions import HTTPError
|
||||
from typing import Any, Generator, Union
|
||||
from warnings import deprecated
|
||||
|
||||
from dzgui.api.acf import ACF
|
||||
@@ -19,6 +21,7 @@ from dzgui.const.constants import (
|
||||
DAYZ_LAUNCHER,
|
||||
DEBIAN_STEAM_PATH,
|
||||
DEFAULT_STEAM_PATH,
|
||||
DOWNLOADS_PATH,
|
||||
FLATPAK_STEAM_PATH,
|
||||
LIBRARYFOLDERS_PATH,
|
||||
UBUNTU_STEAM_PATH,
|
||||
@@ -32,6 +35,7 @@ from dzgui.const.endpoints import (
|
||||
UNSUB_ENDPOINT,
|
||||
)
|
||||
from dzgui.strings import wizard
|
||||
from dzgui.strings.errors import api_validation_error, workshop_endpoint_error
|
||||
from dzgui.util.bash import concat_bash_args
|
||||
|
||||
logger = logging.getLogger(APP_NAME)
|
||||
@@ -243,9 +247,9 @@ def find_user_id(path: Path) -> str | None:
|
||||
last_user = str(next(iter(users)))
|
||||
if len(users) == 1:
|
||||
return last_user
|
||||
last_stamp = int(users[last_user]["Timestamp"])
|
||||
last_stamp = int(users[last_user]["timestamp"])
|
||||
for user in users:
|
||||
stamp = int(users[user]["Timestamp"])
|
||||
stamp = int(users[user]["timestamp"])
|
||||
if stamp > last_stamp:
|
||||
last_stamp = stamp
|
||||
last_user = user
|
||||
@@ -262,6 +266,16 @@ def find_user_id_32(path: Path) -> int:
|
||||
return int(uid) & 0xFFFFFFFF
|
||||
|
||||
|
||||
class SteamAPIKeyError(Exception):
|
||||
def __init__(self, msg: str | None = None):
|
||||
super().__init__(msg or api_validation_error)
|
||||
|
||||
|
||||
class SteamWorkshopEndpointError(Exception):
|
||||
def __init__(self, msg: str | None = None):
|
||||
super().__init__(msg or workshop_endpoint_error)
|
||||
|
||||
|
||||
def update_workshop(key: str, mod: int, endpoint: str) -> None:
|
||||
payload: dict[str, Union[int, str]] = {
|
||||
"publishedfileid": mod,
|
||||
@@ -273,8 +287,19 @@ def update_workshop(key: str, mod: int, endpoint: str) -> None:
|
||||
try:
|
||||
res = requests.post(endpoint, params=payload, timeout=REQUEST_TIMEOUT)
|
||||
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:
|
||||
logger.critical(e)
|
||||
raise e
|
||||
|
||||
|
||||
def subscribe(key: str, mod: int) -> None:
|
||||
@@ -416,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)
|
||||
|
||||
|
||||
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:
|
||||
app_path = None
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ DAYZ_BINARY = "DayZ_x64.exe"
|
||||
DAYZ_LAUNCHER = "DayZLauncher.exe"
|
||||
|
||||
LIBRARYFOLDERS_PATH = "steamapps/libraryfolders.vdf"
|
||||
DOWNLOADS_PATH = "steamapps/downloading"
|
||||
WORKSHOP_PATH = "steamapps/workshop/content/" + str(APPID_DAYZ)
|
||||
|
||||
STEAM_CMD = "steam"
|
||||
|
||||
@@ -541,8 +541,8 @@ class Controller(GObject.GObject):
|
||||
def update_status(self) -> None:
|
||||
self.mediator.preconnect.mark_finished()
|
||||
|
||||
def get_steam_client_name(self) -> str:
|
||||
return self.mediator.options.get_client_name()
|
||||
def get_steam_client_name(self, cmd: str) -> str:
|
||||
return self.mediator.options.get_client_name(cmd)
|
||||
|
||||
def present_window(self) -> None:
|
||||
self.mediator.window.present_with_time(Gdk.CURRENT_TIME)
|
||||
|
||||
+9
-4
@@ -16,12 +16,15 @@ logger = logging.getLogger(APP_NAME)
|
||||
|
||||
|
||||
def is_steam_running(cmd: str) -> bool:
|
||||
if cmd == STEAM_CMD:
|
||||
is_flatpak_running = is_flatpak_steam_running()
|
||||
if cmd in (FLATPAK_RUN_CMD, FLATPAK_SANDBOX):
|
||||
return is_flatpak_running
|
||||
elif cmd == STEAM_CMD:
|
||||
if is_flatpak_running:
|
||||
return False
|
||||
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()
|
||||
else:
|
||||
raise TypeError("Not a valid Steam client selection")
|
||||
|
||||
@@ -31,7 +34,9 @@ def is_steam_running(cmd: str) -> bool:
|
||||
def is_flatpak_steam_running() -> bool:
|
||||
if has_cmd(FLATPAK_CMD) is False:
|
||||
return False
|
||||
proc = subprocess.check_output([FLATPAK_CMD, "ps"], text=True)
|
||||
proc = subprocess.check_output(
|
||||
[FLATPAK_CMD, "ps", "--columns=application"], text=True
|
||||
)
|
||||
lines = proc.splitlines()
|
||||
if FLATPAK_APPID in lines:
|
||||
return True
|
||||
|
||||
@@ -14,6 +14,7 @@ from dzgui.api.shortcuts import Shortcuts
|
||||
from dzgui.api.steam import (
|
||||
connect,
|
||||
connect_debug,
|
||||
is_dayz_downloading,
|
||||
get_app_allows_downloads,
|
||||
get_app_name,
|
||||
get_needs_update,
|
||||
@@ -48,7 +49,7 @@ from dzgui.strings.dialogs import (
|
||||
)
|
||||
from dzgui.strings import kb
|
||||
from dzgui.strings.server_mods import checkmark, resync
|
||||
from dzgui.util.format import format_mib
|
||||
from dzgui.util.format import format_exception, format_mib
|
||||
from dzgui.util.strings import dialog, server_timeout
|
||||
from dzgui.util.symlink import rebuild_symlinks
|
||||
from dzgui.views.dialogs.generic import ExceptionDialog
|
||||
@@ -84,6 +85,7 @@ class Prerequisites:
|
||||
available_space: float
|
||||
passworded: bool
|
||||
dayz_running: bool
|
||||
download_paused: bool
|
||||
steam_proc: SteamProcess
|
||||
mods: list[list[str]]
|
||||
game_mode: bool
|
||||
@@ -169,6 +171,7 @@ class ConnectionManager:
|
||||
free_mib = format_mib(free)
|
||||
|
||||
dayz_running = is_dayz_running()
|
||||
download_paused = is_dayz_downloading(steam_path, self.appid)
|
||||
|
||||
running_app = get_running_app()
|
||||
if running_app is not None:
|
||||
@@ -184,8 +187,8 @@ class ConnectionManager:
|
||||
else:
|
||||
allows_downloads = (True, "")
|
||||
|
||||
client_name = self.controller.get_steam_client_name()
|
||||
client = self.controller.query_config(Preferences.CLIENT)
|
||||
client_name = self.controller.get_steam_client_name(client)
|
||||
running = is_steam_running(client)
|
||||
steam_proc = SteamProcess(client_name, running)
|
||||
self.client = client
|
||||
@@ -210,6 +213,7 @@ class ConnectionManager:
|
||||
available_space=free_mib,
|
||||
passworded=info.password_protected,
|
||||
dayz_running=dayz_running,
|
||||
download_paused=download_paused,
|
||||
steam_proc=steam_proc,
|
||||
mods=remote_mods,
|
||||
game_mode=game_mode,
|
||||
@@ -300,6 +304,11 @@ class ConnectionManager:
|
||||
dialog = ServerDetailsDialog(self.controller, details)
|
||||
dialog.run()
|
||||
|
||||
def _workshop_timeout(self, msg: str) -> None:
|
||||
dialog = ExceptionDialog(self.controller, msg)
|
||||
dialog.show_all()
|
||||
dialog.run()
|
||||
|
||||
def _server_timeout(self) -> None:
|
||||
dialog = ExceptionDialog(self.controller, server_timeout)
|
||||
dialog.set_details_buffer(kb.DZG_006)
|
||||
@@ -351,8 +360,14 @@ class ConnectionManager:
|
||||
for title, mod, stamp, size in self.missing_mods:
|
||||
if self.controller.is_cancel_pending():
|
||||
return
|
||||
subscribe(key, int(mod))
|
||||
time.sleep(API_RATE_LIMIT)
|
||||
try:
|
||||
subscribe(key, int(mod))
|
||||
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)
|
||||
for title, mod, stamp, size in self.missing_mods:
|
||||
|
||||
@@ -22,11 +22,12 @@ class FilterManager:
|
||||
strings.filter_night: True,
|
||||
strings.filter_full: show_full,
|
||||
strings.filter_official: True,
|
||||
strings.filter_nonascii: False,
|
||||
strings.filter_modded: True,
|
||||
strings.filter_lowpop: True,
|
||||
strings.filter_unofficial: True,
|
||||
strings.filter_unmodded: True,
|
||||
strings.filter_duplicate: False,
|
||||
strings.filter_modded: True,
|
||||
strings.filter_nonascii: False,
|
||||
}
|
||||
|
||||
# TODO: strings
|
||||
@@ -43,6 +44,7 @@ class FilterManager:
|
||||
strings.filter_unofficial: "Third-party servers",
|
||||
strings.filter_duplicate: "Duplicates of existing servers (usually spoofed)",
|
||||
strings.filter_modded: "Servers with one or more mods",
|
||||
strings.filter_unmodded: "Servers with no mods",
|
||||
}
|
||||
|
||||
self.active_keyword = ""
|
||||
|
||||
@@ -122,7 +122,11 @@ class ModManager:
|
||||
def unsub_atomic_mod(self, mod: str) -> None:
|
||||
config_man = self.controller.get_config_man()
|
||||
key = config_man.lookup(Preferences.STEAM)
|
||||
unsubscribe(key, int(mod))
|
||||
try:
|
||||
unsubscribe(key, int(mod))
|
||||
except Exception as e:
|
||||
logger.critical(e)
|
||||
return
|
||||
|
||||
steam_path = Path(self.path)
|
||||
app_path = PeFile.get_nested_app_path(steam_path, APPID_DAYZ)
|
||||
|
||||
@@ -243,6 +243,8 @@ class ProxyModelManager:
|
||||
rows = [row for row in rows if (row[4] / row[5] * 100) > 30]
|
||||
case strings.filter_modded:
|
||||
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
|
||||
|
||||
def filter_toggle_on(self, filters: tuple[str]) -> list:
|
||||
|
||||
@@ -4,3 +4,4 @@ api_validation_error = (
|
||||
|
||||
api_popover = "API key validation failed."
|
||||
dayz_not_found = "Could not find a valid DayZ installation."
|
||||
workshop_endpoint_error = "Steam Workshop service unavailable, try again later."
|
||||
@@ -47,3 +47,7 @@ running_app = (
|
||||
"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."
|
||||
)
|
||||
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_unofficial = "Unofficial"
|
||||
filter_modded = "Modded"
|
||||
filter_unmodded = "Unmodded"
|
||||
|
||||
# maps
|
||||
all_maps = "All maps"
|
||||
|
||||
+1
-8
@@ -1,6 +1,5 @@
|
||||
import logging
|
||||
import signal
|
||||
import warnings
|
||||
|
||||
from typing import TYPE_CHECKING, Literal, Self
|
||||
|
||||
@@ -47,10 +46,6 @@ if TYPE_CHECKING:
|
||||
|
||||
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):
|
||||
def __init__(self, app: Gtk.Application) -> None:
|
||||
@@ -344,8 +339,6 @@ 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)
|
||||
logger.warning(strings.steam_icon_missing)
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
warnings.warn(strings.gtk_theme_missing, stacklevel=2)
|
||||
@@ -15,7 +15,7 @@ class ClientCombo(Gtk.ComboBox):
|
||||
super().__init__()
|
||||
|
||||
self.client_store = Gtk.ListStore(str, str)
|
||||
clients = (
|
||||
self.clients = (
|
||||
(
|
||||
options.steam_combo,
|
||||
STEAM_CMD,
|
||||
@@ -29,7 +29,7 @@ class ClientCombo(Gtk.ComboBox):
|
||||
FLATPAK_SANDBOX,
|
||||
),
|
||||
)
|
||||
for client in clients:
|
||||
for client in self.clients:
|
||||
self.client_store.append(client)
|
||||
self.set_model(self.client_store)
|
||||
renderer_text = Gtk.CellRendererText()
|
||||
@@ -37,6 +37,13 @@ class ClientCombo(Gtk.ComboBox):
|
||||
self.add_attribute(renderer_text, "text", 0)
|
||||
self.set_active(0)
|
||||
|
||||
def get_client_human_readable(self, cmd: str) -> str:
|
||||
for client in self.clients:
|
||||
human, command = client
|
||||
if command == cmd:
|
||||
return human
|
||||
raise TypeError("Not a valid Steam client selection")
|
||||
|
||||
class ErrorPopover(Gtk.Popover):
|
||||
def __init__(self, relative_to: Gtk.Widget, position: Gtk.PositionType) -> None:
|
||||
super().__init__(relative_to=relative_to, position=position)
|
||||
|
||||
@@ -76,7 +76,7 @@ class SubmitField(Gtk.Box):
|
||||
self.button = Gtk.Button(label=options.save_button)
|
||||
|
||||
self.button.connect("clicked", self._on_save_clicked)
|
||||
self.entry.connect("insert-text", self._on_text_typed)
|
||||
self.entry.connect("changed", self._on_text_typed)
|
||||
self.entry.connect("activate", self._on_field_activated)
|
||||
self.entry.get_property("buffer").connect("deleted-text", self._on_text_deleted)
|
||||
|
||||
@@ -113,13 +113,9 @@ class SubmitField(Gtk.Box):
|
||||
def _on_text_typed(
|
||||
self,
|
||||
entry: Gtk.Entry,
|
||||
text: str,
|
||||
length: int,
|
||||
pos: int,
|
||||
) -> None:
|
||||
|
||||
buffer = entry.get_property("buffer")
|
||||
text = buffer.get_text() + text
|
||||
text = entry.get_text()
|
||||
state = self._is_valid_text(text)
|
||||
self.button.set_sensitive(state)
|
||||
|
||||
@@ -371,10 +367,8 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||
|
||||
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 get_client_name(self, cmd: str) -> str:
|
||||
return self.client_combo.get_client_human_readable(cmd)
|
||||
|
||||
def block_text_entry(self) -> None:
|
||||
self.steam_box.block_text_entry()
|
||||
|
||||
@@ -279,6 +279,8 @@ class PreConnectionAssistant(Gtk.Box):
|
||||
warnings.append(preconnect.protected_server)
|
||||
if prereqs.dayz_running is True:
|
||||
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
|
||||
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 = [
|
||||
{name = "aclist"}
|
||||
]
|
||||
version = "7.0.0b25"
|
||||
version = "7.0.0b28"
|
||||
license = "GPL-3.0-or-later"
|
||||
license-files = ["LICENSE"]
|
||||
readme = "README.md"
|
||||
|
||||
+1
-1
@@ -8,6 +8,6 @@
|
||||
"WantsOfflineMode" "0"
|
||||
"SkipOfflineModeWarning" "0"
|
||||
"AutoLogin" "1"
|
||||
"Timestamp" "0"
|
||||
"timestamp" "0"
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
"WantsOfflineMode" "0"
|
||||
"SkipOfflineModeWarning" "0"
|
||||
"AutoLogin" "1"
|
||||
"Timestamp" "1787904411"
|
||||
"timestamp" "1787904411"
|
||||
}
|
||||
"1"
|
||||
{
|
||||
@@ -18,6 +18,6 @@
|
||||
"WantsOfflineMode" "0"
|
||||
"SkipOfflineModeWarning" "0"
|
||||
"AutoLogin" "1"
|
||||
"Timestamp" "1787904647"
|
||||
"timestamp" "1787904647"
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
"WantsOfflineMode" "0"
|
||||
"SkipOfflineModeWarning" "0"
|
||||
"AutoLogin" "1"
|
||||
"Timestamp" "1797904411"
|
||||
"timestamp" "1797904411"
|
||||
}
|
||||
"1"
|
||||
{
|
||||
@@ -18,6 +18,6 @@
|
||||
"WantsOfflineMode" "0"
|
||||
"SkipOfflineModeWarning" "0"
|
||||
"AutoLogin" "1"
|
||||
"Timestamp" "1787904647"
|
||||
"timestamp" "1787904647"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -9,6 +9,6 @@
|
||||
"SkipOfflineModeWarning" "0"
|
||||
"AllowAutoLogin" "1"
|
||||
"MostRecent" "1"
|
||||
"Timestamp" "0"
|
||||
"timestamp" "0"
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
"SkipOfflineModeWarning" "0"
|
||||
"AllowAutoLogin" "1"
|
||||
"MostRecent" "0"
|
||||
"Timestamp" "0"
|
||||
"timestamp" "0"
|
||||
}
|
||||
"1"
|
||||
{
|
||||
@@ -20,6 +20,6 @@
|
||||
"SkipOfflineModeWarning" "0"
|
||||
"AllowAutoLogin" "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
|
||||
|
||||
|
||||
class TestWidget(ColorAwareApp, Gtk.Button):
|
||||
class DummyWidget(ColorAwareApp, Gtk.Button):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
@@ -22,11 +22,10 @@ class TestWidget(ColorAwareApp, Gtk.Button):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def widget() -> TestWidget:
|
||||
return TestWidget()
|
||||
def widget() -> DummyWidget:
|
||||
return DummyWidget()
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.parametrize("key, expect", [("default", False), ("prefer-dark", True)])
|
||||
def test_light_mode(widget, key: str, expect: bool) -> None:
|
||||
local_pref = subprocess.run(
|
||||
|
||||
@@ -3,6 +3,7 @@ import pytest
|
||||
from pathlib import Path
|
||||
|
||||
from dzgui.api.steam import find_user_id
|
||||
from dzgui.const.constants import DEFAULT_STEAM_PATH
|
||||
from tests.fixtures import fixture_path
|
||||
|
||||
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)
|
||||
uid = find_user_id(Path(""))
|
||||
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
|
||||
@@ -0,0 +1,61 @@
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from dzgui.const.constants import (
|
||||
FLATPAK_APPID,
|
||||
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 running in (True, False):
|
||||
with (
|
||||
self.subTest(running=running),
|
||||
patch.object(proc, "is_running", return_value=running),
|
||||
):
|
||||
self.assertEqual(proc.is_steam_running(STEAM_CMD), running)
|
||||
|
||||
def test_native_steam_while_flatpak_is_running(self):
|
||||
with patch.object(proc, "is_flatpak_steam_running", return_value=True):
|
||||
self.assertEqual(proc.is_steam_running(STEAM_CMD), False)
|
||||
|
||||
def test_invalid_client(self):
|
||||
with self.assertRaises(TypeError):
|
||||
proc.is_steam_running("invalid-client")
|
||||
@@ -14,7 +14,11 @@ from dzgui.views.dialogs.calc import (
|
||||
|
||||
|
||||
class DummyEmitter:
|
||||
pass
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def connect(self, _, __) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture(params=[("05:00", 10.0), ("15:30", 5.0)])
|
||||
|
||||
Reference in new issue
Block a user