feat: generate shortcuts

This commit is contained in:
aclist committed 2026-07-11 15:33:52 +09:00
1 parent b50d061e1f
commit caf071f997
13 files changed
+380 -40

No files matched your search

+166
View File
@@ -0,0 +1,166 @@
import binascii
import ctypes
import logging
import shutil
import vdf # type: ignore
from dataclasses import dataclass
from importlib import resources
from pathlib import Path
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
logger = logging.getLogger(APP_NAME)
@dataclass(slots=True, frozen=True)
class ShortcutMetadata:
appname: str
appid: int
exe_path: str
start_dir: str
icon: str
class Shortcuts:
def __init__(self, steam_path: Path) -> None:
uid = find_user_id_32(steam_path)
self.uid = uid
self.user_config_path = steam_path.joinpath(f"userdata/{uid}/config")
self.shortcuts_path = self.user_config_path.joinpath("shortcuts.vdf")
self._load_shortcuts(self.shortcuts_path)
@classmethod
def gen_bpid(cls, uid: str) -> int:
u = uid.encode()
encoded = binascii.crc32(u)
return encoded | 0x80000000
@classmethod
def gen_signed_appid(cls, appid: int) -> int:
return ctypes.c_int(appid).value
@classmethod
def gen_exe_uid(cls, appname: str, exe: Path) -> str:
wrapped_exe = f'"{exe}"'
return appname + wrapped_exe
def get_shortcuts(self) -> Any:
return self.shortcuts
def _load_shortcuts(self, path: Path) -> None:
try:
with open(path, "rb") as f:
shortcuts = vdf.binary_load(f)
self.shortcuts = shortcuts
except Exception as e:
logger.warning(e)
raise e
def add_shortcut(
self, appname: str, start_dir: Path, exe_path: Path, icon: Path
) -> None:
uid = self.gen_exe_uid(appname, exe_path)
self.bpid = self.gen_bpid(uid)
self.signed_appid = self.gen_signed_appid(self.bpid)
meta = ShortcutMetadata(
appname, self.signed_appid, str(exe_path), str(start_dir), str(icon)
)
entry = self._create_entry(meta)
self._insert_at_last_index(entry)
def add_grid_images(self, images: Path) -> None:
"""
{BPID}_hero.png: hero (splash)
{BPID}p.ping: portrait boxart in library
{BPID}_logo.png: logo overlay on top of hero
{BPID}.png: legacy Big Picture/landscape image
"""
grid_path = self.user_config_path.joinpath("grid")
for img in ("_hero", "p", "_logo"):
filename = str(self.bpid) + img
dest = grid_path.joinpath(filename).with_suffix(".png")
b = images.joinpath(img).with_suffix(".png").read_bytes()
dest.write_bytes(b)
# NOTE: legacy Big Picture header
b = images.joinpath("_hero.png").read_bytes()
dest = grid_path.joinpath(str(self.bpid)).with_suffix(".png")
dest.write_bytes(b)
def _insert_at_last_index(self, entry: dict[str, Any]) -> None:
last = list(self.shortcuts["shortcuts"].keys())[-1]
n = int(last) + 1
self.shortcuts["shortcuts"][str(n)] = entry
@classmethod
def _create_entry(cls, meta: ShortcutMetadata) -> dict[str, Any]:
"""
https://developer.valvesoftware.com/wiki/Add_Non-Steam_Game
https://developer.valvesoftware.com/wiki/Steam_Library_Shortcuts
Wiki variously lists keys with title case and lowercase, but keys actually do
not seem to be case-sensitive. Some entries generated by Steam do not match the wiki
Keys are entered into the dictionary in a linear insertion order
# TODO: try to replicate key case sensitivity as it is created by Steam with generic shortcut
appid: signed int CRC
exe: absolute path to the executable, must be wrapped in literal quotes
StartDir: directory the executable starts in, generally the parent
"""
NEW_ENTRY: dict[str, Any] = {}
NEW_ENTRY["appid"] = meta.appid
NEW_ENTRY["appname"] = meta.appname
NEW_ENTRY["exe"] = f'"{meta.exe_path}"'
NEW_ENTRY["StartDir"] = f"{meta.start_dir}"
NEW_ENTRY["icon"] = meta.icon
NEW_ENTRY["ShortcutPath"] = ""
NEW_ENTRY["LaunchOptions"] = ""
NEW_ENTRY["IsHidden"] = 0
NEW_ENTRY["AllowDesktopConfig"] = 1
NEW_ENTRY["AllowOverlay"] = 1
NEW_ENTRY["openvr"] = 0
NEW_ENTRY["Devkit"] = 0
NEW_ENTRY["DevkitGameID"] = ""
NEW_ENTRY["DevkitOverrideAppID"] = 0
NEW_ENTRY["LastPlayTime"] = 0
NEW_ENTRY["FlatpakAppID"] = ""
NEW_ENTRY["sortas"] = ""
NEW_ENTRY["tags"] = {}
return NEW_ENTRY
def save_shortcuts(self) -> None:
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:
logger.critical(e)
def add_steam_shortcut(steam_path: Path, exe_path: Path) -> None:
try:
start_dir = exe_path.parent
traversable = resources.files(APP_NAME_LOWER).joinpath(IMAGES_PATH)
images = Path(str(traversable))
icon = images.joinpath("icon.png")
# TEST: assert that record (start dir and exe combo) is in shortcuts after creation
shortcuts = Shortcuts(Path(steam_path))
shortcuts.add_shortcut(APP_NAME, start_dir, exe_path, icon)
shortcuts.save_shortcuts()
shortcuts.add_grid_images(images)
copy_dzgui_to_xdg_data(exe_path)
except Exception as e:
logger.warning(e)
+6 -14
View File
@@ -219,6 +219,12 @@ def find_user_id(path: Path) -> str | None:
logger.warn(e) logger.warn(e)
return None return None
def find_user_id_32(path: Path) -> int:
uid = find_user_id(path)
if uid is None:
raise ValueError("Failed to parse a valid Steam32 ID")
return int(uid) & 0xFFFFFFFF
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]] = {
@@ -243,20 +249,6 @@ def unsubscribe(key: str, mod: int) -> None:
update_workshop(key, mod, UNSUB_ENDPOINT) update_workshop(key, mod, UNSUB_ENDPOINT)
def gen_shortcut() -> None:
# TODO:
"""
during setup, prompt user to select matching user id from loginusers
show user account name, select outer steam id
steam/userdata/<steamid_32>/config/shortcuts.vdf
"""
# STEAMID_MAGIC = 76561197960265728
# STEAMID_64 - STEAMID_MAGIC = STEAMID32
# or get right-most 32 bits
# STEAMID_64 & 0xFFFFFFFF
pass
@deprecated("Use subscribe()") @deprecated("Use subscribe()")
def enqueue_mod(client: str, mod: str, appid: int) -> None: def enqueue_mod(client: str, mod: str, appid: int) -> None:
client_args = concat_bash_args(client) client_args = concat_bash_args(client)
+8 -6
View File
@@ -20,6 +20,7 @@ from dzgui.strings import boot
# from dzgui.util.map_count import get_map_count # from dzgui.util.map_count import get_map_count
from dzgui.util.deck import is_steam_deck, is_game_mode from dzgui.util.deck import is_steam_deck, is_game_mode
from dzgui.util.dirs import make_parents
from dzgui.util.localize import set_locale from dzgui.util.localize import set_locale
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
@@ -34,10 +35,7 @@ if TYPE_CHECKING:
logger = logging.getLogger(APP_NAME) logger = logging.getLogger(APP_NAME)
# TODO: profile load time # TODO: profile load time
def make_parents(path: "Path") -> None:
path.parent.mkdir(parents=True, exist_ok=True)
def setup_logger(log_path: "Path") -> None: def setup_logger(log_path: "Path") -> None:
@@ -124,11 +122,15 @@ def load_gui(version: str, is_debug: bool) -> None:
migrate_cols_file(XDG.columns) migrate_cols_file(XDG.columns)
copy_state_files(xdg_paths["XDG_STATE_HOME"]) copy_state_files(xdg_paths["XDG_STATE_HOME"])
# TODO: add logging inside wizard # TODO: add logging inside wizard
SetupWizard(_is_steam_deck, XDG.config) wizard = SetupWizard(_is_steam_deck, XDG)
if not wizard.is_setup_complete():
return
# TODO: drop
# NOTE: implies that setup wizard failed or was closed # NOTE: implies that setup wizard failed or was closed
if has_new_config(XDG.config) is False: #if has_new_config(XDG.config) is False:
return # return
setup_logger(XDG.debug) setup_logger(XDG.debug)
with open(XDG.debug, "w") as f: with open(XDG.debug, "w") as f:
+19 -4
View File
@@ -1,18 +1,33 @@
import textwrap import textwrap
from pathlib import Path from pathlib import Path
from dzgui.util.dirs import copy_dzgui_to_xdg_data, find_icon_resource, make_parents
def write_desktop_file(exe_path: Path) -> None:
icon = find_icon_resource()
def write_desktop_file(share_path: Path) -> None:
template = f"""\ template = f"""\
[Desktop Entry] [Desktop Entry]
Version=1.0 Version=1.0
Type=Application Type=Application
Terminal=false Terminal=false
Exec={share_path}/launch.sh Exec={exe_path}
Name=DZGUI Name=DZGUI
Comment=dzgui Comment=dzgui
Icon={share_path}/dzgui Icon={icon}
Categories=Game""" Categories=Game"""
file = share_path / "dzgui.desktop" copy_dzgui_to_xdg_data(exe_path)
share_path = exe_path.parent.parent
file = share_path.joinpath("applications/dzgui.desktop")
make_parents(file)
file.write_text(textwrap.dedent(template)) file.write_text(textwrap.dedent(template))
def write_desktop_shortcut(exe_path: Path) -> None:
# TODO: symlink the above, /Desktop
# TODO: dynamically link to prior option in UI
pass
+6 -1
View File
@@ -18,6 +18,7 @@ class Xdg:
debug: Path debug: Path
ips: Path ips: Path
filters: Path filters: Path
shortcut: Path
def is_writeable(path_str: str) -> bool: def is_writeable(path_str: str) -> bool:
@@ -67,6 +68,7 @@ def get_xdg_paths() -> dict:
def parse_filepaths(xdg: dict) -> Xdg: def parse_filepaths(xdg: dict) -> Xdg:
config = xdg["XDG_CONFIG_HOME"] config = xdg["XDG_CONFIG_HOME"]
state = xdg["XDG_STATE_HOME"] state = xdg["XDG_STATE_HOME"]
share = xdg["XDG_DATA_HOME"]
config = config / "config.json" config = config / "config.json"
@@ -81,6 +83,8 @@ def parse_filepaths(xdg: dict) -> Xdg:
ips = state / "ips.csv" ips = state / "ips.csv"
filters = state / "dzg.filters.json" filters = state / "dzg.filters.json"
shortcut = share / APP_NAME_LOWER
return Xdg( return Xdg(
config, config,
columns, columns,
@@ -91,5 +95,6 @@ def parse_filepaths(xdg: dict) -> Xdg:
system, system,
debug, debug,
ips, ips,
filters filters,
shortcut,
) )
+3 -1
View File
@@ -72,7 +72,9 @@ LEGACY_IPS_PATH = ".local/share/dzgui/helpers/ips.csv"
DEBUG_LOG = f"{APP_NAME}_DEBUG.LOG" DEBUG_LOG = f"{APP_NAME}_DEBUG.LOG"
SYSTEM_LOG = f"{APP_NAME}_SYSTEM.LOG" SYSTEM_LOG = f"{APP_NAME}_SYSTEM.LOG"
CHANGELOG_PATH = "data/CHANGELOG.md" CHANGELOG_PATH = "data/CHANGELOG.md"
HERO_PATH = "data/images/hero.png" IMAGES_PATH = "data/images"
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"
File renamed without changes.
File renamed without changes.
File renamed without changes.
+7
View File
@@ -64,6 +64,13 @@ radio_mi = "mi (miles)"
label_dist = "Distance display" label_dist = "Distance display"
label_client = "Steam client" label_client = "Steam client"
### Shortcuts page
heading_shortcuts = "Create shortcuts"
blurb_shortcuts = "You can optionally create shortcuts to facilitate launching DZGUI faster. Note that if you create a Steam shortcut, you <b>must restart Steam</b> for these changes to take effect."
button_shortcuts = "Create shortcuts"
checkbox_steam_shortcut = ("Add a shortcut to Steam", "This will not replace or update existing shortcuts.")
checkbox_start_menu = ("Add a start menu shortcut", "Depends on local window manager settings. Will work on Steam Deck.")
checkbox_desktop_shortcut = ("Add a desktop shortcut", "Depends on the option above.")
### Completion page ### Completion page
heading_completion = "Setup complete" heading_completion = "Setup complete"
+1 -1
View File
@@ -41,10 +41,10 @@ def print_mods(mods: list[int]) -> str:
def write_diagnostic(config: Path, outfile: Path) -> None: def write_diagnostic(config: Path, outfile: Path) -> None:
# TODO: test availability on other distros
date = datetime.now().isoformat() date = datetime.now().isoformat()
try: try:
# FIXME: ID_LIKE not available on CachyOS
distro = platform.freedesktop_os_release()["ID_LIKE"] distro = platform.freedesktop_os_release()["ID_LIKE"]
except Exception as e: except Exception as e:
logger.warn(e) logger.warn(e)
+23
View File
@@ -0,0 +1,23 @@
import os
import shutil
from importlib import resources
from pathlib import Path
from dzgui.const.constants import APP_NAME_LOWER, IMAGES_PATH
def make_parents(path: "Path") -> None:
path.parent.mkdir(parents=True, exist_ok=True)
def copy_dzgui_to_xdg_data(exe_path: Path) -> None:
dzgui = os.getenv("PYAPP")
if dzgui is None:
return
make_parents(exe_path)
shutil.copy(dzgui, exe_path)
def find_icon_resource() -> "Path":
traversable = resources.files(APP_NAME_LOWER).joinpath(IMAGES_PATH)
images = Path(str(traversable))
icon = images.joinpath("icon.png")
return icon
+141 -13
View File
@@ -1,11 +1,13 @@
import os
import textwrap import textwrap
from enum import Enum from enum import Enum
from importlib import resources from importlib import resources
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Self from typing import Any, Callable, Self, TYPE_CHECKING
from dzgui.api.probe import test_steam_api, test_bm_api from dzgui.api.probe import test_steam_api, test_bm_api
from dzgui.api.shortcuts import add_steam_shortcut
from dzgui.api.steam import get_steam_paths from dzgui.api.steam import get_steam_paths
from dzgui.const.constants import ( from dzgui.const.constants import (
APP_NAME, APP_NAME,
@@ -15,6 +17,8 @@ from dzgui.const.constants import (
) )
from dzgui.const.boilerplate import config_boilerplate from dzgui.const.boilerplate import config_boilerplate
from dzgui.const.endpoints import BM_API_SETUP, STEAM_API_SETUP from dzgui.const.endpoints import BM_API_SETUP, STEAM_API_SETUP
from dzgui.const.enum import Preferences
from dzgui.config.query import lookup
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
@@ -30,6 +34,9 @@ import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gdk, Gtk, GLib, GObject, GdkPixbuf # noqa E402 from gi.repository import Gdk, Gtk, GLib, GObject, GdkPixbuf # noqa E402
if TYPE_CHECKING:
from dzgui.config.xdg import Xdg
class PageNum(Enum): class PageNum(Enum):
INTRO = 1 INTRO = 1
@@ -38,7 +45,14 @@ class PageNum(Enum):
STEAM_API = 4 STEAM_API = 4
BM_API = 5 BM_API = 5
USER_PREFS = 6 USER_PREFS = 6
FINAL = 7 SHORTCUTS = 7
FINAL = 8
class Shortcut(Enum):
STEAM = 1
DESKTOP = 2
START = 3
class DescriptionArea(Gtk.Box): class DescriptionArea(Gtk.Box):
@@ -141,6 +155,10 @@ class NotificationFrame(Gtk.Frame):
if error: if error:
add_class(self, "error-frame") add_class(self, "error-frame")
def set_text(self, text: str) -> None:
wrapped = textwrap.fill(text, width=80)
self.label.set_markup(wrapped)
class APIValidationPage(ScrolledWizardPage): class APIValidationPage(ScrolledWizardPage):
def __init__( def __init__(
@@ -339,6 +357,7 @@ class ConfigMigrationPage(ScrolledWizardPage):
def _on_import_clicked(self, button: Gtk.Button) -> None: def _on_import_clicked(self, button: Gtk.Button) -> None:
self.grid.set_sensitive(False) self.grid.set_sensitive(False)
try: try:
# TODO: this could be deferred to the final page (prevents accidental destruction of dialog via ESC)
migrate_legacy_conf(self.config) migrate_legacy_conf(self.config)
self.migrated = True self.migrated = True
self.success_box.set_visible(True) self.success_box.set_visible(True)
@@ -423,24 +442,27 @@ class CompletionPage(ScrolledWizardPage):
class Assistant(Gtk.Assistant): class Assistant(Gtk.Assistant):
def __init__(self, is_deck: bool, config: Path): def __init__(self, is_deck: bool, XDG: "Xdg"):
super().__init__() super().__init__()
if is_deck: if is_deck:
self.fullscreen() self.fullscreen()
else: else:
self.set_default_size(1500, 900) self.set_default_size(1500, 900)
self.config_path = config self.config_path = XDG.config
self.config_values: dict[str, Any] = config_boilerplate self.config_values: dict[str, Any] = config_boilerplate
self.setup_complete = False
self.page1 = IntroductionPage() self.page1 = IntroductionPage()
self.page2 = ConfigMigrationPage(config) self.page2 = ConfigMigrationPage(XDG.config)
self.page3 = SteamPathPage() self.page3 = SteamPathPage()
self.page4 = SteamValidationPage() self.page4 = SteamValidationPage()
self.page5 = BMValidationPage() self.page5 = BMValidationPage()
self.page6 = PreferencesPage() self.page6 = PreferencesPage()
self.page7 = CompletionPage() self.page7 = ShortcutCreationPage(XDG.shortcut)
self.page8 = CompletionPage()
self.set_forward_page_func(self._advance_page) self.set_forward_page_func(self._advance_page)
@@ -458,9 +480,18 @@ class Assistant(Gtk.Assistant):
self.page5, self.page5,
self.page6, self.page6,
self.page7, self.page7,
self.page8,
): ):
# NOTE: skip config migration page if no legacy config file # NOTE: skip config migration page if no legacy config file
if page == self.page2 and self.has_legacy_config is False: if (
isinstance(page, ConfigMigrationPage)
and self.has_legacy_config is False
):
continue
# NOTE: disabled for now on system-provided packages
# TODO: drop, for testing purposes
os.environ["PYAPP"] = str(Path.home().joinpath("dzgui/dzgui"))
if isinstance(page, ShortcutCreationPage) and os.getenv("PYAPP") is None:
continue continue
self._add_page(page, page.get_page_type()) self._add_page(page, page.get_page_type())
@@ -475,13 +506,15 @@ class Assistant(Gtk.Assistant):
def _advance_page(self, index: int) -> int: def _advance_page(self, index: int) -> int:
page = self.get_nth_page(index) page = self.get_nth_page(index)
# TODO: use enums # TODO: use enums/isinstance
match page: match page:
case self.page1: case self.page1:
pass pass
case self.page2: case self.page2:
if self.page2.is_migrated(): if self.page2.is_migrated():
return self.get_n_pages() - 1 steam_path = lookup(self.config_path, Preferences.DEFAULT)
self.page7.set_steam_path(steam_path)
return self.get_n_pages() - 2
case self.page3: case self.page3:
self.config_values["default_steam_path"] = page.get_path_from_radio() self.config_values["default_steam_path"] = page.get_path_from_radio()
case self.page4: case self.page4:
@@ -495,10 +528,17 @@ class Assistant(Gtk.Assistant):
self.config_values["use_miles"] = use_miles self.config_values["use_miles"] = use_miles
self.config_values["client"] = client self.config_values["client"] = client
self.write_config() self.write_config()
self.page7.set_steam_path(self.config_values["default_steam_path"])
case self.page7:
self.page7.create_shortcuts()
self.setup_complete = True
case _: case _:
raise AttributeError("Trying to advance a non-canonical page") raise AttributeError("Trying to advance a non-canonical page")
return index + 1 return index + 1
def is_setup_complete(self) -> bool:
return self.setup_complete
def destroy_and_quit(self, widget: Self) -> None: def destroy_and_quit(self, widget: Self) -> None:
self.destroy() self.destroy()
Gtk.main_quit() Gtk.main_quit()
@@ -543,6 +583,91 @@ class Assistant(Gtk.Assistant):
EMITTER.emit("step_pending") EMITTER.emit("step_pending")
class CheckboxWithLabel(Gtk.Box):
def __init__(self, text: str, blurb_text: str, enum: Shortcut) -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=5)
self.enum = enum
self.button = Gtk.CheckButton(label=text)
self.button.set_active(True)
blurb = Gtk.Label(label=f"- {blurb_text}", halign=Gtk.Align.START, margin_start=20)
for el in self.button, blurb:
self.add(el)
def get_enum(self) -> Shortcut:
return self.enum
def get_active(self) -> bool:
return self.button.get_active()
class ShortcutCreationPage(ScrolledWizardPage):
def __init__(self, shortcut: Path) -> None:
super().__init__(
enum=PageNum.SHORTCUTS,
heading=wizard.heading_shortcuts,
description=wizard.blurb_shortcuts,
)
self.steam_path: Path
self.shortcut_path = shortcut
self.checks_area = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, spacing=10, margin_top=20
)
self.page_type = Gtk.AssistantPageType.INTRO
# TODO: pass enums
for checkbox in (
(wizard.checkbox_steam_shortcut, Shortcut.STEAM),
(wizard.checkbox_desktop_shortcut, Shortcut.DESKTOP),
(wizard.checkbox_start_menu, Shortcut.START),
):
box, enum = checkbox
label, blurb = box
checkbox = CheckboxWithLabel(label, blurb, enum)
self.checks_area.add(checkbox)
self.add_start(self.checks_area)
self.show_all()
self.connect("map", self._on_map)
"""
TODO: checkboxes for:
[] steam shortcut (adds a new shortcut. it will not replace or update existing DZGUI shortcuts)
[] add to desktop (PATH)
[] add to start menu (free desktop)
checkbox with blurb below
# TODO: warn about how to back up
"""
def _on_map(self, page: "ScrolledWizardPage") -> None:
EMITTER.emit("step_complete")
def set_steam_path(self, path: Path) -> None:
self.steam_path = path
# TODO: move elsewhere
def write_desktop_file(self) -> None:
# .local/share/applications/dzgui/dzgui.desktop
pass
def create_shortcuts(self) -> None:
# NOTE: best-effort, permissive even on failure (page is already marked as complete)
for box in self.checks_area.get_children():
if not box.get_active():
continue
match box.get_enum():
case Shortcut.STEAM:
add_steam_shortcut(self.steam_path, self.shortcut_path)
case Shortcut.DESKTOP:
pass
case Shortcut.START:
pass
case _:
pass
class SteamPathPage(ScrolledWizardPage): class SteamPathPage(ScrolledWizardPage):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
@@ -599,17 +724,20 @@ class SteamPathPage(ScrolledWizardPage):
class SetupWizard(Gtk.Application): class SetupWizard(Gtk.Application):
def __init__(self, is_deck: bool, config: Path) -> None: def __init__(self, is_deck: bool, XDG: "Xdg") -> None:
super().__init__() super().__init__()
GLib.set_prgname(APP_NAME) GLib.set_prgname(APP_NAME)
Window(is_deck, config) self.win = Window(is_deck, XDG)
Gtk.main() Gtk.main()
def is_setup_complete(self) -> int:
return self.win.assistant.is_setup_complete()
class Window(Gtk.Window): class Window(Gtk.Window):
def __init__(self, is_deck: bool, config: Path) -> None: def __init__(self, is_deck: bool, XDG: "Xdg") -> None:
super().__init__(title=APP_NAME, icon_name=APP_NAME) super().__init__(title=APP_NAME, icon_name=APP_NAME)
Assistant(is_deck, config) self.assistant = Assistant(is_deck, XDG)
class Emitter(GObject.GObject): class Emitter(GObject.GObject):