mirror of
https://github.com/aclist/dztui.git
synced 2026-10-25 12:12:31 +01:00
Compare commits
61
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
141f96fb9f | ||
|
|
0359aacd54 | ||
|
|
80e127889e | ||
|
|
da98b1d5b1 | ||
|
|
33869158de | ||
|
|
5cb8c68f5c | ||
|
|
2f92c2a44f | ||
|
|
da48834fa7 | ||
|
|
197c2eba06 | ||
|
|
0131ee6f0e | ||
|
|
b70edebd05 | ||
|
|
ae1c3a19d4 | ||
|
|
7822672cda | ||
|
|
f2a7bde432 | ||
|
|
00b5476aaf | ||
|
|
2a08b9ba82 | ||
|
|
0f8b6fcaad | ||
|
|
95988459d5 | ||
|
|
eed40e6f1a | ||
|
|
07460a1f8f | ||
|
|
a74d576387 | ||
|
|
052f7f0708 | ||
|
|
6feae44887 | ||
|
|
8cdb615796 | ||
|
|
31b6705017 | ||
|
|
ef1e7d17f5 | ||
|
|
3c52072eb5 | ||
|
|
bf69936891 | ||
|
|
fa180b750a | ||
|
|
96d50ffafa | ||
|
|
6c801ffec5 | ||
|
|
5eb70a1eb8 | ||
|
|
a3fff40f8f | ||
|
|
343630d2da | ||
|
|
d63c7ce3ec | ||
|
|
f7ad357e6f | ||
|
|
035a01fe34 | ||
|
|
7dd14804aa | ||
|
|
1cc1ba2864 | ||
|
|
8c4d0aecdc | ||
|
|
616f18452a | ||
|
|
1064f7a9d6 | ||
|
|
b0e85f0e35 | ||
|
|
4eec00e8e7 | ||
|
|
09ef3e446a | ||
|
|
4d02604bcf | ||
|
|
ebffbe39f2 | ||
|
|
61860f6aee | ||
|
|
191776ce74 | ||
|
|
7ae34747e9 | ||
|
|
1555ba2d64 | ||
|
|
4bac804fd7 | ||
|
|
748e1870da | ||
|
|
cd4c6fdfb2 | ||
|
|
1752018331 | ||
|
|
b3b05918b7 | ||
|
|
e6e0a3036d | ||
|
|
8c44a6e255 | ||
|
|
7fc1264b65 | ||
|
|
f748b09ba7 | ||
|
|
01be1247b4 |
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
|
||||||
|
|||||||
+46
-5
@@ -1,16 +1,18 @@
|
|||||||
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 typing import Any, Union
|
from requests.exceptions import HTTPError
|
||||||
|
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
|
||||||
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,
|
||||||
@@ -19,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,
|
||||||
@@ -32,6 +35,7 @@ from dzgui.const.endpoints import (
|
|||||||
UNSUB_ENDPOINT,
|
UNSUB_ENDPOINT,
|
||||||
)
|
)
|
||||||
from dzgui.strings import wizard
|
from dzgui.strings import wizard
|
||||||
|
from dzgui.strings.errors import api_validation_error, workshop_endpoint_error
|
||||||
from dzgui.util.bash import concat_bash_args
|
from dzgui.util.bash import concat_bash_args
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
@@ -57,7 +61,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:
|
||||||
@@ -243,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
|
||||||
@@ -262,6 +266,16 @@ def find_user_id_32(path: Path) -> int:
|
|||||||
return int(uid) & 0xFFFFFFFF
|
return int(uid) & 0xFFFFFFFF
|
||||||
|
|
||||||
|
|
||||||
|
class SteamAPIKeyError(Exception):
|
||||||
|
def __init__(self, msg: str | None = None) -> None:
|
||||||
|
super().__init__(msg or api_validation_error)
|
||||||
|
|
||||||
|
|
||||||
|
class SteamWorkshopEndpointError(Exception):
|
||||||
|
def __init__(self, msg: str | None = None) -> None:
|
||||||
|
super().__init__(msg or workshop_endpoint_error)
|
||||||
|
|
||||||
|
|
||||||
def update_workshop(key: str, mod: int, endpoint: str) -> None:
|
def update_workshop(key: str, mod: int, endpoint: str) -> None:
|
||||||
payload: dict[str, Union[int, str]] = {
|
payload: dict[str, Union[int, str]] = {
|
||||||
"publishedfileid": mod,
|
"publishedfileid": mod,
|
||||||
@@ -273,8 +287,19 @@ def update_workshop(key: str, mod: int, endpoint: str) -> None:
|
|||||||
try:
|
try:
|
||||||
res = requests.post(endpoint, params=payload, timeout=REQUEST_TIMEOUT)
|
res = requests.post(endpoint, params=payload, timeout=REQUEST_TIMEOUT)
|
||||||
res.raise_for_status()
|
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:
|
except Exception as e:
|
||||||
logger.critical(e)
|
logger.critical(e)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def subscribe(key: str, mod: int) -> None:
|
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)
|
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
|
||||||
|
|
||||||
|
|||||||
+30
-4
@@ -1,10 +1,13 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
from warnings import deprecated
|
||||||
|
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME
|
from dzgui.const.constants import APP_NAME, STEAM_ICON, STEAM_ICONS_PATH
|
||||||
from dzgui.const.enum import Preferences
|
from dzgui.const.enum import Preferences
|
||||||
from dzgui.config.query import lookup
|
from dzgui.config.query import lookup
|
||||||
from dzgui.config.userprefs import UserPrefs
|
from dzgui.config.userprefs import UserPrefs
|
||||||
@@ -15,7 +18,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
|
||||||
@@ -23,7 +26,7 @@ from dzgui.util.dirs import make_parents
|
|||||||
from dzgui.util.localize import set_locale
|
from dzgui.util.localize import set_locale
|
||||||
from dzgui.util.map_count import test_map_count
|
from dzgui.util.map_count import test_map_count
|
||||||
from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS
|
from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS
|
||||||
from dzgui.util.strings import init
|
from dzgui.util.strings import init, steam_icon_missing
|
||||||
|
|
||||||
from dzgui.views.base import App
|
from dzgui.views.base import App
|
||||||
from dzgui.views.dialogs.boot import BootWindow
|
from dzgui.views.dialogs.boot import BootWindow
|
||||||
@@ -96,6 +99,28 @@ def copy_bare_configs(config: "Path", resolution: "Path") -> tuple[bool, bool]:
|
|||||||
return (config_changed, state_changed)
|
return (config_changed, state_changed)
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated("Currently unused")
|
||||||
|
def load_steam_icon(config: Path) -> None:
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk # noqa
|
||||||
|
|
||||||
|
default = lookup(config, Preferences.DEFAULT)
|
||||||
|
icon_path = Path(default).joinpath(STEAM_ICONS_PATH)
|
||||||
|
try:
|
||||||
|
theme = Gtk.IconTheme.get_default()
|
||||||
|
paths = theme.get_search_path()
|
||||||
|
theme.set_search_path(paths + [str(icon_path)])
|
||||||
|
paths = theme.get_search_path()
|
||||||
|
icons = theme.list_icons(None)
|
||||||
|
if STEAM_ICON not in icons:
|
||||||
|
logger.warning(steam_icon_missing)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
logger.warning(e)
|
||||||
|
|
||||||
|
|
||||||
def load_gui(version: str, is_debug: bool) -> None:
|
def load_gui(version: str, is_debug: bool) -> None:
|
||||||
lock = lock_acquire() # noqa
|
lock = lock_acquire() # noqa
|
||||||
|
|
||||||
@@ -136,13 +161,14 @@ 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)
|
||||||
local_coords, latest_release = bootwin.get_results()
|
local_coords, latest_release = bootwin.get_results()
|
||||||
|
|
||||||
use_miles = lookup(XDG.config, Preferences.DIST)
|
use_miles = lookup(XDG.config, Preferences.DIST)
|
||||||
|
|
||||||
prefs = UserPrefs(
|
prefs = UserPrefs(
|
||||||
is_steam_deck=_is_steam_deck,
|
is_steam_deck=_is_steam_deck,
|
||||||
is_game_mode=_is_game_mode,
|
is_game_mode=_is_game_mode,
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ from pathlib import Path
|
|||||||
from dzgui.util._json import read_json
|
from dzgui.util._json import read_json
|
||||||
|
|
||||||
from dzgui.const.enum import Preferences
|
from dzgui.const.enum import Preferences
|
||||||
from dzgui.const.constants import STEAM_CMD, FLATPAK_RUN_CMD, FLATPAK_SANDBOX
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigFileError(Exception):
|
class ConfigFileError(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -40,23 +38,5 @@ def get_favorites(path: Path) -> list[str]:
|
|||||||
return list(conf["ip_list"])
|
return list(conf["ip_list"])
|
||||||
|
|
||||||
|
|
||||||
# def is_in_favs(record: str, path: Path) -> bool:
|
|
||||||
# favs = get_favorites(path)
|
|
||||||
# if record in favs:
|
|
||||||
# return True
|
|
||||||
# return False
|
|
||||||
|
|
||||||
|
|
||||||
def enum_to_key(enum: Preferences) -> str:
|
def enum_to_key(enum: Preferences) -> str:
|
||||||
return str(enum.dict["key"])
|
return str(enum.dict["key"])
|
||||||
|
|
||||||
|
|
||||||
def get_client_index(client: str) -> int:
|
|
||||||
if client == STEAM_CMD:
|
|
||||||
return 0
|
|
||||||
elif client == FLATPAK_RUN_CMD:
|
|
||||||
return 1
|
|
||||||
elif client == FLATPAK_SANDBOX:
|
|
||||||
return 2
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
@@ -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"
|
||||||
@@ -84,9 +85,11 @@ HERO_PATH = "data/images/_hero.png"
|
|||||||
CSS_PATH = "data/app.css"
|
CSS_PATH = "data/app.css"
|
||||||
VDF_PATH = "steamapps/libraryfolders.vdf"
|
VDF_PATH = "steamapps/libraryfolders.vdf"
|
||||||
DEFAULT_STEAM_PATH = ".local/share/Steam"
|
DEFAULT_STEAM_PATH = ".local/share/Steam"
|
||||||
FLATPAK_STEAM_PATH = ".var/app/com.valvesoftware.Steam/data/Steam"
|
FLATPAK_ROOT_PATH = ".var/app/com.valvesoftware.Steam"
|
||||||
|
FLATPAK_STEAM_PATH = FLATPAK_ROOT_PATH + "/data/Steam"
|
||||||
UBUNTU_STEAM_PATH = ".steam/steam"
|
UBUNTU_STEAM_PATH = ".steam/steam"
|
||||||
DEBIAN_STEAM_PATH = ".steam/debian-installation"
|
DEBIAN_STEAM_PATH = ".steam/debian-installation"
|
||||||
|
STEAM_ICONS_PATH = "public"
|
||||||
|
|
||||||
LOG_FILTERS = ("CRITICAL", "WARNING", "INFO", "DEBUG")
|
LOG_FILTERS = ("CRITICAL", "WARNING", "INFO", "DEBUG")
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ class Emitter(GObject.GObject):
|
|||||||
def check_toggled(self, label: str, state: bool) -> None:
|
def check_toggled(self, label: str, state: bool) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
|
||||||
|
def servers_timed_out(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(object,))
|
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(object,))
|
||||||
def servers_loaded(self, tag: "ServerTab") -> None:
|
def servers_loaded(self, tag: "ServerTab") -> None:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -541,8 +541,10 @@ class Controller(GObject.GObject):
|
|||||||
def update_status(self) -> None:
|
def update_status(self) -> None:
|
||||||
self.mediator.preconnect.mark_finished()
|
self.mediator.preconnect.mark_finished()
|
||||||
|
|
||||||
def get_steam_client_name(self) -> str:
|
def get_client_name(self) -> str:
|
||||||
return self.mediator.options.get_client_name()
|
# NOTE: only possible values are "Steam" and "Flatpak"
|
||||||
|
cmd = self.query_config(Preferences.CLIENT)
|
||||||
|
return str(cmd.split()[0].capitalize())
|
||||||
|
|
||||||
def present_window(self) -> None:
|
def present_window(self) -> None:
|
||||||
self.mediator.window.present_with_time(Gdk.CURRENT_TIME)
|
self.mediator.window.present_with_time(Gdk.CURRENT_TIME)
|
||||||
|
|||||||
+20
-7
@@ -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()
|
||||||
+32
-13
@@ -10,42 +10,61 @@ from dzgui.const.constants import (
|
|||||||
FLATPAK_CMD,
|
FLATPAK_CMD,
|
||||||
FLATPAK_RUN_CMD,
|
FLATPAK_RUN_CMD,
|
||||||
FLATPAK_SANDBOX,
|
FLATPAK_SANDBOX,
|
||||||
|
FLATPAK_ROOT_PATH,
|
||||||
|
FLATPAK_STEAM_PATH,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
def is_steam_running(cmd: str) -> bool:
|
def is_steam_running(cmd: str) -> bool:
|
||||||
if cmd == STEAM_CMD:
|
if cmd in (FLATPAK_RUN_CMD, FLATPAK_SANDBOX):
|
||||||
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()
|
return is_flatpak_steam_running()
|
||||||
|
elif cmd == STEAM_CMD:
|
||||||
|
return is_native_steam_running()
|
||||||
else:
|
else:
|
||||||
raise TypeError("Not a valid Steam client selection")
|
raise TypeError("Not a valid Steam client selection")
|
||||||
|
|
||||||
|
|
||||||
# CHORE: test alternate clients
|
|
||||||
# TODO: has_flatpak_steam
|
|
||||||
def is_flatpak_steam_running() -> bool:
|
def is_flatpak_steam_running() -> bool:
|
||||||
if has_cmd(FLATPAK_CMD) is False:
|
if has_cmd(FLATPAK_CMD) is False:
|
||||||
return 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()
|
lines = proc.splitlines()
|
||||||
if FLATPAK_APPID in lines:
|
if FLATPAK_APPID in lines:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_running(proc: str) -> bool:
|
def get_steam_procs() -> list[list[str]]:
|
||||||
for process in psutil.process_iter(["pid", "name"]):
|
return [
|
||||||
if process.info["name"] == proc:
|
p.cmdline()
|
||||||
return True
|
for p in psutil.process_iter(["pid", "name"])
|
||||||
return False
|
if STEAM_CMD in p.info["name"]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def is_native_steam_running() -> bool:
|
||||||
|
if has_cmd(STEAM_CMD) is False:
|
||||||
|
return False
|
||||||
|
substrings = [FLATPAK_ROOT_PATH, "steam-runtime-launcher-service"]
|
||||||
|
steam_procs = get_steam_procs()
|
||||||
|
native_steam_procs = [
|
||||||
|
proc
|
||||||
|
for proc in steam_procs
|
||||||
|
if not any(sub in item for item in proc for sub in substrings)
|
||||||
|
]
|
||||||
|
return bool(native_steam_procs)
|
||||||
|
|
||||||
|
|
||||||
def has_cmd(cmd: str) -> bool:
|
def has_cmd(cmd: str) -> bool:
|
||||||
if shutil.which(cmd) is not None:
|
if shutil.which(cmd) is not None:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def path_to_steam_cmd(path: str) -> str:
|
||||||
|
cmd = FLATPAK_RUN_CMD if FLATPAK_STEAM_PATH in path else STEAM_CMD
|
||||||
|
return cmd
|
||||||
@@ -6,9 +6,6 @@ from typing import Any, TYPE_CHECKING
|
|||||||
|
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import (
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
FLATPAK_RUN_CMD,
|
|
||||||
FLATPAK_SANDBOX,
|
|
||||||
STEAM_CMD,
|
|
||||||
WINDOW_DEFAULT_X,
|
WINDOW_DEFAULT_X,
|
||||||
WINDOW_DEFAULT_Y,
|
WINDOW_DEFAULT_Y,
|
||||||
)
|
)
|
||||||
@@ -129,15 +126,6 @@ class ConfigManager:
|
|||||||
def enum_to_key(self, enum: Preferences) -> str:
|
def enum_to_key(self, enum: Preferences) -> str:
|
||||||
return str(enum.dict["key"])
|
return str(enum.dict["key"])
|
||||||
|
|
||||||
def get_client_index(self, client: str) -> int:
|
|
||||||
if client == STEAM_CMD:
|
|
||||||
return 0
|
|
||||||
if client == FLATPAK_RUN_CMD:
|
|
||||||
return 1
|
|
||||||
if client == FLATPAK_SANDBOX:
|
|
||||||
return 2
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def toggle_config(self, key: Preferences) -> None:
|
def toggle_config(self, key: Preferences) -> None:
|
||||||
try:
|
try:
|
||||||
real_key = self.enum_to_key(key)
|
real_key = self.enum_to_key(key)
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -48,7 +49,7 @@ from dzgui.strings.dialogs import (
|
|||||||
)
|
)
|
||||||
from dzgui.strings import kb
|
from dzgui.strings import kb
|
||||||
from dzgui.strings.server_mods import checkmark, resync
|
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.strings import dialog, server_timeout
|
||||||
from dzgui.util.symlink import rebuild_symlinks
|
from dzgui.util.symlink import rebuild_symlinks
|
||||||
from dzgui.views.dialogs.generic import ExceptionDialog
|
from dzgui.views.dialogs.generic import ExceptionDialog
|
||||||
@@ -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:
|
||||||
@@ -184,8 +187,8 @@ class ConnectionManager:
|
|||||||
else:
|
else:
|
||||||
allows_downloads = (True, "")
|
allows_downloads = (True, "")
|
||||||
|
|
||||||
client_name = self.controller.get_steam_client_name()
|
|
||||||
client = self.controller.query_config(Preferences.CLIENT)
|
client = self.controller.query_config(Preferences.CLIENT)
|
||||||
|
client_name = self.controller.get_client_name()
|
||||||
running = is_steam_running(client)
|
running = is_steam_running(client)
|
||||||
steam_proc = SteamProcess(client_name, running)
|
steam_proc = SteamProcess(client_name, running)
|
||||||
self.client = client
|
self.client = client
|
||||||
@@ -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,
|
||||||
@@ -300,6 +304,11 @@ class ConnectionManager:
|
|||||||
dialog = ServerDetailsDialog(self.controller, details)
|
dialog = ServerDetailsDialog(self.controller, details)
|
||||||
dialog.run()
|
dialog.run()
|
||||||
|
|
||||||
|
def _workshop_timeout(self, msg: str) -> None:
|
||||||
|
dialog = ExceptionDialog(self.controller, msg)
|
||||||
|
dialog.show_all()
|
||||||
|
dialog.run()
|
||||||
|
|
||||||
def _server_timeout(self) -> None:
|
def _server_timeout(self) -> None:
|
||||||
dialog = ExceptionDialog(self.controller, server_timeout)
|
dialog = ExceptionDialog(self.controller, server_timeout)
|
||||||
dialog.set_details_buffer(kb.DZG_006)
|
dialog.set_details_buffer(kb.DZG_006)
|
||||||
@@ -351,8 +360,14 @@ class ConnectionManager:
|
|||||||
for title, mod, stamp, size in self.missing_mods:
|
for title, mod, stamp, size in self.missing_mods:
|
||||||
if self.controller.is_cancel_pending():
|
if self.controller.is_cancel_pending():
|
||||||
return
|
return
|
||||||
subscribe(key, int(mod))
|
try:
|
||||||
time.sleep(API_RATE_LIMIT)
|
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)
|
self.thread_man.update_dialog(waiting_for_directories)
|
||||||
for title, mod, stamp, size in self.missing_mods:
|
for title, mod, stamp, size in self.missing_mods:
|
||||||
|
|||||||
@@ -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 = ""
|
||||||
|
|||||||
@@ -122,7 +122,11 @@ class ModManager:
|
|||||||
def unsub_atomic_mod(self, mod: str) -> None:
|
def unsub_atomic_mod(self, mod: str) -> None:
|
||||||
config_man = self.controller.get_config_man()
|
config_man = self.controller.get_config_man()
|
||||||
key = config_man.lookup(Preferences.STEAM)
|
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)
|
steam_path = Path(self.path)
|
||||||
app_path = PeFile.get_nested_app_path(steam_path, APPID_DAYZ)
|
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]
|
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:
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ class ServerModelManager:
|
|||||||
job = Servers.query_api
|
job = Servers.query_api
|
||||||
params = Servers.params
|
params = Servers.params
|
||||||
servers = []
|
servers = []
|
||||||
|
|
||||||
with ThreadPoolExecutor() as executor:
|
with ThreadPoolExecutor() as executor:
|
||||||
futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
|
futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
@@ -414,6 +415,8 @@ class ServerModelManager:
|
|||||||
# TODO: disable map, keyword, and filter widgets if model is None
|
# TODO: disable map, keyword, and filter widgets if model is None
|
||||||
# signal driven (servers_empty, servers_failed_to_load)
|
# signal driven (servers_empty, servers_failed_to_load)
|
||||||
|
|
||||||
|
self.emitter.emit("servers_timed_out")
|
||||||
|
|
||||||
if self.preserve_on_fail is False:
|
if self.preserve_on_fail is False:
|
||||||
self.tv.set_model(None)
|
self.tv.set_model(None)
|
||||||
filter_man = self.tv.get_filter_man()
|
filter_man = self.tv.get_filter_man()
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ api_validation_error = (
|
|||||||
|
|
||||||
api_popover = "API key validation failed."
|
api_popover = "API key validation failed."
|
||||||
dayz_not_found = "Could not find a valid DayZ installation."
|
dayz_not_found = "Could not find a valid DayZ installation."
|
||||||
|
workshop_endpoint_error = "Steam Workshop service unavailable, try again later."
|
||||||
@@ -17,3 +17,10 @@ fullscreen_eventbox = "This option is not available on Steam Deck."
|
|||||||
save_button = "Save"
|
save_button = "Save"
|
||||||
|
|
||||||
api_failed = "API validation failed"
|
api_failed = "API validation failed"
|
||||||
|
|
||||||
|
flatpak_label = "Flatpak client"
|
||||||
|
flatpak_standard = "Standard"
|
||||||
|
flatpak_container = "Containerized (flatpak-spawn --host)"
|
||||||
|
flatpak_info = (
|
||||||
|
"Containerized mode is used on certain distributions. May have side effects."
|
||||||
|
)
|
||||||
@@ -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."
|
||||||
|
)
|
||||||
@@ -70,7 +70,8 @@ checkbox_steam_shortcut = (
|
|||||||
(
|
(
|
||||||
"This creates a standalone copy of DZGUI and adds it to Steam. "
|
"This creates a standalone copy of DZGUI and adds it to Steam. "
|
||||||
"You <b>must restart Steam</b> for the shortcut to appear. This will not replace or update existing shortcuts. "
|
"You <b>must restart Steam</b> for the shortcut to appear. This will not replace or update existing shortcuts. "
|
||||||
"If you update DZGUI when it is launched via Steam, updates will be applied to the Steam version."
|
"If you update DZGUI when it is launched via Steam, updates will be applied to the Steam version. "
|
||||||
|
"Not available if you selected the Flatpak client as your Steam default."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
checkbox_start_menu = (
|
checkbox_start_menu = (
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
+1
-18
@@ -1,6 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
import signal
|
import signal
|
||||||
import warnings
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Literal, Self
|
from typing import TYPE_CHECKING, Literal, Self
|
||||||
|
|
||||||
@@ -8,12 +7,11 @@ from dzgui.const.constants import (
|
|||||||
APP_NAME,
|
APP_NAME,
|
||||||
APP_NAME_LOWER,
|
APP_NAME_LOWER,
|
||||||
GTK_APP_ID,
|
GTK_APP_ID,
|
||||||
STEAM_ICON,
|
|
||||||
)
|
)
|
||||||
from dzgui.const.enum import NotebookPage
|
from dzgui.const.enum import NotebookPage
|
||||||
from dzgui.controllers.emitter import Emitter
|
from dzgui.controllers.emitter import Emitter
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
from dzgui.util import css, strings
|
from dzgui.util import css
|
||||||
from dzgui.util.keys import is_ctrl_mask
|
from dzgui.util.keys import is_ctrl_mask
|
||||||
from dzgui.views.components.connect_panel import ConnectPanel
|
from dzgui.views.components.connect_panel import ConnectPanel
|
||||||
from dzgui.views.components.crumbs import Breadcrumbs
|
from dzgui.views.components.crumbs import Breadcrumbs
|
||||||
@@ -47,10 +45,6 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
logger = logging.getLogger(APP_NAME)
|
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):
|
class OuterWindow(Gtk.ApplicationWindow):
|
||||||
def __init__(self, app: Gtk.Application) -> None:
|
def __init__(self, app: Gtk.Application) -> None:
|
||||||
@@ -337,15 +331,4 @@ class App(ColorAwareApp, Gtk.Application): # type: ignore
|
|||||||
) -> None:
|
) -> None:
|
||||||
self.win.halt_proc_and_quit()
|
self.win.halt_proc_and_quit()
|
||||||
|
|
||||||
|
|
||||||
MainController = Controller()
|
MainController = Controller()
|
||||||
|
|
||||||
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)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(e)
|
|
||||||
warnings.warn(strings.gtk_theme_missing, stacklevel=2)
|
|
||||||
@@ -65,6 +65,7 @@ class IconButton(Gtk.Button):
|
|||||||
self.set_image(self.icon)
|
self.set_image(self.icon)
|
||||||
self.set_image_position(position)
|
self.set_image_position(position)
|
||||||
self.set_focus_on_click(False)
|
self.set_focus_on_click(False)
|
||||||
|
self.set_always_show_image(True)
|
||||||
|
|
||||||
def swap_icon(self, icon: str) -> None:
|
def swap_icon(self, icon: str) -> None:
|
||||||
start = self.icon.get_margin_start()
|
start = self.icon.get_margin_start()
|
||||||
@@ -162,18 +163,24 @@ class RefreshButton(IconTextButton):
|
|||||||
self.set_tooltip_text(atomic_buttons.refresh_tooltip)
|
self.set_tooltip_text(atomic_buttons.refresh_tooltip)
|
||||||
|
|
||||||
self.connect("clicked", self._on_refresh_clicked)
|
self.connect("clicked", self._on_refresh_clicked)
|
||||||
emitter.connect("servers_loaded", self.start_decrement)
|
emitter.connect("servers_loaded", self._on_load)
|
||||||
|
emitter.connect("servers_timed_out", self._on_timeout)
|
||||||
|
|
||||||
|
def _on_timeout(self, emitter: "Emitter") -> None:
|
||||||
|
self.is_clicked = True
|
||||||
|
self.start_decrement()
|
||||||
|
|
||||||
|
def _on_load(self, emitter: "Emitter", tab: "ServerTab") -> None:
|
||||||
|
self.start_decrement()
|
||||||
|
|
||||||
def _on_refresh_clicked(self, button: Self) -> None:
|
def _on_refresh_clicked(self, button: Self) -> None:
|
||||||
"""Spawned in a thread"""
|
"""Spawned in a thread"""
|
||||||
if self.controller.get_active_treeview().is_loaded() is False:
|
|
||||||
return
|
|
||||||
self.is_clicked = True
|
self.is_clicked = True
|
||||||
self.controller.refresh_tree()
|
self.controller.refresh_tree()
|
||||||
# TODO: get server tab enum
|
# TODO: get server tab enum
|
||||||
# if LAN tab, reload existing entries in place
|
# if LAN tab, reload existing entries in place
|
||||||
|
|
||||||
def start_decrement(self, emitter: "Emitter", tab: "ServerTab") -> None:
|
def start_decrement(self) -> None:
|
||||||
if self.is_clicked is False:
|
if self.is_clicked is False:
|
||||||
return
|
return
|
||||||
if self.loading:
|
if self.loading:
|
||||||
|
|||||||
@@ -1,42 +1,72 @@
|
|||||||
|
from typing import Self
|
||||||
|
from warnings import deprecated
|
||||||
|
|
||||||
from dzgui.const.constants import ERROR, FLATPAK_RUN_CMD, FLATPAK_SANDBOX, STEAM_CMD
|
from dzgui.const.constants import ERROR, FLATPAK_RUN_CMD, FLATPAK_SANDBOX, STEAM_CMD
|
||||||
|
from dzgui.init.prereqs import has_steam_client, has_flatpak_client
|
||||||
from dzgui.strings import options
|
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
|
||||||
|
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk # noqa
|
from gi.repository import Gtk # noqa
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated("Client choice is determined at setup time")
|
||||||
class ClientCombo(Gtk.ComboBox):
|
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()
|
||||||
clients = (
|
has_flatpak = has_flatpak_client()
|
||||||
|
self.client_store = Gtk.ListStore(str, str, bool)
|
||||||
|
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 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:
|
||||||
|
for client in self.clients:
|
||||||
|
human, command, sens = client
|
||||||
|
if command == cmd:
|
||||||
|
return human
|
||||||
|
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)
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ from dzgui.const.constants import (
|
|||||||
APP_NAME,
|
APP_NAME,
|
||||||
APP_NAME_LOWER,
|
APP_NAME_LOWER,
|
||||||
HERO_PATH,
|
HERO_PATH,
|
||||||
|
FLATPAK_STEAM_PATH,
|
||||||
LEGACY_CONFIG_PATH,
|
LEGACY_CONFIG_PATH,
|
||||||
)
|
)
|
||||||
from dzgui.const.boilerplate import config_boilerplate
|
from dzgui.const.boilerplate import config_boilerplate
|
||||||
from dzgui.const.endpoints import STEAM_API_SETUP
|
from dzgui.const.endpoints import STEAM_API_SETUP
|
||||||
from dzgui.config import freedesktop
|
from dzgui.config import freedesktop
|
||||||
|
from dzgui.init.proc import path_to_steam_cmd
|
||||||
from dzgui.init.migrate import migrate_legacy_conf
|
from dzgui.init.migrate import migrate_legacy_conf
|
||||||
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
||||||
from dzgui.strings import wizard
|
from dzgui.strings import wizard
|
||||||
@@ -25,7 +27,6 @@ from dzgui.util.open_links import open_link_by_url
|
|||||||
from dzgui.util.css import add_class, load_css
|
from dzgui.util.css import add_class, load_css
|
||||||
from dzgui.views.components.buttons import WebButton
|
from dzgui.views.components.buttons import WebButton
|
||||||
from dzgui.views.components.entry import APIEntry
|
from dzgui.views.components.entry import APIEntry
|
||||||
from dzgui.views.components.misc import ClientCombo
|
|
||||||
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
@@ -384,26 +385,17 @@ class PreferencesPage(EnumeratedWizardPage):
|
|||||||
self.dist_box.add(self.radio_km)
|
self.dist_box.add(self.radio_km)
|
||||||
self.dist_box.add(self.radio_miles)
|
self.dist_box.add(self.radio_miles)
|
||||||
|
|
||||||
client_label = Gtk.Label(label=wizard.label_client, halign=Gtk.Align.START)
|
|
||||||
self.client_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
||||||
self.client_combo = ClientCombo()
|
|
||||||
self.client_box.add(client_label)
|
|
||||||
self.client_box.add(self.client_combo)
|
|
||||||
|
|
||||||
outer_box = Gtk.Box(
|
outer_box = Gtk.Box(
|
||||||
orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER, spacing=10
|
orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER, spacing=10
|
||||||
)
|
)
|
||||||
for el in self.name_box, self.client_box, self.dist_box:
|
for el in self.name_box, self.dist_box:
|
||||||
outer_box.add(el)
|
outer_box.add(el)
|
||||||
self.add_start(outer_box)
|
self.add_start(outer_box)
|
||||||
|
|
||||||
def get_prefs(self) -> tuple[str, bool, str]:
|
def get_prefs(self) -> tuple[str, bool]:
|
||||||
name = self.name_entry.get_text().strip()
|
name = self.name_entry.get_text().strip()
|
||||||
use_miles = self.radio_miles.get_active()
|
use_miles = self.radio_miles.get_active()
|
||||||
model = self.client_combo.get_model()
|
return name, use_miles
|
||||||
ind = self.client_combo.get_active()
|
|
||||||
client = model[ind][1]
|
|
||||||
return name, use_miles, client
|
|
||||||
|
|
||||||
def _on_entry_changed(self, entry: Gtk.Entry) -> None:
|
def _on_entry_changed(self, entry: Gtk.Entry) -> None:
|
||||||
text = entry.get_text()
|
text = entry.get_text()
|
||||||
@@ -499,15 +491,16 @@ class Assistant(Gtk.Assistant):
|
|||||||
self.config_values = self.page_migration.migrated_conf
|
self.config_values = self.page_migration.migrated_conf
|
||||||
return self.get_final_page()
|
return self.get_final_page()
|
||||||
case SteamPathPage():
|
case SteamPathPage():
|
||||||
self.config_values["default_steam_path"] = page.get_path_from_radio()
|
path = page.get_path_from_radio()
|
||||||
|
self.config_values["default_steam_path"] = path
|
||||||
|
self.config_values["client"] = path_to_steam_cmd(path)
|
||||||
case SteamValidationPage():
|
case SteamValidationPage():
|
||||||
self.config_values["steam_api"] = page.get_api_key()
|
self.config_values["steam_api"] = page.get_api_key()
|
||||||
case PreferencesPage():
|
case PreferencesPage():
|
||||||
# NOTE: collects config values before advancing to last page
|
# NOTE: collects config values before advancing to last page
|
||||||
name, use_miles, client = self.page_prefs.get_prefs()
|
name, use_miles = self.page_prefs.get_prefs()
|
||||||
self.config_values["name"] = name
|
self.config_values["name"] = name
|
||||||
self.config_values["use_miles"] = use_miles
|
self.config_values["use_miles"] = use_miles
|
||||||
self.config_values["client"] = client
|
|
||||||
self.page_shortcuts.set_steam_path(
|
self.page_shortcuts.set_steam_path(
|
||||||
self.config_values["default_steam_path"]
|
self.config_values["default_steam_path"]
|
||||||
)
|
)
|
||||||
@@ -632,6 +625,12 @@ class ShortcutCreationPage(OptionalPageMixin, EnumeratedWizardPage): # type: ig
|
|||||||
self.add_start(self.checks_area)
|
self.add_start(self.checks_area)
|
||||||
self.show_all()
|
self.show_all()
|
||||||
self.connect("map", self._on_map)
|
self.connect("map", self._on_map)
|
||||||
|
self.connect("map", self._toggle_checkbox)
|
||||||
|
|
||||||
|
def _toggle_checkbox(self, page: "EnumeratedWizardPage") -> None:
|
||||||
|
if FLATPAK_STEAM_PATH in str(self.steam_path):
|
||||||
|
self.steam_checkbox.set_active(False)
|
||||||
|
self.steam_checkbox.set_sensitive(False)
|
||||||
|
|
||||||
def _on_start_menu_toggled(self, button: Gtk.CheckButton) -> None:
|
def _on_start_menu_toggled(self, button: Gtk.CheckButton) -> None:
|
||||||
state = button.get_active()
|
state = button.get_active()
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ from dzgui.const.constants import (
|
|||||||
APPID_DAYZ_EXP,
|
APPID_DAYZ_EXP,
|
||||||
APPNAME_DAYZ,
|
APPNAME_DAYZ,
|
||||||
APPNAME_DAYZ_EXP_HUMAN,
|
APPNAME_DAYZ_EXP_HUMAN,
|
||||||
|
FLATPAK_RUN_CMD,
|
||||||
|
FLATPAK_SANDBOX,
|
||||||
|
FLATPAK_STEAM_PATH,
|
||||||
NO_EXPAND,
|
NO_EXPAND,
|
||||||
NO_FILL,
|
NO_FILL,
|
||||||
NO_PADDING,
|
NO_PADDING,
|
||||||
@@ -25,7 +28,7 @@ from dzgui.views.components.eventbox import InfoEventBox
|
|||||||
from dzgui.views.components.labels import LeftLabel
|
from dzgui.views.components.labels import LeftLabel
|
||||||
from dzgui.views.components.buttons import SpinnerButton, WebButton
|
from dzgui.views.components.buttons import SpinnerButton, WebButton
|
||||||
from dzgui.views.components.frame import HeadingFrame
|
from dzgui.views.components.frame import HeadingFrame
|
||||||
from dzgui.views.components.misc import ClientCombo, ErrorPopover
|
from dzgui.views.components.misc import ErrorPopover
|
||||||
from dzgui.views.dialogs.generic import ExceptionDialog
|
from dzgui.views.dialogs.generic import ExceptionDialog
|
||||||
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
|
from dzgui.views.mixins.scrollable_mixin import ScrollableMixin
|
||||||
|
|
||||||
@@ -76,7 +79,7 @@ class SubmitField(Gtk.Box):
|
|||||||
self.button = Gtk.Button(label=options.save_button)
|
self.button = Gtk.Button(label=options.save_button)
|
||||||
|
|
||||||
self.button.connect("clicked", self._on_save_clicked)
|
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.connect("activate", self._on_field_activated)
|
||||||
self.entry.get_property("buffer").connect("deleted-text", self._on_text_deleted)
|
self.entry.get_property("buffer").connect("deleted-text", self._on_text_deleted)
|
||||||
|
|
||||||
@@ -113,13 +116,9 @@ class SubmitField(Gtk.Box):
|
|||||||
def _on_text_typed(
|
def _on_text_typed(
|
||||||
self,
|
self,
|
||||||
entry: Gtk.Entry,
|
entry: Gtk.Entry,
|
||||||
text: str,
|
|
||||||
length: int,
|
|
||||||
pos: int,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
buffer = entry.get_property("buffer")
|
text = entry.get_text()
|
||||||
text = buffer.get_text() + text
|
|
||||||
state = self._is_valid_text(text)
|
state = self._is_valid_text(text)
|
||||||
self.button.set_sensitive(state)
|
self.button.set_sensitive(state)
|
||||||
|
|
||||||
@@ -250,6 +249,27 @@ class ToggleField(Gtk.Box):
|
|||||||
el.set_sensitive(state)
|
el.set_sensitive(state)
|
||||||
|
|
||||||
|
|
||||||
|
class FlatpakToggleField(ToggleField):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
controller: "Controller",
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
controller,
|
||||||
|
first_option=options.flatpak_standard,
|
||||||
|
second_option=options.flatpak_container,
|
||||||
|
context=Preferences.CLIENT,
|
||||||
|
)
|
||||||
|
self.add(InfoEventBox(options.flatpak_info, self.controller))
|
||||||
|
self.controller = controller
|
||||||
|
self.radio1.connect("toggled", self._on_radio_toggled, Preferences.CLIENT)
|
||||||
|
|
||||||
|
def _on_radio_toggled(self, button: Gtk.RadioButton, context: Preferences) -> None:
|
||||||
|
cmd = FLATPAK_RUN_CMD if self.radio1.get_active() else FLATPAK_SANDBOX
|
||||||
|
print(context)
|
||||||
|
self.controller.update_config(context, cmd)
|
||||||
|
|
||||||
|
|
||||||
class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||||
def __init__(self, controller: "Controller"):
|
def __init__(self, controller: "Controller"):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@@ -278,6 +298,7 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
self.player_box.set_halign(Gtk.Align.START)
|
self.player_box.set_halign(Gtk.Align.START)
|
||||||
self.player_box.get_children()[0].set_width_chars(30) # type: ignore
|
self.player_box.get_children()[0].set_width_chars(30) # type: ignore
|
||||||
|
|
||||||
|
self.flatpak_toggle = FlatpakToggleField(self.controller)
|
||||||
self.fullscreen_toggle = ToggleField(
|
self.fullscreen_toggle = ToggleField(
|
||||||
self.controller,
|
self.controller,
|
||||||
strings.options.last_used,
|
strings.options.last_used,
|
||||||
@@ -285,11 +306,6 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
Preferences.WINDOW,
|
Preferences.WINDOW,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.client_combo = ClientCombo()
|
|
||||||
self.client_combo.connect("changed", self._on_client_changed)
|
|
||||||
|
|
||||||
client_hbox = ShortHBox(self.client_combo)
|
|
||||||
|
|
||||||
self.distance_toggle = ToggleField(
|
self.distance_toggle = ToggleField(
|
||||||
self.controller, strings.options.km, strings.options.mi, Preferences.DIST
|
self.controller, strings.options.km, strings.options.mi, Preferences.DIST
|
||||||
)
|
)
|
||||||
@@ -313,12 +329,17 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
self.start_tab_combo.connect("changed", self._on_start_tab_changed)
|
self.start_tab_combo.connect("changed", self._on_start_tab_changed)
|
||||||
|
|
||||||
pref_rows = [
|
pref_rows = [
|
||||||
[LeftLabel(strings.options.client), client_hbox],
|
|
||||||
[LeftLabel(strings.options.window_size), self.fullscreen_toggle],
|
[LeftLabel(strings.options.window_size), self.fullscreen_toggle],
|
||||||
[LeftLabel(strings.options.distance), self.distance_toggle],
|
[LeftLabel(strings.options.distance), self.distance_toggle],
|
||||||
[LeftLabel(options.start_tab), start_tab_hbox],
|
[LeftLabel(options.start_tab), start_tab_hbox],
|
||||||
[LeftLabel(strings.options.name), self.player_box],
|
[LeftLabel(strings.options.name), self.player_box],
|
||||||
]
|
]
|
||||||
|
default = self.controller.query_config(Preferences.DEFAULT)
|
||||||
|
if FLATPAK_STEAM_PATH in default:
|
||||||
|
pref_rows.insert(
|
||||||
|
0,
|
||||||
|
[LeftLabel(options.flatpak_label), self.flatpak_toggle],
|
||||||
|
)
|
||||||
|
|
||||||
self.dayz_version_label = Gtk.Label(label=strings.null)
|
self.dayz_version_label = Gtk.Label(label=strings.null)
|
||||||
self.dayz_exp_version_label = Gtk.Label(label=strings.null)
|
self.dayz_exp_version_label = Gtk.Label(label=strings.null)
|
||||||
@@ -371,19 +392,11 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
|
|
||||||
self.connect("key-press-event", self._on_keypress)
|
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 block_text_entry(self) -> None:
|
def block_text_entry(self) -> None:
|
||||||
self.steam_box.block_text_entry()
|
self.steam_box.block_text_entry()
|
||||||
# self.steam_entry.set_position(-1)
|
|
||||||
# self.steam_entry.set_can_focus(False)
|
|
||||||
|
|
||||||
def unblock_text_entry(self) -> None:
|
def unblock_text_entry(self) -> None:
|
||||||
self.steam_box.unblock_text_entry()
|
self.steam_box.unblock_text_entry()
|
||||||
# self.steam_entry.set_can_focus(True)
|
|
||||||
|
|
||||||
def _on_developers_clicked(self, button: Gtk.Button) -> None:
|
def _on_developers_clicked(self, button: Gtk.Button) -> None:
|
||||||
self.controller.show_developers_page()
|
self.controller.show_developers_page()
|
||||||
@@ -418,13 +431,6 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
index = enum.value
|
index = enum.value
|
||||||
self.controller.update_config(Preferences.START_TAB, index)
|
self.controller.update_config(Preferences.START_TAB, index)
|
||||||
|
|
||||||
def _on_client_changed(self, combo: Gtk.ComboBoxText) -> None:
|
|
||||||
_iter = combo.get_active_iter()
|
|
||||||
if _iter is None:
|
|
||||||
raise ValueError(f"No active iterator set on {combo}")
|
|
||||||
real_cmd = combo.get_model()[_iter][1]
|
|
||||||
self.controller.update_config(Preferences.CLIENT, real_cmd)
|
|
||||||
|
|
||||||
def populate_settings(self) -> None:
|
def populate_settings(self) -> None:
|
||||||
prefs = self.controller.get_prefs()
|
prefs = self.controller.get_prefs()
|
||||||
# NOTE: re-check in case file was removed by user between runs
|
# NOTE: re-check in case file was removed by user between runs
|
||||||
@@ -470,10 +476,6 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
|||||||
self.dayz_version_label.set_text(dayz_version)
|
self.dayz_version_label.set_text(dayz_version)
|
||||||
self.dayz_exp_version_label.set_text(dayz_exp_version)
|
self.dayz_exp_version_label.set_text(dayz_exp_version)
|
||||||
|
|
||||||
# TODO: bicolumn list store with no cell renderer on index 1, use raw command names
|
|
||||||
active_combo = query.get_client_index(config["client"])
|
|
||||||
self.client_combo.set_active(active_combo)
|
|
||||||
|
|
||||||
start_tab = self.controller.query_config(Preferences.START_TAB)
|
start_tab = self.controller.query_config(Preferences.START_TAB)
|
||||||
self.start_tab_combo.set_active(start_tab)
|
self.start_tab_combo.set_active(start_tab)
|
||||||
|
|
||||||
|
|||||||
@@ -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.0b25"
|
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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from dzgui.const.constants import FLATPAK_RUN_CMD, STEAM_CMD
|
||||||
|
from dzgui.controllers.mc import Controller
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"value, expected", [(FLATPAK_RUN_CMD, "Flatpak"), (STEAM_CMD, "Steam")]
|
||||||
|
)
|
||||||
|
def test_human_name(monkeypatch, value, expected) -> None:
|
||||||
|
def mock_query_config(*args) -> str:
|
||||||
|
return value
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"dzgui.controllers.mc.Controller.query_config", mock_query_config
|
||||||
|
)
|
||||||
|
controller = Controller()
|
||||||
|
client = controller.get_client_name()
|
||||||
|
assert client == expected
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from dzgui.const.constants import (
|
||||||
|
FLATPAK_APPID,
|
||||||
|
FLATPAK_ROOT_PATH,
|
||||||
|
FLATPAK_RUN_CMD,
|
||||||
|
FLATPAK_SANDBOX,
|
||||||
|
)
|
||||||
|
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 output, expected in (
|
||||||
|
(
|
||||||
|
[
|
||||||
|
[FLATPAK_ROOT_PATH],
|
||||||
|
"/home/user/.local/share/Steam/ubuntu12_64/steamwebhelper",
|
||||||
|
],
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
([[FLATPAK_ROOT_PATH]], False),
|
||||||
|
([[FLATPAK_ROOT_PATH], ["steam-runtime-launcher-service"]], False),
|
||||||
|
([["/home/user/.local/share/Steam/ubuntu12_64/steamwebhelper"]], True),
|
||||||
|
(
|
||||||
|
[
|
||||||
|
["/home/user/.local/share/Steam/ubuntu12_64/steamwebhelper"],
|
||||||
|
"steam-runtime-launcher-service",
|
||||||
|
],
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
with (
|
||||||
|
self.subTest(running=output),
|
||||||
|
patch.object(proc, "get_steam_procs", return_value=output),
|
||||||
|
):
|
||||||
|
self.assertEqual(proc.is_native_steam_running(), expected)
|
||||||
|
|
||||||
|
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:
|
class DummyEmitter:
|
||||||
pass
|
def __init__(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def connect(self, _, __) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=[("05:00", 10.0), ("15:30", 5.0)])
|
@pytest.fixture(params=[("05:00", 10.0), ("15:30", 5.0)])
|
||||||
|
|||||||
Reference in new issue
Block a user