mirror of
https://github.com/aclist/dztui.git
synced 2026-10-26 12:42:34 +01:00
Compare commits
99
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3d62c6749 | ||
|
|
5134bf02fb | ||
|
|
f97be9352d | ||
|
|
b092a809f4 | ||
|
|
2cc7710807 | ||
|
|
5fb10d9758 | ||
|
|
100cb43afc | ||
|
|
7e2a585812 | ||
|
|
c1181435b3 | ||
|
|
22a7814ce4 | ||
|
|
b030da5c65 | ||
|
|
3c0acdd068 | ||
|
|
25ae2052e4 | ||
|
|
1b1777117f | ||
|
|
3e6bd95285 | ||
|
|
f4be9a31e9 | ||
|
|
a6282e4401 | ||
|
|
a9ae5f59b5 | ||
|
|
cdec81071d | ||
|
|
97b5c0e6ee | ||
|
|
67c19fb90d | ||
|
|
d9d3f747bb | ||
|
|
1440871819 | ||
|
|
761988d8c2 | ||
|
|
97ee2b7307 | ||
|
|
ff6edb6181 | ||
|
|
4c22263fb5 | ||
|
|
ab5637ae3b | ||
|
|
682532a867 | ||
|
|
d577ba0970 | ||
|
|
c2993ad289 | ||
|
|
edb87893f9 | ||
|
|
519e6c354d | ||
|
|
e3d1d62b4d | ||
|
|
1dcfdda657 | ||
|
|
019847dcf9 | ||
|
|
c2cfe2b4d9 | ||
|
|
01b3babdf5 | ||
|
|
141f96fb9f | ||
|
|
e286f02342 | ||
|
|
9f3c39fdd6 | ||
|
|
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 | ||
|
|
b3b05918b7 | ||
|
|
e6e0a3036d | ||
|
|
8c44a6e255 | ||
|
|
7fc1264b65 | ||
|
|
f748b09ba7 |
No files matched your search
@@ -17,7 +17,7 @@ DZGUI follows a specific set of guidelines, enumerated below:
|
||||
- Provide a free and open-source tool under GPL
|
||||
- Do not use undocumented, third-party servers to aggregate data; no central "DZGUI" server
|
||||
- All connections go to Steam and DayZ servers directly; the tool should function in perpetuity without intermediary endpoints
|
||||
- Target Linux desktop and Steam deck specifically; not a cross-platform tool
|
||||
- Target Linux desktop and Steam Deck specifically; not a cross-platform tool
|
||||
|
||||
## Setup and usage
|
||||
|
||||
@@ -35,6 +35,8 @@ Geolocation records from [DB-IP](https://db-ip.com) under [CC 4.0 license](https
|
||||
Executable versions of DZGUI published as release binaries ship with various runtime dependencies and the Python interpreter built in.
|
||||
Users wishing to review the licenses to these components can inspect the `LICENSE` file located in the release tarball.
|
||||
|
||||
Steam and the Steam logo are trademarks and/or registered trademarks of Valve Corporation in the U.S. and/or other countries.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
DZGUI is beta-quality software and is provided as-is.
|
||||
+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
|
||||
|
||||
+11
-3
@@ -11,7 +11,7 @@ from typing import Any
|
||||
|
||||
from dzgui.api.steam import find_user_id_32
|
||||
from dzgui.const.constants import APP_NAME, APP_NAME_LOWER, IMAGES_PATH
|
||||
from dzgui.util.dirs import copy_dzgui_to_xdg_data
|
||||
from dzgui.util.dirs import copy_dzgui_to_xdg_data, make_parents
|
||||
from dzgui.util.strings import unknown
|
||||
|
||||
logger = logging.getLogger(APP_NAME)
|
||||
@@ -75,6 +75,8 @@ class Shortcuts:
|
||||
shortcuts = vdf.binary_load(f)
|
||||
self.shortcuts = shortcuts
|
||||
logger.debug("Loaded shortcuts file")
|
||||
except FileNotFoundError:
|
||||
self.shortcuts = {"shortcuts": {}}
|
||||
except Exception as e:
|
||||
logger.critical(e)
|
||||
raise e
|
||||
@@ -109,6 +111,7 @@ class Shortcuts:
|
||||
for img in ("_hero", "p", "_logo"):
|
||||
filename = str(self.bpid) + img
|
||||
dest = grid_path.joinpath(filename).with_suffix(".png")
|
||||
make_parents(dest)
|
||||
b = images.joinpath(img).with_suffix(".png").read_bytes()
|
||||
dest.write_bytes(b)
|
||||
|
||||
@@ -156,6 +159,7 @@ class Shortcuts:
|
||||
NEW_ENTRY["DevkitOverrideAppID"] = 0
|
||||
NEW_ENTRY["LastPlayTime"] = 0
|
||||
NEW_ENTRY["FlatpakAppID"] = ""
|
||||
|
||||
NEW_ENTRY["sortas"] = ""
|
||||
NEW_ENTRY["tags"] = {}
|
||||
return NEW_ENTRY
|
||||
@@ -169,9 +173,13 @@ class Shortcuts:
|
||||
self.save_shortcuts()
|
||||
|
||||
def save_shortcuts(self) -> None:
|
||||
if self.shortcuts_path.is_file():
|
||||
try:
|
||||
backup = self.shortcuts_path.with_suffix(".vdf.bak")
|
||||
shutil.copy(self.shortcuts_path, backup)
|
||||
except Exception as e:
|
||||
logger.critical(e)
|
||||
try:
|
||||
backup = self.shortcuts_path.with_suffix(".vdf.bak")
|
||||
shutil.copy(self.shortcuts_path, backup)
|
||||
with open(self.shortcuts_path, "wb") as f:
|
||||
vdf.binary_dump(self.shortcuts, f)
|
||||
except Exception as e:
|
||||
|
||||
+32
-8
@@ -1,17 +1,19 @@
|
||||
import logging
|
||||
import os
|
||||
import psutil
|
||||
import re
|
||||
import requests
|
||||
import subprocess
|
||||
import vdf # type: ignore
|
||||
|
||||
from pathlib import Path
|
||||
from requests.exceptions import HTTPError
|
||||
from typing import Any, Union
|
||||
from typing import Any, Generator, Union
|
||||
from warnings import deprecated
|
||||
|
||||
from dzgui.api.acf import ACF
|
||||
from dzgui.init.prereqs import has_steam_client
|
||||
from dzgui.init.prereqs import has_any_client
|
||||
from dzgui.init.prereqs import has_flatpak_client, has_steam_client
|
||||
from dzgui.const.constants import (
|
||||
APPID_DAYZ,
|
||||
APPID_DAYZ_EXP,
|
||||
@@ -20,10 +22,11 @@ from dzgui.const.constants import (
|
||||
DAYZ_LAUNCHER,
|
||||
DEBIAN_STEAM_PATH,
|
||||
DEFAULT_STEAM_PATH,
|
||||
DOWNLOADS_PATH,
|
||||
FLATPAK_STEAM_PATH,
|
||||
LIBRARYFOLDERS_PATH,
|
||||
UBUNTU_STEAM_PATH,
|
||||
REQUEST_TIMEOUT,
|
||||
UBUNTU_STEAM_PATH,
|
||||
VDF_PATH,
|
||||
)
|
||||
from dzgui.const.endpoints import (
|
||||
@@ -59,7 +62,7 @@ class VDFLoadError(Exception):
|
||||
|
||||
def get_steam_paths() -> list[tuple[Path, str]]:
|
||||
paths = []
|
||||
if has_steam_client():
|
||||
if has_any_client():
|
||||
HOME = Path.home()
|
||||
env = os.environ.get("XDG_DATA_HOME")
|
||||
if env is not None:
|
||||
@@ -80,6 +83,10 @@ def get_steam_paths() -> list[tuple[Path, str]]:
|
||||
}
|
||||
|
||||
for path in DEFAULT_PATH, FLATPAK_PATH, UBUNTU_PATH, DEBIAN_PATH:
|
||||
if path is FLATPAK_PATH and has_flatpak_client() is False:
|
||||
continue
|
||||
if path is not FLATPAK_PATH and has_steam_client() is False:
|
||||
continue
|
||||
if path.joinpath(VDF_PATH).is_file():
|
||||
paths.append((path, human[path]))
|
||||
return paths
|
||||
@@ -196,6 +203,7 @@ def load_to_menu(client: str, appid: int, name: str, mods: list[str]) -> int:
|
||||
"""Loads to the menu screen with the selected mods; used for AFK/pre-joining"""
|
||||
concat = concat_mods(mods)
|
||||
client_args = concat_bash_args(client)
|
||||
logger.debug(f"Setting client arguments to: {client_args}")
|
||||
params = [
|
||||
"-applaunch",
|
||||
str(appid),
|
||||
@@ -245,9 +253,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
|
||||
@@ -265,12 +273,12 @@ def find_user_id_32(path: Path) -> int:
|
||||
|
||||
|
||||
class SteamAPIKeyError(Exception):
|
||||
def __init__(self, msg=None):
|
||||
def __init__(self, msg: str | None = None) -> None:
|
||||
super().__init__(msg or api_validation_error)
|
||||
|
||||
|
||||
class SteamWorkshopEndpointError(Exception):
|
||||
def __init__(self, msg=None):
|
||||
def __init__(self, msg: str | None = None) -> None:
|
||||
super().__init__(msg or workshop_endpoint_error)
|
||||
|
||||
|
||||
@@ -439,6 +447,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
|
||||
|
||||
|
||||
+35
-4
@@ -1,10 +1,18 @@
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from importlib import resources
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
from dzgui.const.constants import APP_NAME
|
||||
from dzgui.const.constants import (
|
||||
APP_NAME,
|
||||
APP_NAME_LOWER,
|
||||
FLATPAK_ROOT_PATH,
|
||||
ICONS_PATH,
|
||||
)
|
||||
from dzgui.const.enum import Preferences
|
||||
from dzgui.config.query import lookup
|
||||
from dzgui.config.userprefs import UserPrefs
|
||||
@@ -15,7 +23,7 @@ from dzgui.init.migrate import (
|
||||
migrate_cols_file,
|
||||
)
|
||||
from dzgui.init.flock import lock_acquire
|
||||
from dzgui.init.prereqs import has_steam_client
|
||||
from dzgui.init.prereqs import has_flatpak_client, has_steam_client
|
||||
from dzgui.strings import boot
|
||||
|
||||
from dzgui.util.deck import is_steam_deck, is_game_mode
|
||||
@@ -96,6 +104,23 @@ def copy_bare_configs(config: "Path", resolution: "Path") -> tuple[bool, bool]:
|
||||
return (config_changed, state_changed)
|
||||
|
||||
|
||||
def load_steam_icon() -> None:
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk # noqa
|
||||
|
||||
try:
|
||||
traversable = resources.files(APP_NAME_LOWER).joinpath(ICONS_PATH)
|
||||
icons = Path(str(traversable))
|
||||
|
||||
theme = Gtk.IconTheme.get_default()
|
||||
paths = theme.get_search_path()
|
||||
theme.set_search_path(paths + [str(icons)])
|
||||
except Exception as e:
|
||||
logger.critical(e)
|
||||
|
||||
|
||||
def load_gui(version: str, is_debug: bool) -> None:
|
||||
lock = lock_acquire() # noqa
|
||||
|
||||
@@ -123,7 +148,7 @@ def load_gui(version: str, is_debug: bool) -> None:
|
||||
migrate_cols_file(XDG.columns)
|
||||
copy_state_files(xdg_paths["XDG_STATE_HOME"])
|
||||
# TODO: add logging inside wizard
|
||||
wizard = SetupWizard(_is_steam_deck, XDG)
|
||||
wizard = SetupWizard(_is_steam_deck, XDG, xdg_paths)
|
||||
|
||||
if not wizard.is_setup_complete():
|
||||
return
|
||||
@@ -136,13 +161,18 @@ def load_gui(version: str, is_debug: bool) -> None:
|
||||
# TODO: sudo escalation dialog
|
||||
# count = get_map_count()
|
||||
# TODO: move into module
|
||||
if has_steam_client() is False:
|
||||
if FLATPAK_ROOT_PATH in lookup(XDG.config, Preferences.DEFAULT):
|
||||
has_client = has_flatpak_client()
|
||||
else:
|
||||
has_client = has_steam_client()
|
||||
if has_client is False:
|
||||
EarlyAlertDialog(init.requires_steam)
|
||||
|
||||
bootwin = BootWindow(XDG, version)
|
||||
local_coords, latest_release = bootwin.get_results()
|
||||
|
||||
use_miles = lookup(XDG.config, Preferences.DIST)
|
||||
|
||||
prefs = UserPrefs(
|
||||
is_steam_deck=_is_steam_deck,
|
||||
is_game_mode=_is_game_mode,
|
||||
@@ -154,5 +184,6 @@ def load_gui(version: str, is_debug: bool) -> None:
|
||||
use_miles=use_miles,
|
||||
)
|
||||
print(boot.all_ok)
|
||||
load_steam_icon()
|
||||
app = App(prefs)
|
||||
app.run(None)
|
||||
@@ -4,8 +4,6 @@ from pathlib import Path
|
||||
from dzgui.util._json import read_json
|
||||
|
||||
from dzgui.const.enum import Preferences
|
||||
from dzgui.const.constants import STEAM_CMD, FLATPAK_RUN_CMD, FLATPAK_SANDBOX
|
||||
|
||||
|
||||
class ConfigFileError(Exception):
|
||||
pass
|
||||
@@ -40,23 +38,5 @@ def get_favorites(path: Path) -> list[str]:
|
||||
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:
|
||||
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
|
||||
+10
-2
@@ -3,7 +3,13 @@ import os
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
|
||||
from dzgui.const.constants import APP_NAME_LOWER, DEBUG_LOG, SYSTEM_LOG
|
||||
from dzgui.const.constants import (
|
||||
APP_NAME_LOWER,
|
||||
DEBUG_LOG,
|
||||
FLATPAK_ROOT_PATH,
|
||||
SYSTEM_LOG,
|
||||
)
|
||||
from dzgui.init.proc import is_sandboxed
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -53,10 +59,12 @@ def get_xdg_paths() -> dict:
|
||||
"XDG_DATA_HOME": home / ".local" / "share",
|
||||
"XDG_CACHE_HOME": home / ".cache",
|
||||
}
|
||||
|
||||
resolved_paths = {}
|
||||
for path in xdg_paths:
|
||||
real_path = os.environ.get(path)
|
||||
if real_path is not None and is_sandboxed():
|
||||
search_string = FLATPAK_ROOT_PATH + "/"
|
||||
real_path = real_path.replace(search_string, "")
|
||||
if real_path is not None and is_writeable(real_path):
|
||||
new_path = Path(real_path)
|
||||
else:
|
||||
|
||||
@@ -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"
|
||||
@@ -50,7 +51,7 @@ INPUT_KEYBOARD = "input-keyboard-symbolic"
|
||||
LIST_ADD = "list-add-symbolic"
|
||||
REFRESH_ICON = "view-refresh-symbolic"
|
||||
SEARCH_ICON = "system-search-symbolic"
|
||||
STEAM_ICON = "steam_tray_mono"
|
||||
STEAM_ICON = "steam-tray-symbolic"
|
||||
VIEW_CONCEAL = "view-conceal-symbolic"
|
||||
VIEW_REVEAL = "view-reveal-symbolic"
|
||||
WARNING = "dialog-warning-symbolic"
|
||||
@@ -79,12 +80,14 @@ DEBUG_LOG = f"{APP_NAME}_DEBUG.LOG"
|
||||
SYSTEM_LOG = f"{APP_NAME}_SYSTEM.LOG"
|
||||
CHANGELOG_PATH = "data/CHANGELOG.md"
|
||||
IMAGES_PATH = "data/images"
|
||||
ICONS_PATH = "data/images/gtk"
|
||||
|
||||
HERO_PATH = "data/images/_hero.png"
|
||||
CSS_PATH = "data/app.css"
|
||||
VDF_PATH = "steamapps/libraryfolders.vdf"
|
||||
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"
|
||||
DEBIAN_STEAM_PATH = ".steam/debian-installation"
|
||||
|
||||
|
||||
@@ -111,6 +111,10 @@ class Emitter(GObject.GObject):
|
||||
def check_toggled(self, label: str, state: bool) -> None:
|
||||
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,))
|
||||
def servers_loaded(self, tag: "ServerTab") -> None:
|
||||
pass
|
||||
|
||||
@@ -541,8 +541,10 @@ 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_client_name(self) -> str:
|
||||
# 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:
|
||||
self.mediator.window.present_with_time(Gdk.CURRENT_TIME)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.3.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M568 320C568 457 456.8 568 319.6 568C205.8 568 110 491.7 80.6 387.6L175.8 426.9C182.2 459 210.7 483.3 244.7 483.3C283.9 483.3 316.6 450.9 314.9 409.8L399.4 349.6C451.5 350.9 495.2 308.7 495.2 256.1C495.2 204.5 453.2 162.6 401.5 162.6C349.8 162.6 307.8 204.6 307.8 256.1L307.8 257.3L248.6 343C233.1 342.1 217.9 346.4 205.1 355.1L72 300.1C82.2 172.4 189.1 72 319.6 72C456.8 72 568 183 568 320zM227.7 448.3L197.2 435.7C202.8 447.3 212.5 456.5 224.4 461.5C251.3 472.7 282.2 459.9 293.4 433.1C298.8 420.1 298.9 405.8 293.5 392.8C288.1 379.8 278 369.6 265 364.2C252.1 358.8 238.3 359 226.1 363.6L257.6 376.6C277.4 384.8 286.8 407.5 278.5 427.3C270.2 447.2 247.5 456.5 227.7 448.3zM401.5 193.8C435.9 193.8 463.8 221.7 463.8 256.1C463.8 290.5 435.9 318.4 401.5 318.4C367.1 318.4 339.2 290.5 339.2 256.1C339.2 221.7 367.1 193.8 401.5 193.8zM401.6 302.8C427.4 302.8 448.4 281.8 448.4 256C448.4 230.2 427.4 209.2 401.6 209.2C375.8 209.2 354.8 230.2 354.8 256C354.8 281.8 375.8 302.8 401.6 302.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
+22
-6
@@ -3,23 +3,39 @@ import shutil
|
||||
import subprocess
|
||||
|
||||
from dzgui.const.constants import APP_NAME, FLATPAK_APPID, FLATPAK_CMD, STEAM_CMD
|
||||
from dzgui.init.proc import is_sandboxed
|
||||
|
||||
logger = logging.getLogger(APP_NAME)
|
||||
|
||||
|
||||
def has_steam_client() -> bool:
|
||||
if shutil.which(STEAM_CMD):
|
||||
def has_flatpak_client() -> bool:
|
||||
if is_sandboxed():
|
||||
return True
|
||||
if not shutil.which(FLATPAK_CMD):
|
||||
return False
|
||||
try:
|
||||
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:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except subprocess.CalledProcessError as e:
|
||||
except Exception as e:
|
||||
logger.critical(e)
|
||||
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()
|
||||
+42
-16
@@ -1,7 +1,8 @@
|
||||
import psutil
|
||||
import subprocess
|
||||
import shutil
|
||||
import logging
|
||||
import os
|
||||
import psutil
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from dzgui.const.constants import (
|
||||
APP_NAME,
|
||||
@@ -10,42 +11,67 @@ from dzgui.const.constants import (
|
||||
FLATPAK_CMD,
|
||||
FLATPAK_RUN_CMD,
|
||||
FLATPAK_SANDBOX,
|
||||
FLATPAK_ROOT_PATH,
|
||||
FLATPAK_STEAM_PATH,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(APP_NAME)
|
||||
|
||||
|
||||
def is_steam_running(cmd: str) -> bool:
|
||||
if cmd == STEAM_CMD:
|
||||
if has_cmd(STEAM_CMD) is False:
|
||||
return False
|
||||
return is_running(STEAM_CMD)
|
||||
elif cmd == FLATPAK_RUN_CMD or FLATPAK_SANDBOX:
|
||||
if cmd in (FLATPAK_RUN_CMD, FLATPAK_SANDBOX):
|
||||
return is_flatpak_steam_running()
|
||||
elif cmd == STEAM_CMD:
|
||||
return is_native_steam_running()
|
||||
else:
|
||||
raise TypeError("Not a valid Steam client selection")
|
||||
|
||||
|
||||
# CHORE: test alternate clients
|
||||
# TODO: has_flatpak_steam
|
||||
def is_sandboxed() -> bool:
|
||||
return bool(os.environ.get("FLATPAK_ID"))
|
||||
|
||||
|
||||
def is_flatpak_steam_running() -> bool:
|
||||
if is_sandboxed():
|
||||
return True
|
||||
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
|
||||
return False
|
||||
|
||||
|
||||
def is_running(proc: str) -> bool:
|
||||
for process in psutil.process_iter(["pid", "name"]):
|
||||
if process.info["name"] == proc:
|
||||
return True
|
||||
return False
|
||||
def get_steam_procs() -> list[list[str]]:
|
||||
return [
|
||||
p.cmdline()
|
||||
for p in psutil.process_iter(["pid", "name"])
|
||||
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:
|
||||
if shutil.which(cmd) is not None:
|
||||
return True
|
||||
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 (
|
||||
APP_NAME,
|
||||
FLATPAK_RUN_CMD,
|
||||
FLATPAK_SANDBOX,
|
||||
STEAM_CMD,
|
||||
WINDOW_DEFAULT_X,
|
||||
WINDOW_DEFAULT_Y,
|
||||
)
|
||||
@@ -129,15 +126,6 @@ class ConfigManager:
|
||||
def enum_to_key(self, enum: Preferences) -> str:
|
||||
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:
|
||||
try:
|
||||
real_key = self.enum_to_key(key)
|
||||
|
||||
@@ -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,
|
||||
@@ -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_client_name()
|
||||
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,
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -92,6 +92,7 @@ class ServerModelManager:
|
||||
job = Servers.query_api
|
||||
params = Servers.params
|
||||
servers = []
|
||||
|
||||
with ThreadPoolExecutor() as executor:
|
||||
futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
|
||||
for future in as_completed(futures):
|
||||
@@ -414,6 +415,8 @@ class ServerModelManager:
|
||||
# TODO: disable map, keyword, and filter widgets if model is None
|
||||
# signal driven (servers_empty, servers_failed_to_load)
|
||||
|
||||
self.emitter.emit("servers_timed_out")
|
||||
|
||||
if self.preserve_on_fail is False:
|
||||
self.tv.set_model(None)
|
||||
filter_man = self.tv.get_filter_man()
|
||||
|
||||
@@ -17,3 +17,10 @@ fullscreen_eventbox = "This option is not available on Steam Deck."
|
||||
save_button = "Save"
|
||||
|
||||
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"
|
||||
"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."
|
||||
)
|
||||
+16
-2
@@ -3,6 +3,8 @@ title_intro = "Welcome"
|
||||
blurb_intro = """
|
||||
This wizard will help you set up some common config options before launching the application.
|
||||
"""
|
||||
error_sandbox = "Setup cannot be run from inside Flatpak sandbox.\nPlease launch the application directly."
|
||||
welcome = "Welcome to {0}!"
|
||||
|
||||
### SteamPathPage
|
||||
heading_steam_path = "Steam path"
|
||||
@@ -17,7 +19,7 @@ desc_flatpak_path = (
|
||||
desc_ubuntu_path = "This is the default Steam path on <b>Ubuntu-based systems</b>."
|
||||
desc_debian_path = "This is the default Steam path on <b>Debian-based systems</b>."
|
||||
no_valid_paths = (
|
||||
"No valid Steam paths found on system. Please install Steam to continue."
|
||||
"No valid Steam path or Steam command found on system. Please install Steam to continue."
|
||||
)
|
||||
button_scan = "Scan for Steam"
|
||||
|
||||
@@ -70,7 +72,8 @@ checkbox_steam_shortcut = (
|
||||
(
|
||||
"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. "
|
||||
"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 = (
|
||||
@@ -79,6 +82,17 @@ checkbox_start_menu = (
|
||||
)
|
||||
checkbox_desktop_shortcut = ("Add a desktop shortcut", "Depends on the option above.")
|
||||
|
||||
### Flatpak page
|
||||
heading_flatpak = "Flatpak sandbox overrides"
|
||||
blurb_flatpak = (
|
||||
"You chose to add a shortcut directly to Flatpak Steam. "
|
||||
"To launch DZGUI from Flatpak's sandbox, you must grant "
|
||||
"permission to read from and write to the following locations."
|
||||
)
|
||||
tree_column = "Paths"
|
||||
overrides_applied = "Overrides applied."
|
||||
button_flatpak = "Grant permissions"
|
||||
|
||||
### Completion page
|
||||
heading_completion = "Setup complete"
|
||||
blurb_completion = "Configuration completed successfully. Click 'Close' to close this dialog and launch DZGUI."
|
||||
@@ -1,5 +1,12 @@
|
||||
import shlex
|
||||
|
||||
from dzgui.const.constants import STEAM_CMD
|
||||
from dzgui.init.proc import is_sandboxed
|
||||
|
||||
|
||||
def concat_bash_args(command: str) -> list[str]:
|
||||
# NOTE: NSGs spawned from sandbox cannot access host application commands,
|
||||
# so fallback to Steam internal URIs
|
||||
if is_sandboxed():
|
||||
return [STEAM_CMD]
|
||||
return shlex.split(command)
|
||||
@@ -0,0 +1,31 @@
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
from dzgui.const.constants import APP_NAME, FLATPAK_APPID, GTK_APP_ID
|
||||
|
||||
logger = logging.getLogger(APP_NAME)
|
||||
|
||||
|
||||
def apply_overrides(xdg: dict[str, str]) -> None:
|
||||
try:
|
||||
# NOTE: D-Bus registration required for sandbox
|
||||
args = [
|
||||
"flatpak",
|
||||
"--user",
|
||||
"override",
|
||||
f"--own-name={GTK_APP_ID}",
|
||||
FLATPAK_APPID,
|
||||
]
|
||||
subprocess.run([*args], capture_output=True)
|
||||
|
||||
for path in xdg.values():
|
||||
args = [
|
||||
"flatpak",
|
||||
"--user",
|
||||
"override",
|
||||
f"--filesystem={path}",
|
||||
FLATPAK_APPID,
|
||||
]
|
||||
subprocess.run([*args], capture_output=True)
|
||||
except Exception as e:
|
||||
logger.critical(e)
|
||||
@@ -156,6 +156,7 @@ filter_duplicate = "Duplicate"
|
||||
filter_official = "Official"
|
||||
filter_unofficial = "Unofficial"
|
||||
filter_modded = "Modded"
|
||||
filter_unmodded = "Unmodded"
|
||||
|
||||
# maps
|
||||
all_maps = "All maps"
|
||||
@@ -296,7 +297,7 @@ class Options:
|
||||
init = Init(
|
||||
is_steam_running="Is Steam running? DZGUI must be run on top of Steam.",
|
||||
is_dayz_running="Is DayZ already running? DZGUI cannot launch DayZ if another process is using it.",
|
||||
requires_steam="DZGUI requires that Steam or Flatpak Steam be installed on the system.",
|
||||
requires_steam="Your chosen client (Steam/Flatpak Steam) must be installed on the system.",
|
||||
)
|
||||
|
||||
mod_panel = ModPanelStrings(
|
||||
|
||||
+1
-18
@@ -1,6 +1,5 @@
|
||||
import logging
|
||||
import signal
|
||||
import warnings
|
||||
|
||||
from typing import TYPE_CHECKING, Literal, Self
|
||||
|
||||
@@ -8,12 +7,11 @@ from dzgui.const.constants import (
|
||||
APP_NAME,
|
||||
APP_NAME_LOWER,
|
||||
GTK_APP_ID,
|
||||
STEAM_ICON,
|
||||
)
|
||||
from dzgui.const.enum import NotebookPage
|
||||
from dzgui.controllers.emitter import Emitter
|
||||
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.views.components.connect_panel import ConnectPanel
|
||||
from dzgui.views.components.crumbs import Breadcrumbs
|
||||
@@ -47,10 +45,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:
|
||||
@@ -337,15 +331,4 @@ class App(ColorAwareApp, Gtk.Application): # type: ignore
|
||||
) -> None:
|
||||
self.win.halt_proc_and_quit()
|
||||
|
||||
|
||||
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_position(position)
|
||||
self.set_focus_on_click(False)
|
||||
self.set_always_show_image(True)
|
||||
|
||||
def swap_icon(self, icon: str) -> None:
|
||||
start = self.icon.get_margin_start()
|
||||
@@ -162,18 +163,24 @@ class RefreshButton(IconTextButton):
|
||||
self.set_tooltip_text(atomic_buttons.refresh_tooltip)
|
||||
|
||||
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:
|
||||
"""Spawned in a thread"""
|
||||
if self.controller.get_active_treeview().is_loaded() is False:
|
||||
return
|
||||
self.is_clicked = True
|
||||
self.controller.refresh_tree()
|
||||
# TODO: get server tab enum
|
||||
# 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:
|
||||
return
|
||||
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.init.prereqs import has_steam_client, has_flatpak_client
|
||||
from dzgui.strings import options
|
||||
from dzgui.views.components.box import HBox
|
||||
from dzgui.views.components.buttons import Icon
|
||||
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk # noqa
|
||||
|
||||
|
||||
@deprecated("Client choice is determined at setup time")
|
||||
class ClientCombo(Gtk.ComboBox):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.client_store = Gtk.ListStore(str, str)
|
||||
clients = (
|
||||
has_steam = has_steam_client()
|
||||
has_flatpak = has_flatpak_client()
|
||||
self.client_store = Gtk.ListStore(str, str, bool)
|
||||
self.clients = (
|
||||
(
|
||||
options.steam_combo,
|
||||
STEAM_CMD,
|
||||
has_steam,
|
||||
),
|
||||
(
|
||||
options.flatpak_combo,
|
||||
FLATPAK_RUN_CMD,
|
||||
has_flatpak,
|
||||
),
|
||||
(
|
||||
options.flatpak_container_combo,
|
||||
FLATPAK_SANDBOX,
|
||||
has_flatpak,
|
||||
),
|
||||
)
|
||||
for client in clients:
|
||||
for client in self.clients:
|
||||
self.client_store.append(client)
|
||||
|
||||
self.set_model(self.client_store)
|
||||
renderer_text = Gtk.CellRendererText()
|
||||
self.pack_start(renderer_text, True)
|
||||
self.add_attribute(renderer_text, "text", 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):
|
||||
def __init__(self, relative_to: Gtk.Widget, position: Gtk.PositionType) -> None:
|
||||
super().__init__(relative_to=relative_to, position=position)
|
||||
|
||||
@@ -12,21 +12,24 @@ from dzgui.const.constants import (
|
||||
APP_NAME,
|
||||
APP_NAME_LOWER,
|
||||
HERO_PATH,
|
||||
FLATPAK_ROOT_PATH,
|
||||
LEGACY_CONFIG_PATH,
|
||||
)
|
||||
from dzgui.const.boilerplate import config_boilerplate
|
||||
from dzgui.const.endpoints import STEAM_API_SETUP
|
||||
from dzgui.config import freedesktop
|
||||
from dzgui.init.proc import is_sandboxed, path_to_steam_cmd
|
||||
from dzgui.init.migrate import migrate_legacy_conf
|
||||
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
|
||||
from dzgui.strings import wizard
|
||||
from dzgui.util._json import write_json
|
||||
from dzgui.util.flatpak import apply_overrides
|
||||
from dzgui.util.open_links import open_link_by_url
|
||||
from dzgui.util.css import add_class, load_css
|
||||
from dzgui.views.components.buttons import WebButton
|
||||
from dzgui.views.components.entry import APIEntry
|
||||
from dzgui.views.components.misc import ClientCombo
|
||||
from dzgui.views.mixins.colorscheme import ColorAwareApp
|
||||
from dzgui.views.trees.tree_simple import SimpleTree
|
||||
|
||||
import gi
|
||||
|
||||
@@ -44,7 +47,8 @@ class PageNum(Enum):
|
||||
STEAM_API = 4
|
||||
USER_PREFS = 5
|
||||
SHORTCUTS = 6
|
||||
FINAL = 7
|
||||
FLATPAK = 7
|
||||
FINAL = 8
|
||||
|
||||
|
||||
class OptionalPageMixin:
|
||||
@@ -143,7 +147,7 @@ class NotificationFrame(Gtk.Frame):
|
||||
margin_start=50,
|
||||
margin_end=50,
|
||||
)
|
||||
wrapped = textwrap.fill(label, width=80)
|
||||
wrapped = textwrap.fill(label, width=60)
|
||||
self.label = Gtk.Label(
|
||||
label=wrapped,
|
||||
justify=Gtk.Justification.CENTER,
|
||||
@@ -157,7 +161,7 @@ class NotificationFrame(Gtk.Frame):
|
||||
add_class(self, "error-frame")
|
||||
|
||||
def set_text(self, text: str) -> None:
|
||||
wrapped = textwrap.fill(text, width=80)
|
||||
wrapped = textwrap.fill(text, width=60)
|
||||
self.label.set_markup(wrapped)
|
||||
|
||||
|
||||
@@ -242,12 +246,23 @@ class IntroductionPage(EnumeratedWizardPage):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
enum=PageNum.INTRO,
|
||||
heading=f"Welcome to {APP_NAME}!",
|
||||
heading=wizard.welcome.format(APP_NAME),
|
||||
description=wizard.blurb_intro,
|
||||
)
|
||||
self.page_type = Gtk.AssistantPageType.INTRO
|
||||
self.set_title(wizard.title_intro)
|
||||
|
||||
self.error_box = NotificationFrame(wizard.error_sandbox, error=True)
|
||||
self.add_start(self.error_box)
|
||||
|
||||
self.connect("map", self._on_map)
|
||||
|
||||
def _on_map(self, page: "EnumeratedWizardPage") -> None:
|
||||
self.error_box.hide()
|
||||
if is_sandboxed():
|
||||
self.error_box.show()
|
||||
EMITTER.emit("step_pending")
|
||||
|
||||
|
||||
class Heading(Gtk.Label):
|
||||
def __init__(self, label: str):
|
||||
@@ -384,26 +399,17 @@ class PreferencesPage(EnumeratedWizardPage):
|
||||
self.dist_box.add(self.radio_km)
|
||||
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(
|
||||
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)
|
||||
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()
|
||||
use_miles = self.radio_miles.get_active()
|
||||
model = self.client_combo.get_model()
|
||||
ind = self.client_combo.get_active()
|
||||
client = model[ind][1]
|
||||
return name, use_miles, client
|
||||
return name, use_miles
|
||||
|
||||
def _on_entry_changed(self, entry: Gtk.Entry) -> None:
|
||||
text = entry.get_text()
|
||||
@@ -413,6 +419,42 @@ class PreferencesPage(EnumeratedWizardPage):
|
||||
EMITTER.emit("step_complete")
|
||||
|
||||
|
||||
class FlatpakOverridePage(EnumeratedWizardPage):
|
||||
def __init__(self, xdg_paths: dict[str, str]) -> None:
|
||||
super().__init__(
|
||||
enum=PageNum.FLATPAK,
|
||||
heading=wizard.heading_flatpak,
|
||||
description=wizard.blurb_flatpak,
|
||||
)
|
||||
self.xdg_paths = xdg_paths
|
||||
self.page_type = Gtk.AssistantPageType.INTRO
|
||||
|
||||
store = Gtk.ListStore(str)
|
||||
for path in xdg_paths.values():
|
||||
store.append((str(path),))
|
||||
tree = SimpleTree(store, wizard.tree_column)
|
||||
tree.set_halign(Gtk.Align.CENTER)
|
||||
|
||||
self.authorize_button = Gtk.Button(
|
||||
label=wizard.button_flatpak, halign=Gtk.Align.CENTER
|
||||
)
|
||||
self.authorize_button.connect("clicked", self._on_authorize)
|
||||
|
||||
self.notice = NotificationFrame(wizard.overrides_applied)
|
||||
self.connect("map", self._on_map)
|
||||
|
||||
for el in tree, self.authorize_button, self.notice:
|
||||
self.add_start(el)
|
||||
|
||||
def _on_map(self, page: "EnumeratedWizardPage") -> None:
|
||||
self.notice.hide()
|
||||
|
||||
def _on_authorize(self, button: Gtk.Button) -> None:
|
||||
apply_overrides(self.xdg_paths)
|
||||
self.notice.show()
|
||||
EMITTER.emit("step_complete")
|
||||
|
||||
|
||||
class CompletionPage(EnumeratedWizardPage):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
@@ -426,7 +468,7 @@ class CompletionPage(EnumeratedWizardPage):
|
||||
|
||||
|
||||
class Assistant(Gtk.Assistant):
|
||||
def __init__(self, is_deck: bool, XDG: "Xdg"):
|
||||
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, str]):
|
||||
super().__init__()
|
||||
if is_deck:
|
||||
# NOTE: deemed to be "safe" dimensions that exclude taskbar size
|
||||
@@ -445,6 +487,7 @@ class Assistant(Gtk.Assistant):
|
||||
self.page_api = SteamValidationPage()
|
||||
self.page_prefs = PreferencesPage()
|
||||
self.page_shortcuts = ShortcutCreationPage(XDG.shortcut)
|
||||
self.page_flatpak = FlatpakOverridePage(xdg_paths)
|
||||
self.page_completion = CompletionPage()
|
||||
|
||||
self.set_forward_page_func(self._advance_page)
|
||||
@@ -464,6 +507,7 @@ class Assistant(Gtk.Assistant):
|
||||
self.page_api,
|
||||
self.page_prefs,
|
||||
self.page_shortcuts,
|
||||
self.page_flatpak,
|
||||
self.page_completion,
|
||||
):
|
||||
# NOTE: skip config migration page if no legacy config file
|
||||
@@ -484,7 +528,10 @@ class Assistant(Gtk.Assistant):
|
||||
def write_config(self, config: dict[str, Any]) -> None:
|
||||
write_json(config, self.config_path)
|
||||
|
||||
def get_final_page(self) -> int:
|
||||
def get_completion_page(self) -> int:
|
||||
return self.pages[self.page_completion]
|
||||
|
||||
def get_shortcut_page(self) -> int:
|
||||
return self.pages[self.page_shortcuts]
|
||||
|
||||
def _advance_page(self, index: int) -> int:
|
||||
@@ -497,26 +544,39 @@ class Assistant(Gtk.Assistant):
|
||||
sp = page.migrated_conf["default_steam_path"]
|
||||
self.page_shortcuts.set_steam_path(sp)
|
||||
self.config_values = self.page_migration.migrated_conf
|
||||
return self.get_final_page()
|
||||
return self.get_shortcut_page()
|
||||
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():
|
||||
self.config_values["steam_api"] = page.get_api_key()
|
||||
case PreferencesPage():
|
||||
# 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["use_miles"] = use_miles
|
||||
self.config_values["client"] = client
|
||||
self.page_shortcuts.set_steam_path(
|
||||
self.config_values["default_steam_path"]
|
||||
)
|
||||
case ShortcutCreationPage():
|
||||
page.create_shortcuts()
|
||||
if not self.needs_flatpak_override():
|
||||
return self.get_completion_page()
|
||||
case FlatpakOverridePage():
|
||||
pass
|
||||
case _:
|
||||
raise AttributeError("Trying to advance a non-canonical page")
|
||||
return index + 1
|
||||
|
||||
def needs_flatpak_override(self) -> bool:
|
||||
if (
|
||||
self.page_shortcuts.is_steam_checkbox_active()
|
||||
and FLATPAK_ROOT_PATH in self.config_values["default_steam_path"]
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_setup_complete(self) -> bool:
|
||||
return self.setup_complete
|
||||
|
||||
@@ -642,6 +702,9 @@ class ShortcutCreationPage(OptionalPageMixin, EnumeratedWizardPage): # type: ig
|
||||
def set_steam_path(self, path: Path) -> None:
|
||||
self.steam_path = path
|
||||
|
||||
def is_steam_checkbox_active(self) -> bool:
|
||||
return self.steam_checkbox.get_active()
|
||||
|
||||
def create_shortcuts(self) -> None:
|
||||
# NOTE: best-effort, permissive even on failure (page is already marked as complete)
|
||||
if self.steam_checkbox.get_active():
|
||||
@@ -710,10 +773,10 @@ class SteamPathPage(EnumeratedWizardPage):
|
||||
|
||||
|
||||
class SetupWizard(ColorAwareApp, Gtk.Application): # type: ignore
|
||||
def __init__(self, is_deck: bool, XDG: "Xdg") -> None:
|
||||
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, str]) -> None:
|
||||
super().__init__()
|
||||
GLib.set_prgname(APP_NAME)
|
||||
self.win = Window(is_deck, XDG)
|
||||
self.win = Window(is_deck, XDG, xdg_paths)
|
||||
Gtk.main()
|
||||
|
||||
def is_setup_complete(self) -> int:
|
||||
@@ -721,9 +784,9 @@ class SetupWizard(ColorAwareApp, Gtk.Application): # type: ignore
|
||||
|
||||
|
||||
class Window(Gtk.Window):
|
||||
def __init__(self, is_deck: bool, XDG: "Xdg") -> None:
|
||||
def __init__(self, is_deck: bool, XDG: "Xdg", xdg_paths: dict[str, str]) -> None:
|
||||
super().__init__(title=APP_NAME, icon_name=APP_NAME)
|
||||
self.assistant = Assistant(is_deck, XDG)
|
||||
self.assistant = Assistant(is_deck, XDG, xdg_paths)
|
||||
|
||||
|
||||
class Emitter(GObject.GObject):
|
||||
|
||||
@@ -9,6 +9,9 @@ from dzgui.const.constants import (
|
||||
APPID_DAYZ_EXP,
|
||||
APPNAME_DAYZ,
|
||||
APPNAME_DAYZ_EXP_HUMAN,
|
||||
FLATPAK_RUN_CMD,
|
||||
FLATPAK_SANDBOX,
|
||||
FLATPAK_STEAM_PATH,
|
||||
NO_EXPAND,
|
||||
NO_FILL,
|
||||
NO_PADDING,
|
||||
@@ -25,7 +28,7 @@ from dzgui.views.components.eventbox import InfoEventBox
|
||||
from dzgui.views.components.labels import LeftLabel
|
||||
from dzgui.views.components.buttons import SpinnerButton, WebButton
|
||||
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.mixins.scrollable_mixin import ScrollableMixin
|
||||
|
||||
@@ -76,7 +79,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 +116,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)
|
||||
|
||||
@@ -250,6 +249,26 @@ class ToggleField(Gtk.Box):
|
||||
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
|
||||
self.controller.update_config(context, cmd)
|
||||
|
||||
|
||||
class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||
def __init__(self, controller: "Controller"):
|
||||
super().__init__(
|
||||
@@ -278,6 +297,7 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||
self.player_box.set_halign(Gtk.Align.START)
|
||||
self.player_box.get_children()[0].set_width_chars(30) # type: ignore
|
||||
|
||||
self.flatpak_toggle = FlatpakToggleField(self.controller)
|
||||
self.fullscreen_toggle = ToggleField(
|
||||
self.controller,
|
||||
strings.options.last_used,
|
||||
@@ -285,11 +305,6 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||
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.controller, strings.options.km, strings.options.mi, Preferences.DIST
|
||||
)
|
||||
@@ -313,12 +328,17 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||
self.start_tab_combo.connect("changed", self._on_start_tab_changed)
|
||||
|
||||
pref_rows = [
|
||||
[LeftLabel(strings.options.client), client_hbox],
|
||||
[LeftLabel(strings.options.window_size), self.fullscreen_toggle],
|
||||
[LeftLabel(strings.options.distance), self.distance_toggle],
|
||||
[LeftLabel(options.start_tab), start_tab_hbox],
|
||||
[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_exp_version_label = Gtk.Label(label=strings.null)
|
||||
@@ -371,19 +391,11 @@ 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 block_text_entry(self) -> None:
|
||||
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:
|
||||
self.steam_box.unblock_text_entry()
|
||||
# self.steam_entry.set_can_focus(True)
|
||||
|
||||
def _on_developers_clicked(self, button: Gtk.Button) -> None:
|
||||
self.controller.show_developers_page()
|
||||
@@ -418,13 +430,6 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||
index = enum.value
|
||||
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:
|
||||
prefs = self.controller.get_prefs()
|
||||
# NOTE: re-check in case file was removed by user between runs
|
||||
@@ -470,10 +475,6 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore
|
||||
self.dayz_version_label.set_text(dayz_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)
|
||||
self.start_tab_combo.set_active(start_tab)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.model.model_factory import FastInsertListStore
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk # noqa
|
||||
|
||||
|
||||
class SimpleTree(Gtk.ScrolledWindow):
|
||||
"""Simple scrollable 1-column tree"""
|
||||
|
||||
def __init__(
|
||||
self, model: Union[Gtk.ListStore, "FastInsertListStore"], title: str
|
||||
) -> None:
|
||||
super().__init__(
|
||||
overlay_scrolling=False,
|
||||
propagate_natural_width=True,
|
||||
propagate_natural_height=True,
|
||||
)
|
||||
|
||||
self.tree = Gtk.TreeView(model=model, enable_search=False)
|
||||
renderer = Gtk.CellRendererText()
|
||||
column = Gtk.TreeViewColumn(title, renderer, text=0)
|
||||
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
||||
column.set_resizable(False)
|
||||
column.set_sort_column_id(0)
|
||||
self.tree.append_column(column)
|
||||
|
||||
self.add(self.tree)
|
||||
|
||||
def get_tree(self) -> Gtk.TreeView:
|
||||
return self.tree
|
||||
+3
-2
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
|
||||
authors = [
|
||||
{name = "aclist"}
|
||||
]
|
||||
version = "7.0.0b25"
|
||||
version = "7.0.0b29"
|
||||
license = "GPL-3.0-or-later"
|
||||
license-files = ["LICENSE"]
|
||||
readme = "README.md"
|
||||
@@ -110,10 +110,11 @@ config-settings-package = { pygobject-stubs = { config = "Gtk3,Gdk3,Soup2" } }
|
||||
markers = [
|
||||
"apitest: checks remote endpoints",
|
||||
"config: config file keys/values",
|
||||
"realconfig: depends on actual generated local config file",
|
||||
"flatpak: depends on local flatpak installation",
|
||||
"mods: tests mod metadata/link creation",
|
||||
"pefile: validate PE files",
|
||||
"post_install: requires a completed installation",
|
||||
"realconfig: depends on actual generated local config file",
|
||||
"redact: log redaction mechanisms",
|
||||
"slow: long-running tests",
|
||||
"webtest: depends on remote endpoint",
|
||||
|
||||
+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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
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(
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import configparser
|
||||
import pytest
|
||||
|
||||
from dzgui.const.constants import FLATPAK_APPID, GTK_APP_ID
|
||||
from dzgui.config.xdg import get_xdg_paths
|
||||
from dzgui.util.flatpak import apply_overrides
|
||||
|
||||
PATHS_KEY = "Context"
|
||||
PATHS_SUBKEY = "filesystems"
|
||||
BUS_KEY = "Session Bus Policy"
|
||||
|
||||
pytestmark = pytest.mark.flatpak
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def xdg_paths() -> dict[str, str]:
|
||||
return get_xdg_paths()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def override(xdg_paths) -> None:
|
||||
apply_overrides(xdg_paths)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config(xdg_paths) -> configparser.ConfigParser:
|
||||
conf_path = xdg_paths["XDG_DATA_HOME"].parent.joinpath(
|
||||
f"flatpak/overrides/{FLATPAK_APPID}"
|
||||
)
|
||||
config = configparser.ConfigParser()
|
||||
config.read(conf_path)
|
||||
return config
|
||||
|
||||
|
||||
def test_session_bus(override, config) -> None:
|
||||
bus = config[BUS_KEY][GTK_APP_ID]
|
||||
assert bus == "own"
|
||||
|
||||
|
||||
def test_paths(override, xdg_paths, config) -> None:
|
||||
expect = xdg_paths
|
||||
paths = config[PATHS_KEY][PATHS_SUBKEY].split(";")
|
||||
assert all(str(path) in paths for path in expect.values())
|
||||
@@ -0,0 +1,30 @@
|
||||
import pytest
|
||||
|
||||
from dzgui.const.constants import FLATPAK_RUN_CMD, FLATPAK_SANDBOX
|
||||
from dzgui.const.enum import Preferences
|
||||
from dzgui.views.pages.options import FlatpakToggleField
|
||||
|
||||
|
||||
class MockController:
|
||||
def __init__(self) -> None:
|
||||
self.command: str
|
||||
|
||||
def update_config(self, context: Preferences, cmd: str) -> None:
|
||||
self.command = cmd
|
||||
|
||||
def get_command(self) -> str:
|
||||
return self.command
|
||||
|
||||
|
||||
@pytest.mark.parametrize("index, expect", [(0, FLATPAK_RUN_CMD), (1, FLATPAK_SANDBOX)])
|
||||
def test_toggle(index: int, expect: str) -> None:
|
||||
controller = MockController()
|
||||
toggle = FlatpakToggleField(controller)
|
||||
match index:
|
||||
case 0:
|
||||
toggle.radio2.set_active(True)
|
||||
toggle.radio1.set_active(True)
|
||||
case 1:
|
||||
toggle.radio1.set_active(True)
|
||||
toggle.radio2.set_active(True)
|
||||
assert controller.get_command() == expect
|
||||
@@ -1,3 +1,5 @@
|
||||
import pytest
|
||||
|
||||
from dzgui.api.servers import Details
|
||||
from dzgui.views.dialogs.servers import ServerDetailsDialog
|
||||
|
||||
@@ -21,6 +23,7 @@ class MockController:
|
||||
return None
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
def test_html_escaping() -> None:
|
||||
name = "Test server"
|
||||
desc = "<Server description> Game & mods"
|
||||
@@ -31,5 +34,7 @@ def test_html_escaping() -> None:
|
||||
controller = MockController()
|
||||
dialog = ServerDetailsDialog(controller, details)
|
||||
dialog.destroy()
|
||||
while Gtk.events_pending():
|
||||
Gtk.main_iteration()
|
||||
|
||||
assert dialog.description.get_text() == desc
|
||||
@@ -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,78 @@
|
||||
import pytest
|
||||
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
|
||||
|
||||
pytestmark = pytest.mark.flatpak
|
||||
|
||||
|
||||
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:
|
||||
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