feat: uninstall wizard WIP

This commit is contained in:
aclist 2026-08-20 10:13:37 +09:00 committed by GitHub
parent 63c2b5acd4
commit 93d870b5bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 293 additions and 19 deletions

View File

@ -160,6 +160,13 @@ class Shortcuts:
NEW_ENTRY["tags"] = {}
return NEW_ENTRY
def delete_shortcut(self, start_path: Path) -> None:
for s in self.shortcuts["shortcuts"]:
if self.shortcuts[s]["StartDir"] == str(start_path):
del self.shortcuts[s]
break
self.save_shortcuts()
def save_shortcuts(self) -> None:
try:
backup = self.shortcuts_path.with_suffix(".vdf.bak")

View File

@ -8,30 +8,26 @@ from dzgui.init.prefix import get_version
from dzgui.util.map_count import set_map_count
from dzgui.util.strings import flags
from dzgui.views.dialogs.uninstall import UninstallWizard
from dzgui.config.xdg import get_xdg_paths
parser = argparse.ArgumentParser(description=flags.description)
parser.add_argument("-d", "--debug", action="store_true", help=flags.debug)
parser.add_argument("-m", "--map", action="store_true", help=flags.map_count)
parser.add_argument("-u", "--uninstall", action="store_true", help=flags.uninstall)
parser.add_argument("-v", "--version", action="store_true", help=flags.version)
group = parser.add_mutually_exclusive_group()
group.add_argument("-d", "--debug", action="store_true", help=flags.debug)
group.add_argument("-m", "--map", action="store_true", help=flags.map_count)
group.add_argument("-u", "--uninstall", action="store_true", help=flags.uninstall)
group.add_argument("-v", "--version", action="store_true", help=flags.version)
args = parser.parse_args()
def uninstall() -> None:
# TODO: uninstall data files (-u)
# -u removes state, log, freedesktop
# XDG_SHARE_HOME/dzgui
# XDG_STATE_HOME/dzgui
# XDG_DATA_HOME/dzgui
pass
def main() -> None:
# TODO: isolate single flags
if args.version is True:
print(get_version())
sys.exit(0)
if args.uninstall is True:
uninstall()
paths = get_xdg_paths()
UninstallWizard(False, paths)
sys.exit(0)
if args.debug is True:
warnings.filterwarnings("default", category=DeprecationWarning)

View File

@ -0,0 +1,38 @@
pg1_title = "Choose uninstall targets"
pg1_heading = "Choose uninstall targets"
pg1_blurb = "Here you can choose which DZGUI configuration/state files to remove. "
pg2_title = "Complete"
pg2_heading = "Uninstall complete"
pg2_blurb = "Finished removing the selected files. See the system log for errors."
config_label = "Configuration files"
config_details = (
"User preferences and API key. "
"Keep this file if you want to import it again later."
)
desktop_label = "Desktop shortcut"
desktop_details = "Shortcut set on the desktop, if applicable."
start_menu_label = "Start menu shortcut"
start_menu_details = (
"Shortcut set under the Games category of the start menu, if applicable."
)
state_label = "Long-term state files"
state_details = (
"UI size, filter preferences, column size, server notes, and mod signatures."
)
steam_shortcut = "Steam shortcut"
steam_details = "Shortcut to DZGUI installed within Steam, if applicable. Requires Steam to be restarted to reflect the change."
path_remove_prefix = "The following path will be removed:"
not_installed = "It looks like DZGUI was not previously set up; nothing to uninstall."
standalone_uninstall = (
"It looks like you are running the standalone version of DZGUI. "
"To finalize the uninstall process, you can remove the 'dzgui' file."
)
system_uninstall = (
"It looks like DZGUI was installed via your system package manager or directly from source code.\n"
"To finalize the uninstall process, follow the standard removal procedure for that context."
)

View File

@ -0,0 +1,226 @@
import os
import subprocess
from pathlib import Path
from typing import Self, TYPE_CHECKING
from dzgui.api.shortcuts import Shortcuts
from dzgui.const.constants import APP_NAME
from dzgui.strings import uninstall
from dzgui.util._json import read_json
from dzgui.util.css import load_css
from dzgui.views.components.box import HBox
from dzgui.views.dialogs.wizard import ScrolledWizardPage, CheckboxWithLabel
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Pango # noqa E402
class Assistant(Gtk.Assistant):
def __init__(self, is_deck: bool, paths: dict[str, str]):
super().__init__()
if is_deck:
# NOTE: deemed to be "safe" dimensions that exclude taskbar size
self.set_default_size(1085, 670)
else:
self.set_default_size(1500, 900)
self.set_forward_page_func(self._advance_page)
pyapp = os.getenv("PYAPP")
self.page1 = UninstallPage(paths, pyapp)
self.page2 = CompletionPage(pyapp)
self.append_page(self.page1)
self.set_page_type(self.page1, Gtk.AssistantPageType.INTRO)
self.set_page_title(self.page1, uninstall.pg1_title)
self.set_page_complete(self.page1, True)
self.append_page(self.page2)
self.set_page_title(self.page2, uninstall.pg2_title)
self.set_page_type(self.page2, Gtk.AssistantPageType.SUMMARY)
self.connect("cancel", self.destroy_and_quit)
self.connect("close", self.destroy_and_quit)
self.show_all()
load_css()
def _advance_page(self, index: int) -> int:
cur_page = self.get_nth_page(index)
if cur_page == self.page1:
self.page1.uninstall()
return index + 1
def destroy_and_quit(self, widget: Self) -> None:
self.destroy()
Gtk.main_quit()
class Window(Gtk.Window):
def __init__(self, is_deck: bool, paths: dict[str, str]) -> None:
super().__init__(title=APP_NAME, icon_name=APP_NAME)
self.assistant = Assistant(is_deck, paths)
class CompletionPage(ScrolledWizardPage):
def __init__(self, pyapp: str | None):
super().__init__(heading=uninstall.pg2_heading, description=uninstall.pg2_blurb)
text = (
uninstall.standalone_uninstall
if pyapp is not None
else uninstall.system_uninstall
)
label = Gtk.Label(
label=text,
wrap_mode=Pango.WrapMode.WORD,
margin=10,
max_width_chars=80,
justify=Gtk.Justification.CENTER,
)
frame = Gtk.Frame()
frame.add(label)
self.add_start(frame)
class FileLabel(HBox):
def __init__(self, path: Path) -> None:
super().__init__(spacing=10)
label = Gtk.Label(label=uninstall.path_remove_prefix, halign=Gtk.Align.START)
textview = Gtk.TextView(
editable=False,
halign=Gtk.Align.START,
left_margin=10,
right_margin=10,
)
textview.set_buffer(Gtk.TextBuffer(text=str(path)))
self.extend([label, textview])
class CheckboxWithPath(CheckboxWithLabel):
def __init__(
self, label: str, details: str, path: Path, active: bool = True
) -> None:
super().__init__(text=label, blurb_text=details)
self.conf_path = path
self.set_active(active)
fl = FileLabel(path)
self.indent_below(fl)
def get_conf_path(self) -> Path:
return self.conf_path
class UninstallPage(ScrolledWizardPage):
def __init__(self, paths: dict[str, str], pyapp: str | None):
super().__init__(heading=uninstall.pg1_heading, description=uninstall.pg1_blurb)
self.pyapp = pyapp
config = Path(paths["XDG_CONFIG_HOME"])
state = Path(paths["XDG_STATE_HOME"])
self.share = Path(paths["XDG_DATA_HOME"])
shortcut = self.share.parent.joinpath("applications/dzgui.desktop")
desktop = config.parent.parent.joinpath("Desktop/dzgui.desktop")
self.steam_path = self.parse_steam_path(config, self.share)
self.boxes: list[CheckboxWithPath] = []
self.checks_area = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, spacing=20, margin_top=20
)
self.config_box = CheckboxWithPath(
uninstall.config_label, uninstall.config_details, config, active=False
)
self.desktop_box = CheckboxWithPath(
uninstall.desktop_label, uninstall.desktop_details, desktop
)
self.start_menu_box = CheckboxWithPath(
uninstall.start_menu_label, uninstall.start_menu_details, shortcut
)
self.share_box = CheckboxWithPath(
uninstall.desktop_label, uninstall.desktop_details, desktop
)
self.state_box = CheckboxWithPath(
uninstall.state_label, uninstall.state_details, state
)
self.steam_box = CheckboxWithLabel(
uninstall.steam_shortcut, uninstall.steam_details
)
for box in (
self.config_box,
self.state_box,
self.desktop_box,
self.start_menu_box,
):
self.checks_area.add(box)
self.boxes.append(box)
self.checks_area.add(self.steam_box)
self.add_start(self.checks_area)
self.show_all()
def parse_steam_path(self, config: Path, steam: Path) -> Path | None:
try:
file = config.joinpath("config.json")
conf = read_json(file)
steam = conf["default_steam_path"]
self.steam_path = Path(steam)
return Path(steam)
except Exception as e:
print(e)
return None
def wipe_conf_file(self, box: CheckboxWithPath) -> None:
if box.get_active():
path = box.get_conf_path()
try:
# TODO:
# path.unlink()
print(f"Deleted '{path}'")
except Exception as e:
print(e)
def wipe_steam_shortcut(self) -> None:
# TODO:
pass
# try:
# shortcuts = Shortcuts(self.steam_path)
# shortcuts.delete_shortcut(self.share)
# except Exception as e:
# print(e)
def wipe_pyapp(self) -> None:
if self.pyapp is None:
return
res = subprocess.run([self.pyapp, "self", "remove"])
print(res)
def uninstall(self) -> None:
for box in self.boxes:
self.wipe_conf_file(box)
if self.steam_box.get_active():
self.wipe_steam_shortcut()
# TODO:
# self.wipe_pyapp()
pass
class UninstallWizard(Gtk.Application):
def __init__(self, is_deck: bool, paths: dict[str, str]) -> None:
super().__init__()
config = Path(paths["XDG_CONFIG_HOME"])
# TODO: tests
if not config.is_dir() or not config.joinpath("config.json").is_file():
print(uninstall.not_installed)
return
GLib.set_prgname(APP_NAME)
self.win = Window(is_deck, paths)
Gtk.main()

View File

@ -99,6 +99,8 @@ class ScrolledWizardPage(Gtk.ScrolledWindow):
self.box.pack_start(image, expand=False, fill=True, padding=0)
self.add(self.box)
self.box.pack_start(self.heading, expand=False, fill=True, padding=0)
self.box.pack_start(self.description, expand=False, fill=True, padding=0)
def get_page_type(self) -> Gtk.AssistantPageType:
return self.page_type
@ -123,12 +125,9 @@ class EnumeratedWizardPage(ScrolledWizardPage):
def __init__(self, enum: PageNum, heading: str, description: str) -> None:
super().__init__(heading=heading, description=description)
self.enum = enum
self.prog = Progress()
self.box.pack_end(self.prog, expand=False, fill=False, padding=0)
self.box.pack_start(self.heading, expand=False, fill=True, padding=0)
self.box.pack_start(self.description, expand=False, fill=True, padding=0)
def get_enum(self) -> PageNum:
return self.enum
@ -541,7 +540,9 @@ class Assistant(Gtk.Assistant):
return
self.set_page_complete(page, True)
def _add_page(self, page: EnumeratedWizardPage, ptype: Gtk.AssistantPageType) -> None:
def _add_page(
self, page: EnumeratedWizardPage, ptype: Gtk.AssistantPageType
) -> None:
self.append_page(page)
self.set_page_type(page, ptype)
self.set_page_title(page, page.get_title())
@ -567,15 +568,21 @@ class CheckboxWithLabel(Gtk.Box):
def __init__(self, text: str, blurb_text: str) -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=5)
self.indent = 20
self.button = Gtk.CheckButton(label=text)
self.button.set_active(True)
label = Gtk.Label(label="", halign=Gtk.Align.START, margin_start=20)
label = Gtk.Label(label="", halign=Gtk.Align.START, margin_start=self.indent)
wrapped = textwrap.fill(blurb_text, width=100)
label.set_markup(f"- {wrapped}")
for el in self.button, label:
self.add(el)
def indent_below(self, widget: Gtk.Widget) -> None:
widget.set_margin_start(self.indent)
self.add(widget)
def get_checkbox(self) -> Gtk.CheckButton:
return self.button