diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..63a5323 --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,124 @@ +# History of the project +This file collects notable milestones and events related to DZGUI, mainly around DZGUI as a framework and the history of development. +A chronological summary of features can be found in CHANGELOG.md. + +## 2021 +### 2021-12-03: DayZ adds support for Battleye over Proton +DayZ was among a handful of games with whitelisted anti-cheat support under Valve's emerging WINE-based Proton compatibility layer. +While adding anti-cheat compatibility at a technical level, this change did not actually allow the client to connect to servers running complex modlists. + +Development of the precursor to DZGUI began at around this time. + +### 2021-12-12: initial version of DZTUI is created +DZTUI, a Bash script and terminal user interface, was the precursor to DZGUI. +This was an initial attempt at bootstrapping the Steam client to handle mods correctly when launching DayZ. +Due to user interest, features continued to be added. This early tool is archived under the 'dztui' branch. + +## 2022 +### 2022-05-09: addition of zenity GUI and project name change (DZGUI) +With the introduction of the Steam Deck, more users wanted a graphical user interface +to interact with DZTUI. Being that DZTUI was purely a Bash script, a method of +drawing menus was needed, so the "zenity" package was used to create prefabricated dialogs. +While this met the minimum requirement to show prompts and selectable options, no rich customization was possible. + +The introduction of zenity as a dependency marked the shift from "DZTUI" to "DZGUI". + +Throughout 2022, the limits of zenity for DZGUI's purposes were tested, particularly in the context of SteamOS. +This led to the discovery of a number of bugs in zenity, GTK, and SteamOS, which were reported up to their respective channels. +While this was improving the SteamOS user experience, it started to become clear that a fully custom UI was needed. + +This barebones version of DZGUI sufficed for some time, with the Bash script and some standalone Python modules doing the heavy lifting, +and this period served to surface a number of bugs and interesting interactions with the Steam client. + +## 2023 +### 2023-11-23: Python 3.10 dependency +Prior to this time, DZGUI open-endedly allowed you to use any Python version for its modules, without any significant handling for +the idiosyncrasies of different versions. A minimum requirement of Python 3.10 was thus instituted to normalize expected behavior. + +Throughout 2023, various features related to server browsing were added, such as filters and more granular search parameters. + +There were still a lot of unknowns at this time, so things were being resolved through trial and error. + +## 2024 +### 2024-01-31: DZGUI 5 API introduces GTK UI +Using zenity as a simple GUI had exposed some bugs and limitations in that package, particularly when trying to push huge lists of server records +into tables and sorting them in a performant manner. + +Around this time it became clear that a more robust UI framework was needed. While keeping the original Bash script and Python modules for +background processing, PyGObject (GTK) was added to the project in order to ship a custom UI that could be designed from the ground up. + +However, it should be noted that at this point, the DZGUI logic itself was still a (increasingly large) Bash script to which a GTK UI had been stitched on. +These were totally separate components, with UI actions on the GTK side triggering calls to Bash functions in an overly complex helper file. +The UI was basically a thin client around the old Bash logic, but this was still a far cry from an integrated application with proper signals and dedicated components. + +Effectively, DZGUI 5 through 6 were a stepping stone that set the groundwork to go from DZGUI 4 (pure Bash script) to +DZGUI 7 (standalone Python application). Today, little of DZGUI 5's original logic remains outside of the basic UI grid. + +### 2024-10-15: Frostline DLC released, widespread parsing problems +DayZ's first DLC introduced a new format for server responses that packed extra metadata into the A2S_RULES format. +Upon its release, all third-party DayZ launchers and server listing web sites were unable to parse the mod information being returned from Frostline servers. + +For a period of two weeks, this remained unsolved and various attempts were made to understand the updated specification. + +### 2024-10-30: DZGUI hotfix adds support for Frostline servers +DZGUI was the first third-party tool to fix parsing for Frostline servers and restore functionality. +This was a small milestone, because it meant DZGUI was evolving from a "dumb" launcher to a featureful tool +touching upon and improving all aspects of the end-to-end DayZ experience. + +Particularly around this time, the sheer diversity of needs of modders, server operators, and players started to become clear. +Users had highly specific and diverse use cases for setting up their game environment, and requested a number of features to support that on +the DZGUI side. + +## 2025 +### 2025-01-17: DZGUI 6 API +With DZGUI 6, the difficult decision to enforce a Python 3.13 dependency was made. +While the previous dependency was Python 3.10+, it was deemed better to develop against and target identical versions. + +However, due to the vagaries of managing Python environments on end-user systems, this change added unnecessary complexity and frustration. +Users had to properly set up virtual environments, install the relevant dependencies, and create a wrapper script to run this version, whereas +previous versions had essentially been point and click. + +### 2025-10-23: DZGUI 7 API and the "Ship of Theseus" +It had become clear that the situation above was untenable and the legacy Bash script was unmaintainable given the project's enlarged scope. +The lack of a robust testing or typehinting framework made it difficult to diagnose rare bugs. + +Shipping a standalone, turnkey application with zero or minimal dependencies was the most urgent priority. +DZGUI was still carrying around small bits and pieces of the old Bash script logic, so the decision was made to +refactor the application into a pure Python + GTK tool. This would effectively be a total rewrite of all logic, +only preserving the most minimal skeleton of the original UI layout as a reference. + +Over the course of a year, a wholesale "Ship of Theseus" rewrite took place. +Nearly every component of DZGUI 6, both API and UI side, was taken apart and replaced. + +A soft alpha and beta phase were used to iterate on this concept further. DZGUI 7 was increasingly recommended to users +as the preferred choice. This allowed for keeping the established userbase on the (relatively) stable DZGUI 6 spec, while building up +the new spec and making it available to more users as features were elaborated. + +Through late 2025, several LLM-generated applications that recycled significant parts of DZGUI's code without the applicable GPL license were found. +The maintainers of those projects were advised to excise the offending segments or apply the necessary GPL licenses. + +## 2026 +### 2026-05-17: Switch to standalone releases +The DZGUI 7 beta starts shipping standalone glibc builds with an integrated setup wizard and installer. + +DZGUI 6 had relied on "over the air" updates delivered via either a "stable" or "testing" branch +(archived respectively under the "dzgui" and "testing" branches). Effectively, the script would bootstrap itself and +fetch raw files directly from the repository. This was prone to problems because updates were monolithic, not giving +an easy escape hatch if the user needed to rollback in the event of a regression. + +This was changed to atomic, tagged releases. In addition to updating directly through DZGUI, these prebuilt releases can be +downloaded manually as needed, and rollbacks are available for all prior versions under the DZGUI 7 specification. + +### 2026-10-27: 500 tickets closed +The milestone of 500 tickets/PRs being closed was reached. + +2026 was by far DZGUI's most intensive year by commits, perhaps suggesting the scope of the rewrite: + +- 2021: 16 +- 2022: 434 +- 2023: 322 +- 2024: 347 +- 2025: 426 +- 2026: 1300 + +DZGUI 7 is scheduled to release as a stable version within the year. diff --git a/README.md b/README.md index c3079a8..d062444 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ Users wishing to review the licenses to these components can inspect the `LICENS Steam and the Steam logo are trademarks and/or registered trademarks of Valve Corporation in the U.S. and/or other countries. +DayZ is a trademark and brand of Bohemia Interactive a.s. + ## Disclaimer DZGUI is beta-quality software and is provided as-is. diff --git a/dzgui/api/steam.py b/dzgui/api/steam.py index 4a7d1e8..23357f2 100644 --- a/dzgui/api/steam.py +++ b/dzgui/api/steam.py @@ -31,10 +31,10 @@ from dzgui.const.constants import ( VDF_PATH, ) from dzgui.const.endpoints import ( - APP_DETAILS, - SUB_ENDPOINT, + STEAM_APP_DETAILS, STEAM_PUBLISHED_FILES, - UNSUB_ENDPOINT, + STEAM_SUB, + STEAM_UNSUB, ) from dzgui.strings import wizard from dzgui.strings.errors import api_validation_error, workshop_endpoint_error @@ -310,11 +310,11 @@ def update_workshop(key: str, mod: int, endpoint: str) -> None: def subscribe(key: str, mod: int) -> None: - update_workshop(key, mod, SUB_ENDPOINT) + update_workshop(key, mod, STEAM_SUB) def unsubscribe(key: str, mod: int) -> None: - update_workshop(key, mod, UNSUB_ENDPOINT) + update_workshop(key, mod, STEAM_UNSUB) def validate_mod(client: str, mod: str) -> None: @@ -512,7 +512,7 @@ def get_app_path(folders_path: Path, appid: int) -> Path: def get_app_name(appid: int) -> str | None: payload = {"appids": [appid]} - res = requests.get(APP_DETAILS, params=payload) + res = requests.get(STEAM_APP_DETAILS, params=payload) if res.status_code != 200: return None try: diff --git a/dzgui/const/constants.py b/dzgui/const/constants.py index 65442be..daed17e 100644 --- a/dzgui/const/constants.py +++ b/dzgui/const/constants.py @@ -37,28 +37,6 @@ HEX_GREEN = "#32CD32" HEX_RED = "#FF0000" HEX_ORANGE = "#FFAC1C" -# TODO: normalize icon names -CALCULATOR = "accessories-calculator-symbolic" -CARET_DOWN = "go-down-symbolic" -CARET_UP = "go-up-symbolic" -CLIPBOARD = "edit-copy-symbolic" -CLOSE = "window-close-symbolic" -FOLDER = "folder-symbolic" -EDIT_DELETE = "edit-delete-symbolic" -ERROR = "dialog-error-symbolic" -HELP_BUBBLE = "help-about-symbolic" -INPUT_KEYBOARD = "input-keyboard-symbolic" -LIST_ADD = "list-add-symbolic" -REFRESH_ICON = "view-refresh-symbolic" -SEARCH_ICON = "system-search-symbolic" -STEAM_ICON = "steam-tray-symbolic" -VIEW_CONCEAL = "view-conceal-symbolic" -VIEW_REVEAL = "view-reveal-symbolic" -WARNING = "dialog-warning-symbolic" -WEB_BROWSER = "web-browser-symbolic" -DAY_ICON = "weather-clear-symbolic" -NIGHT_ICON = "weather-clear-night-symbolic" - SEPARATOR = "SEPARATOR" SCROLL_INCREMENT = 50 diff --git a/dzgui/const/endpoints.py b/dzgui/const/endpoints.py index 7616ff0..ed6239d 100644 --- a/dzgui/const/endpoints.py +++ b/dzgui/const/endpoints.py @@ -3,9 +3,9 @@ STEAM_PUBLISHED_FILES = ( "https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1" ) STEAM_SERVERS = "https://api.steampowered.com/IGameServersService/GetServerList/v1" -SUB_ENDPOINT = "https://api.steampowered.com/IPublishedFileService/Subscribe/v1" -UNSUB_ENDPOINT = "https://api.steampowered.com/IPublishedFileService/Unsubscribe/v1" -APP_DETAILS = "https://store.steampowered.com/api/appdetails?" +STEAM_SUB = "https://api.steampowered.com/IPublishedFileService/Subscribe/v1" +STEAM_UNSUB = "https://api.steampowered.com/IPublishedFileService/Unsubscribe/v1" +STEAM_APP_DETAILS = "https://store.steampowered.com/api/appdetails?" GITHUB = "https://github.com/aclist" GITHUB_RELEASES = "https://api.github.com/repos/aclist/dztui/releases/latest" diff --git a/dzgui/const/icons.py b/dzgui/const/icons.py new file mode 100644 index 0000000..176e73e --- /dev/null +++ b/dzgui/const/icons.py @@ -0,0 +1,20 @@ +ICON_CALCULATOR = "accessories-calculator-symbolic" +ICON_CARET_DOWN = "go-down-symbolic" +ICON_CARET_UP = "go-up-symbolic" +ICON_CLIPBOARD = "edit-copy-symbolic" +ICON_CLOSE = "window-close-symbolic" +ICON_DAY = "weather-clear-symbolic" +ICON_EDIT_DELETE = "edit-delete-symbolic" +ICON_ERROR = "dialog-error-symbolic" +ICON_FOLDER = "folder-symbolic" +ICON_HELP_BUBBLE = "help-about-symbolic" +ICON_INPUT_KEYBOARD = "input-keyboard-symbolic" +ICON_LIST_ADD = "list-add-symbolic" +ICON_NIGHT = "weather-clear-night-symbolic" +ICON_REFRESH = "view-refresh-symbolic" +ICON_SEARCH = "system-search-symbolic" +ICON_STEAM = "steam-tray-symbolic" +ICON_VIEW_CONCEAL = "view-conceal-symbolic" +ICON_VIEW_REVEAL = "view-reveal-symbolic" +ICON_WARNING = "dialog-warning-symbolic" +ICON_WEB_BROWSER = "web-browser-symbolic" diff --git a/dzgui/views/components/buttons.py b/dzgui/views/components/buttons.py index bb3d45d..2135892 100644 --- a/dzgui/views/components/buttons.py +++ b/dzgui/views/components/buttons.py @@ -7,15 +7,15 @@ from dzgui.util.strings import ( alert_button_tooltip, atomic_buttons, ) -from dzgui.const.constants import ( - CLIPBOARD, - CLOSE, - INPUT_KEYBOARD, - LIST_ADD, - REFRESH_ICON, - STEAM_ICON, - WARNING, - WEB_BROWSER, +from dzgui.const.icons import ( + ICON_CLIPBOARD, + ICON_CLOSE, + ICON_INPUT_KEYBOARD, + ICON_LIST_ADD, + ICON_REFRESH, + ICON_STEAM, + ICON_WARNING, + ICON_WEB_BROWSER, ) from dzgui.strings import connect_panel @@ -102,7 +102,7 @@ class LargeIconTextButton(IconButton): class ClipboardButton(IconTextButton): def __init__(self, controller: Union["Controller", None], func: Callable) -> None: - super().__init__(CLIPBOARD, atomic_buttons.copy) + super().__init__(ICON_CLIPBOARD, atomic_buttons.copy) self.controller = controller self.connect("clicked", self._on_button_clicked, func) @@ -131,7 +131,7 @@ class CopyIpButton(ClipboardButton): class CloseButton(IconTextButton): def __init__(self, label: str) -> None: - super().__init__(icon=CLOSE, label=label) + super().__init__(icon=ICON_CLOSE, label=label) # TODO: strings self.set_tooltip_text("Unset this server as favorite") @@ -139,14 +139,14 @@ class CloseButton(IconTextButton): class WebButton(IconTextButton): def __init__(self, label: str) -> None: - super().__init__(icon=WEB_BROWSER, label=label) + super().__init__(icon=ICON_WEB_BROWSER, label=label) pass class RefreshButton(IconTextButton): def __init__(self, controller: "Controller") -> None: super().__init__( - icon=REFRESH_ICON, + icon=ICON_REFRESH, label=atomic_buttons.refresh, ) @@ -211,7 +211,7 @@ class RefreshButton(IconTextButton): class KeysButton(IconTextButton): def __init__(self, controller: "Controller") -> None: - super().__init__(icon=INPUT_KEYBOARD, label=atomic_buttons.keys) + super().__init__(icon=ICON_INPUT_KEYBOARD, label=atomic_buttons.keys) self.controller = controller self.set_margin_top(10) @@ -228,7 +228,7 @@ class KeysButton(IconTextButton): class SteamConnectButton(LargeIconTextButton): def __init__(self) -> None: - super().__init__(icon=STEAM_ICON, label=connect_panel.connect_button) + super().__init__(icon=ICON_STEAM, label=connect_panel.connect_button) self.set_tooltip_text(connect_panel.connect_tooltip) @@ -248,7 +248,7 @@ class SteamWorkshopButton(SteamTextButton): class AddButton(IconTextButton): def __init__(self) -> None: - super().__init__(icon=LIST_ADD, label=connect_panel.add_button) + super().__init__(icon=ICON_LIST_ADD, label=connect_panel.add_button) self.set_tooltip_text(connect_panel.add_tooltip) @@ -267,7 +267,7 @@ class LoggerAlertsButton(IconTextButton): separator = "," concat = warnings_text + separator + errors_text super().__init__( - icon=WARNING, + icon=ICON_WARNING, label=f"Loaded with{concat}", ) self.set_halign(Gtk.Align.END) diff --git a/dzgui/views/components/connect_panel.py b/dzgui/views/components/connect_panel.py index 17d0a71..2884baa 100644 --- a/dzgui/views/components/connect_panel.py +++ b/dzgui/views/components/connect_panel.py @@ -1,6 +1,7 @@ from typing import Self, TYPE_CHECKING -from dzgui.const.constants import CARET_DOWN, CARET_UP, UDP_PORT +from dzgui.const.icons import ICON_CARET_DOWN, ICON_CARET_UP +from dzgui.const.constants import UDP_PORT from dzgui.model.servers import ServerModelManager from dzgui.strings import connect_panel from dzgui.util.keys import is_ctrl_mask @@ -30,7 +31,7 @@ ROWS = 1 class DockButton(IconButton): def __init__(self, child: Gtk.Widget) -> None: - super().__init__(CARET_DOWN) + super().__init__(ICON_CARET_DOWN) self.expanded = True self.child = child @@ -43,12 +44,12 @@ class DockButton(IconButton): self.set_tooltip_text(self.collapse_text) def collapse(self) -> None: - self.set_image(Icon(CARET_UP)) + self.set_image(Icon(ICON_CARET_UP)) self.child.hide() self.set_tooltip_text(self.expand_text) def expand(self) -> None: - self.set_image(Icon(CARET_DOWN)) + self.set_image(Icon(ICON_CARET_DOWN)) self.child.show() self.set_tooltip_text(self.collapse_text) diff --git a/dzgui/views/components/entry.py b/dzgui/views/components/entry.py index e1b3e7e..1f7fe0f 100644 --- a/dzgui/views/components/entry.py +++ b/dzgui/views/components/entry.py @@ -1,7 +1,7 @@ from typing import Any, Callable, TYPE_CHECKING from dzgui.api.servers import validate_ip -from dzgui.const.constants import VIEW_CONCEAL, VIEW_REVEAL +from dzgui.const.icons import ICON_VIEW_CONCEAL, ICON_VIEW_REVEAL from dzgui.util.css import add_class, remove_class from dzgui.strings import connect_panel from dzgui.strings.errors import api_popover @@ -147,7 +147,7 @@ class APIEntry(Gtk.Box): self.entry = Gtk.Entry( width_chars=60, hexpand=True, placeholder_text="Enter API key" ) - self.entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, VIEW_REVEAL) + self.entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, ICON_VIEW_REVEAL) self.entry.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, True) self.entry.set_visibility(False) self.entry.connect("changed", self._on_text_changed) @@ -206,9 +206,9 @@ class APIEntry(Gtk.Box): ) -> None: visible = widget.get_visibility() if visible: - icon, state = VIEW_REVEAL, False + icon, state = ICON_VIEW_REVEAL, False else: - icon, state = VIEW_CONCEAL, True + icon, state = ICON_VIEW_CONCEAL, True widget.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) widget.set_visibility(state) diff --git a/dzgui/views/components/eventbox.py b/dzgui/views/components/eventbox.py index 5e06127..5ef530d 100644 --- a/dzgui/views/components/eventbox.py +++ b/dzgui/views/components/eventbox.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING from dzgui.const.enum import NotebookPage -from dzgui.const.constants import HELP_BUBBLE +from dzgui.const.icons import ICON_HELP_BUBBLE import gi @@ -20,7 +20,7 @@ class InfoEventBox(Gtk.EventBox): self.text = text self.icon = Gtk.Image.new_from_icon_name( - HELP_BUBBLE, Gtk.IconSize.LARGE_TOOLBAR + ICON_HELP_BUBBLE, Gtk.IconSize.LARGE_TOOLBAR ) self.icon.set_opacity(0.8) box = Gtk.Box() diff --git a/dzgui/views/components/filter_panel.py b/dzgui/views/components/filter_panel.py index 5d80c9d..b2259f2 100644 --- a/dzgui/views/components/filter_panel.py +++ b/dzgui/views/components/filter_panel.py @@ -5,6 +5,7 @@ from dzgui.const.constants import ( APP_NAME, SEARCH_ICON, ) +from dzgui.const.icons import ICON_SEARCH from dzgui.util.format import format_exception from dzgui.util.strings import all_maps from dzgui.views.components.maps_combo import MapsCombo @@ -108,7 +109,7 @@ class KeywordEntry(Gtk.Entry): self.connect("key-press-event", self._on_keypress) self.connect("icon-release", lambda *args: self.activate()) - self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, SEARCH_ICON) + self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, ICON_SEARCH) self.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, True) self.emitter.connect("load_maps", self._on_maps_loaded) diff --git a/dzgui/views/components/misc.py b/dzgui/views/components/misc.py index cdc3f49..80e694e 100644 --- a/dzgui/views/components/misc.py +++ b/dzgui/views/components/misc.py @@ -1,7 +1,8 @@ from typing import Self from warnings import deprecated -from dzgui.const.constants import ERROR, FLATPAK_RUN_CMD, FLATPAK_SANDBOX, STEAM_CMD +from dzgui.const.constants import FLATPAK_RUN_CMD, FLATPAK_SANDBOX, STEAM_CMD +from dzgui.const.icons import ICON_ERROR from dzgui.init.prereqs import has_steam_client, has_flatpak_client from dzgui.strings import options from dzgui.views.components.box import HBox @@ -73,7 +74,7 @@ class ErrorPopover(Gtk.Popover): self.hbox = HBox() self.label = Gtk.Label(label="", margin_start=10, margin_end=10) - error_icon = Icon(ERROR, margin_start=10) + error_icon = Icon(ICON_ERROR, margin_start=10) self.hbox.extend([error_icon, self.label]) self.add(self.hbox) self.show_all() diff --git a/dzgui/views/dialogs/calc.py b/dzgui/views/dialogs/calc.py index 979cb0a..0b6501e 100644 --- a/dzgui/views/dialogs/calc.py +++ b/dzgui/views/dialogs/calc.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from datetime import datetime, time, timedelta from typing import Literal -from dzgui.const.constants import DAY_ICON, NIGHT_ICON +from dzgui.const.icons import ICON_DAY, ICON_NIGHT from dzgui.strings import calc from dzgui.views.components.icon import Icon from dzgui.views.components.labels import BoldLabel @@ -385,7 +385,7 @@ class TimePickerFrame(Gtk.Frame): h1 = BoldLabel(calc.heading_server_time) h2 = BoldLabel(calc.heading_target_time) - self.icon_indicator = Icon(DAY_ICON) + self.icon_indicator = Icon(ICON_DAY) self.update_server_icon() server_time_box = HBox(10) @@ -411,7 +411,7 @@ class TimePickerFrame(Gtk.Frame): self.add(flowbox) def update_server_icon(self) -> None: - icon = DAY_ICON if self.server_time.is_day() else NIGHT_ICON + icon = ICON_DAY if self.server_time.is_day() else ICON_NIGHT self.icon_indicator.set_icon_name(icon) def _on_server_clock_increment(self, emitter: Emitter, time_now: datetime) -> None: diff --git a/dzgui/views/dialogs/servers.py b/dzgui/views/dialogs/servers.py index 6166aee..b58d352 100644 --- a/dzgui/views/dialogs/servers.py +++ b/dzgui/views/dialogs/servers.py @@ -2,7 +2,7 @@ import gi import html from typing import Self, TYPE_CHECKING -from dzgui.const.constants import CALCULATOR +from dzgui.const.icons import ICON_CALCULATOR from dzgui.const.enum import ContextMenuGroup from dzgui.model.model_factory import ModelFactory from dzgui.util import css @@ -110,7 +110,7 @@ class ServerDetailsDialog(ServerDialog): text = format_hyperlinks(text) self.description.set_markup(html.escape(text)) - calc_button = IconTextButton(CALCULATOR, dialogs.toggle_calc) + calc_button = IconTextButton(ICON_CALCULATOR, dialogs.toggle_calc) calc_button.connect("clicked", self._on_calc_pressed) calc_button.set_halign(Gtk.Align.END) calc_button.set_valign(Gtk.Align.START) diff --git a/dzgui/views/pages/offline.py b/dzgui/views/pages/offline.py index b0200b0..ceb5686 100644 --- a/dzgui/views/pages/offline.py +++ b/dzgui/views/pages/offline.py @@ -3,14 +3,12 @@ from enum import Enum from typing import Self, TYPE_CHECKING, Union from dzgui.util import css +from dzgui.const.icons import ICON_EDIT_DELETE, ICON_FOLDER, ICON_WARNING from dzgui.const.constants import ( APPID_DAYZ, APPID_DAYZ_EXP, APPNAME_DAYZ, APPNAME_DAYZ_EXP_HUMAN, - EDIT_DELETE, - FOLDER, - WARNING, ) from dzgui.const.enum import ContextMenuGroup, NotebookPage from dzgui.managers.offline import OfflineManager @@ -87,7 +85,7 @@ class FolderHBox(HBox): self.emitter = controller.get_emitter() self.eb = InfoEventBox(eb_text, controller) - self.button = IconTextButton(FOLDER, btn_label, Gtk.PositionType.LEFT) + self.button = IconTextButton(ICON_FOLDER, btn_label, Gtk.PositionType.LEFT) self.button.set_halign(Gtk.Align.START) self.button.set_image_position(Gtk.PositionType.LEFT) @@ -99,7 +97,7 @@ class FolderHBox(HBox): self.label = Gtk.Label() self.scrolled_label.add(self.label) - self.unset_button = IconTextButton(EDIT_DELETE, offline.unset_button) + self.unset_button = IconTextButton(ICON_EDIT_DELETE, offline.unset_button) self.unset_button.connect("clicked", self._on_unset_clicked) self.spinner = Gtk.Spinner() @@ -195,7 +193,7 @@ class ModFrame(HeadingFrame): self.tree_vbox.extend([self.scrolled, self.status]) self.no_mods = HBox(spacing=10) - no_mods_icon = Icon(WARNING, margin_start=10) + no_mods_icon = Icon(ICON_WARNING, margin_start=10) no_mods_label = Gtk.Label(label=offline.no_local_mods, halign=Gtk.Align.START) self.no_mods.extend([no_mods_icon, no_mods_label]) diff --git a/dzgui/views/pages/options.py b/dzgui/views/pages/options.py index 2fbf5b9..e40a3d2 100644 --- a/dzgui/views/pages/options.py +++ b/dzgui/views/pages/options.py @@ -16,6 +16,7 @@ from dzgui.const.constants import ( VIEW_CONCEAL, VIEW_REVEAL, ) +from dzgui.const.icons import ICON_VIEW_CONCEAL, ICON_VIEW_REVEAL from dzgui.const.endpoints import STEAM_API_SETUP from dzgui.const.enum import Preferences, ServerTab from dzgui.strings import options @@ -64,7 +65,7 @@ class SubmitField(Gtk.Box): # TODO: audit for all applicable callbacks if private: self.entry.set_icon_from_icon_name( - Gtk.EntryIconPosition.SECONDARY, VIEW_REVEAL + Gtk.EntryIconPosition.SECONDARY, ICON_VIEW_REVEAL ) self.entry.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, True) self.entry.connect("icon-release", self._on_icon_release) @@ -128,9 +129,9 @@ class SubmitField(Gtk.Box): ) -> None: visible = widget.get_visibility() if visible: - icon, state = VIEW_REVEAL, False + icon, state = ICON_VIEW_REVEAL, False else: - icon, state = VIEW_CONCEAL, True + icon, state = ICON_VIEW_CONCEAL, True widget.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) widget.set_visibility(state) diff --git a/dzgui/views/pages/preconnect.py b/dzgui/views/pages/preconnect.py index b737ff0..a094fc7 100644 --- a/dzgui/views/pages/preconnect.py +++ b/dzgui/views/pages/preconnect.py @@ -1,9 +1,9 @@ from dataclasses import dataclass from typing import Self, Sequence, TYPE_CHECKING -from dzgui.const.constants import ( - ERROR, - WARNING, +from dzgui.const.icons import ( + ICON_ERROR, + ICON_WARNING, ) from dzgui.const.enum import NotebookPage from dzgui.util.css import add_class @@ -166,7 +166,7 @@ class PreConnectionAssistant(Gtk.Box): # TODO: abstract into components self.warning_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - self.warning_tree = MaskedTree(WARNING) + self.warning_tree = MaskedTree(ICON_WARNING) self.warning_placeholder = Placeholder(preconnect.placeholder_no_warnings) self.warning_box.add(self.warning_tree) self.warning_box.add(self.warning_placeholder) @@ -175,7 +175,7 @@ class PreConnectionAssistant(Gtk.Box): ) self.error_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - self.error_tree = MaskedTree(ERROR) + self.error_tree = MaskedTree(ICON_ERROR) self.error_placeholder = Placeholder(preconnect.no_errors) self.error_box.add(self.error_tree) self.error_box.add(self.error_placeholder) diff --git a/scripts/concat_licenses.py b/scripts/concat_licenses.py index 56f80a6..c1f149c 100644 --- a/scripts/concat_licenses.py +++ b/scripts/concat_licenses.py @@ -1,4 +1,4 @@ -from pathlib import Path +from shared import get_git_root BOILERPLATE = """ This file includes the combined licenses to the following components: @@ -40,7 +40,7 @@ class CombinedLicense: def concat_license() -> str: combined = CombinedLicense() - root = Path(__file__).resolve().parents[1] + root = get_git_root() licenses = root.joinpath("docs/licenses") main_license = root.joinpath(LICENSE_FILE) diff --git a/scripts/copy_changelog.py b/scripts/copy_changelog.py index c32a4cb..e900469 100644 --- a/scripts/copy_changelog.py +++ b/scripts/copy_changelog.py @@ -1,10 +1,11 @@ import shutil import subprocess -from pathlib import Path -par = Path(__file__).parent.parent -clog = par / "CHANGELOG.md" -target = par / "dzgui/data/CHANGELOG.md" +from shared import get_git_root + +root = get_git_root() +clog = root.joinpath("CHANGELOG.md") +target = root.joinpath("dzgui/data/CHANGELOG.md") shutil.copy(clog, target) diff --git a/scripts/parse_toml.py b/scripts/parse_toml.py index dc0afe0..e4b5210 100644 --- a/scripts/parse_toml.py +++ b/scripts/parse_toml.py @@ -1,10 +1,9 @@ import tomllib -from pathlib import Path - -root = Path(__file__).resolve().parents[1] +from shared import get_git_root def get_entrypoint() -> None: + root = get_git_root() p = root.joinpath("pyproject.toml") with open(p, "rb") as f: t = tomllib.load(f) diff --git a/scripts/prebuild.py b/scripts/prebuild.py index 8700b62..143bc56 100644 --- a/scripts/prebuild.py +++ b/scripts/prebuild.py @@ -3,14 +3,15 @@ import requests import subprocess import tarfile -from pathlib import Path from urllib.parse import unquote +from shared import get_git_root + BUILD = "3.13.9" DATE = "20251014" CPYTHON_BUILD = f"cpython-{BUILD}" -root = Path(__file__).resolve().parents[1] +root = get_git_root() build_dir = root.joinpath("build") cpython_dir = build_dir.joinpath(CPYTHON_BUILD) diff --git a/scripts/release.py b/scripts/release.py index 2eb8f02..2afaf47 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -9,6 +9,7 @@ from pathlib import Path from concat_licenses import concat_license from parse_toml import get_entrypoint from prebuild import get_pyapp, rebuild_cpython +from shared import get_git_root def update_uname(info: tarfile.TarInfo) -> tarfile.TarInfo: @@ -17,7 +18,7 @@ def update_uname(info: tarfile.TarInfo) -> tarfile.TarInfo: return info -root = Path(__file__).resolve().parents[1] +root = get_git_root() dist_dir = root.joinpath("dist") build_dir = root.joinpath("build") diff --git a/scripts/scrot.py b/scripts/scrot.py index 9dbf04e..3800197 100644 --- a/scripts/scrot.py +++ b/scripts/scrot.py @@ -1,16 +1,7 @@ -import subprocess from pathlib import Path from dzgui.app_init import build_gui - - -def get_git_root() -> Path: - proc = subprocess.run( - ["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True - ) - if proc.returncode != 0: - raise OSError("Failed to parse git root") - return Path(proc.stdout.rstrip()) +from shared import get_git_root def get_image_path() -> Path: diff --git a/scripts/shared/__init__.py b/scripts/shared/__init__.py new file mode 100644 index 0000000..f638b7b --- /dev/null +++ b/scripts/shared/__init__.py @@ -0,0 +1,11 @@ +import subprocess +from pathlib import Path + + +def get_git_root() -> Path: + proc = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True + ) + if proc.returncode != 0: + raise OSError("Failed to parse git root") + return Path(proc.stdout.rstrip())