Compare commits

...
17 Commits
Author SHA1 Message Date
aclist cdec81071d feat: show notice on override
Mirror to Codeberg / mirror-to-codeberg (push) Canceled after 0s
2026-10-23 15:39:56 +09:00
aclist 97b5c0e6ee fix: split arguments 2026-10-23 15:19:37 +09:00
aclist 67c19fb90d feat: add SimpleTree 2026-10-23 15:11:05 +09:00
aclist d9d3f747bb feat: add override module 2026-10-23 15:10:24 +09:00
aclist 1440871819 chore: update strings 2026-10-23 14:14:44 +09:00
aclist 761988d8c2 feat: add override module 2026-10-23 14:14:30 +09:00
aclist ab5637ae3b Merge branch 'dzgui7' into feat/dynamic-flatpak-toggle 2026-10-23 12:35:53 +09:00
aclist 682532a867 chore: remove erroneous debug string 2026-10-23 12:34:04 +09:00
aclist d577ba0970 change: remove Flatpak option if binary is not installed 2026-10-23 12:20:10 +09:00
aclist c2993ad289 change: revert prior VDF change, no observable effect on D-Bus 2026-10-23 12:11:19 +09:00
aclist edb87893f9 chore: clear typehinting errors 2026-10-23 11:46:05 +09:00
aclist 519e6c354d chore: patch VDF changes from main 2026-10-23 11:42:08 +09:00
aclist e3d1d62b4d Merge pull request #499 from aclist/fix/vdf-missing
Mirror to Codeberg / mirror-to-codeberg (push) Canceled after 0s
fix: missing shortcuts file
2026-10-23 11:38:48 +09:00
aclist 1dcfdda657 change: allow D-Bus registration when sandboxed 2026-10-23 11:36:13 +09:00
aclist 019847dcf9 fix: handle missing grid directory 2026-10-23 11:35:13 +09:00
aclist c2cfe2b4d9 fix: handle missing shortcut file 2026-10-23 11:34:52 +09:00
aclist 01b3babdf5 feat: FlatpakOverridePage WIP 2026-10-23 11:23:28 +09:00
10 changed files with 203 additions and 26 deletions

No files matched your search

+8 -1
View File
@@ -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
@@ -172,6 +176,9 @@ class Shortcuts:
try:
backup = self.shortcuts_path.with_suffix(".vdf.bak")
shutil.copy(self.shortcuts_path, backup)
except Exception as e:
logger.critical(e)
try:
with open(self.shortcuts_path, "wb") as f:
vdf.binary_dump(self.shortcuts, f)
except Exception as e:
+5
View File
@@ -13,6 +13,7 @@ from warnings import deprecated
from dzgui.api.acf import ACF
from dzgui.init.prereqs import has_any_client
from dzgui.init.proc import has_cmd
from dzgui.const.constants import (
APPID_DAYZ,
APPID_DAYZ_EXP,
@@ -22,6 +23,7 @@ from dzgui.const.constants import (
DEBIAN_STEAM_PATH,
DEFAULT_STEAM_PATH,
DOWNLOADS_PATH,
FLATPAK_CMD,
FLATPAK_STEAM_PATH,
LIBRARYFOLDERS_PATH,
UBUNTU_STEAM_PATH,
@@ -82,6 +84,8 @@ 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_cmd(FLATPAK_CMD) is False:
continue
if path.joinpath(VDF_PATH).is_file():
paths.append((path, human[path]))
return paths
@@ -198,6 +202,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),
+1 -1
View File
@@ -148,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
+10 -2
View File
@@ -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:
+10 -3
View File
@@ -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,
@@ -26,7 +27,13 @@ def is_steam_running(cmd: str) -> bool:
raise TypeError("Not a valid Steam client selection")
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(
+13
View File
@@ -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"
@@ -80,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."
+7
View File
@@ -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)
+31
View File
@@ -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)
+83 -19
View File
@@ -12,22 +12,24 @@ from dzgui.const.constants import (
APP_NAME,
APP_NAME_LOWER,
HERO_PATH,
FLATPAK_STEAM_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 path_to_steam_cmd
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.mixins.colorscheme import ColorAwareApp
from dzgui.views.trees.tree_simple import SimpleTree
import gi
@@ -45,7 +47,8 @@ class PageNum(Enum):
STEAM_API = 4
USER_PREFS = 5
SHORTCUTS = 6
FINAL = 7
FLATPAK = 7
FINAL = 8
class OptionalPageMixin:
@@ -144,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,
@@ -158,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)
@@ -243,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):
@@ -405,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__(
@@ -418,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
@@ -437,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)
@@ -456,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
@@ -476,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:
@@ -489,7 +544,7 @@ 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():
path = page.get_path_from_radio()
self.config_values["default_steam_path"] = path
@@ -506,10 +561,22 @@ class Assistant(Gtk.Assistant):
)
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
@@ -625,12 +692,6 @@ class ShortcutCreationPage(OptionalPageMixin, EnumeratedWizardPage): # type: ig
self.add_start(self.checks_area)
self.show_all()
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:
state = button.get_active()
@@ -641,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():
@@ -709,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:
@@ -720,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):
+35
View File
@@ -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