From 9644de3ff58284d52922899c255d3194a3af96b3 Mon Sep 17 00:00:00 2001 From: m4ko Date: Thu, 10 Sep 2026 00:13:05 +0200 Subject: [PATCH] Fixed some UI issues with updater. --- RELEASE_README.md | 12 +- ptlinux/__init__.py | 2 +- ptlinux/ui/components/update_dialog.py | 231 ++++++++++++++++++++----- pyproject.toml | 2 +- updates/stable.json | 40 ++--- updates/stable.json.sig | Bin 384 -> 384 bytes 6 files changed, 214 insertions(+), 73 deletions(-) diff --git a/RELEASE_README.md b/RELEASE_README.md index fb4238c..63026b4 100644 --- a/RELEASE_README.md +++ b/RELEASE_README.md @@ -1,6 +1,6 @@ # PristonTale EU for Linux -Version 0.9.8 installs, updates, repairs, and launches PristonTale EU through a +Version 0.9.11 installs, updates, repairs, and launches PristonTale EU through a native Linux dashboard. Game files come directly from the publisher and are not included in this archive. @@ -15,11 +15,11 @@ Then run: ```text openssl pkey -pubin -in launcher-update-public.pem -outform DER | sha256sum openssl dgst -sha256 -verify launcher-update-public.pem \ - -signature pristontale-eu-linux-installer-v0-9-8.tar.gz.sig \ - pristontale-eu-linux-installer-v0-9-8.tar.gz -sha256sum -c pristontale-eu-linux-installer-v0-9-8.tar.gz.sha256 -tar -xzf pristontale-eu-linux-installer-v0-9-8.tar.gz -cd pristontale-eu-linux-installer-v0-9-8 + -signature pristontale-eu-linux-installer-v0-9-11.tar.gz.sig \ + pristontale-eu-linux-installer-v0-9-11.tar.gz +sha256sum -c pristontale-eu-linux-installer-v0-9-11.tar.gz.sha256 +tar -xzf pristontale-eu-linux-installer-v0-9-11.tar.gz +cd pristontale-eu-linux-installer-v0-9-11 ./install.sh ``` diff --git a/ptlinux/__init__.py b/ptlinux/__init__.py index 0e06255..0768ee6 100644 --- a/ptlinux/__init__.py +++ b/ptlinux/__init__.py @@ -4,4 +4,4 @@ from .paths import AppPaths __all__ = ["AppPaths", "__version__"] -__version__ = "0.9.8" +__version__ = "0.9.11" diff --git a/ptlinux/ui/components/update_dialog.py b/ptlinux/ui/components/update_dialog.py index 804b862..c63e6a7 100644 --- a/ptlinux/ui/components/update_dialog.py +++ b/ptlinux/ui/components/update_dialog.py @@ -7,7 +7,6 @@ from typing import Any, Callable from ...paths import AppPaths from ...self_update import ( LauncherRemoteHistory, - LauncherRemoteRelease, LauncherUpdateCheck, LauncherUpdateHistory, inspect_launcher_update_history, @@ -52,6 +51,46 @@ def release_notes_preview(markdown: str | None) -> str: return preview +def update_version_heading( + running_version: str, + latest_version: str, + channel: str, + *, + update_available: bool, +) -> str: + """Describe update versions without confusing an older channel head as current.""" + + if update_available: + return f"Update: {running_version} → {latest_version}" + return ( + f"Current version: {running_version}\n" + f"Latest {channel.title()} release: {latest_version}" + ) + + +def release_history_state( + release_status: str, + *, + is_running: bool, + local_active: bool, + local_retained: bool, + is_older: bool, +) -> str: + """Label running and installed releases without treating them as synonymous.""" + + if is_running: + return "Current" + if local_active: + return "Installed" + if release_status != "active": + return release_status.title() + if local_retained: + return "Retained locally" + if is_older: + return "Available rollback" + return "Published" + + class LauncherUpdateDialog: """Own the update window and expose small refresh/error entry points.""" @@ -97,6 +136,8 @@ class LauncherUpdateDialog: self._history: Any = None self._history_filter_value: Any = None self._history_items: dict[str, tuple[str, str, str | None, bool]] = {} + self._release_notes_frame: Any = None + self._release_notes_text: Any = None self._release_notes_button: Any = None self._check_button: Any = None self._rollback_button: Any = None @@ -120,7 +161,7 @@ class LauncherUpdateDialog: dialog.withdraw() self._window = dialog dialog.title("PristonTale EU — Launcher Updates") - dialog.resizable(False, False) + dialog.resizable(False, True) dialog.configure(background="#0c0908") dialog.transient(self._root) self._apply_window_icon(dialog) @@ -201,6 +242,53 @@ class LauncherUpdateDialog: sticky="e", padx=(12, 0), ) + self._release_notes_frame = ttk.Frame( + overview, + style="SettingsCard.TFrame", + ) + self._release_notes_frame.grid( + row=2, + column=0, + columnspan=2, + sticky="ew", + pady=(10, 0), + ) + self._release_notes_frame.columnconfigure(0, weight=1) + ttk.Label( + self._release_notes_frame, + text="WHAT'S NEW", + style="SettingsText.TLabel", + ).grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 4)) + self._release_notes_text = tk.Text( + self._release_notes_frame, + name="launcher_update_release_notes", + width=1, + height=3, + wrap="word", + background="#100d0c", + foreground="#c8b8a8", + selectbackground="#6a482d", + selectforeground="#ffffff", + font=("Sans", 8), + borderwidth=0, + highlightthickness=0, + padx=8, + pady=6, + state="disabled", + takefocus=False, + ) + release_notes_scrollbar = ttk.Scrollbar( + self._release_notes_frame, + orient="vertical", + style="News.Vertical.TScrollbar", + command=self._release_notes_text.yview, + ) + self._release_notes_text.configure( + yscrollcommand=release_notes_scrollbar.set, + ) + self._release_notes_text.grid(row=1, column=0, sticky="ew") + release_notes_scrollbar.grid(row=1, column=1, sticky="ns", padx=(6, 0)) + self._release_notes_frame.grid_remove() history_card = ttk.LabelFrame( content, @@ -244,7 +332,7 @@ class LauncherUpdateDialog: history_card, columns=("version", "channel", "state", "installed"), show="headings", - height=6, + height=4, style="Update.Treeview", selectmode="browse", ) @@ -333,8 +421,8 @@ class LauncherUpdateDialog: self._check_again() dialog.update_idletasks() updates_width, updates_height = fitted_window_size( - 760, - 620, + 820, + 680, scale=self._ui_scale, screen_width=dialog.winfo_screenwidth(), screen_height=dialog.winfo_screenheight(), @@ -365,6 +453,8 @@ class LauncherUpdateDialog: assert self._status_value is not None assert self._details_value is not None assert self._history is not None + assert self._release_notes_frame is not None + assert self._release_notes_text is not None assert self._release_notes_button is not None assert self._check_button is not None assert self._rollback_button is not None @@ -381,10 +471,11 @@ class LauncherUpdateDialog: if check is None: self._status_value.set("Checking the signed update channel…") self._details_value.set( - "Installed launcher " + "Running launcher " + self._launcher_version + " · release information pending" ) + self._set_release_notes_preview("") self._install_button.configure(state="disabled") self._refresh_history_actions(busy=busy) return @@ -400,13 +491,14 @@ class LauncherUpdateDialog: for release in ( remote_history.releases if remote_history is not None else () ) - if release.version == check.current_version and release.status != "active" + if release.version == self._launcher_version + and release.status != "active" ), None, ) if installed_status is not None: status_prefix += ( - f"Warning: installed launcher {check.current_version} is " + f"Warning: running launcher {self._launcher_version} is " f"{installed_status}; install the next active eligible release.\n" ) self._status_value.set(status_prefix + check.reason) @@ -418,18 +510,24 @@ class LauncherUpdateDialog: if check.manifest.channel == "prerelease" else "" ) + summary_line = f"{release_summary}\n" if not notes_preview else "" + version_line = update_version_heading( + self._launcher_version, + check.latest_version, + check.manifest.channel, + update_available=check.available, + ) details = ( - f"{check.current_version} → {check.latest_version}\n" + f"{version_line}\n" f"{release_title}{prerelease_label}\n" - f"{release_summary}\n" + f"{summary_line}" f"Channel: {check.manifest.channel.title()} · " f"{format_bytes(check.manifest.archive_size)} · " f"published {check.manifest.published_at} · " f"checked {check.checked_at or 'time unavailable'}" ) - if notes_preview: - details += f"\n\nWhat's new:\n{notes_preview}" self._details_value.set(details) + self._set_release_notes_preview(notes_preview) self._install_button.configure( state=( "normal" @@ -442,6 +540,20 @@ class LauncherUpdateDialog: ) self._refresh_history_actions(busy=busy) + def _set_release_notes_preview(self, preview: str) -> None: + assert self._release_notes_frame is not None + assert self._release_notes_text is not None + self._release_notes_text.configure(state="normal") + self._release_notes_text.delete("1.0", "end") + if preview: + self._release_notes_text.insert("1.0", preview) + self._release_notes_text.configure(state="disabled") + self._release_notes_text.yview_moveto(0.0) + if preview: + self._release_notes_frame.grid() + else: + self._release_notes_frame.grid_remove() + def _populate_history(self, *, busy: bool) -> None: assert self._history is not None assert self._history_filter_value is not None @@ -461,16 +573,22 @@ class LauncherUpdateDialog: else () ) } - current_version = ( + installed_current_version = ( self._installed_history.current_version if self._installed_history is not None and self._installed_history.current_version is not None else self._launcher_version ) try: - current_semantic = SemanticVersion.parse(current_version) + installed_current_semantic = SemanticVersion.parse( + installed_current_version + ) except ReleaseModelError: - current_semantic = None + installed_current_semantic = None + try: + running_channel = SemanticVersion.parse(self._launcher_version).channel + except ReleaseModelError: + running_channel = "unknown" filter_name = str(self._history_filter_value.get()).casefold() def channel_visible(channel: str) -> bool: @@ -478,20 +596,27 @@ class LauncherUpdateDialog: remote_history = self._get_remote_history() remote_versions: set[str] = set() + displayed_versions: set[str] = set() if remote_history is not None: for index, release in enumerate(remote_history.releases): if not channel_visible(release.channel): continue remote_versions.add(release.version) + displayed_versions.add(release.version) local = installed.get(release.version) is_older = ( - current_semantic is not None - and SemanticVersion.parse(release.version) < current_semantic + installed_current_semantic is not None + and SemanticVersion.parse(release.version) + < installed_current_semantic ) - can_rollback = release.status == "active" and is_older - state = self._remote_release_state( - release, - local_active=bool(local is not None and local.active), + local_active = bool(local is not None and local.active) + can_rollback = ( + release.status == "active" and is_older and not local_active + ) + state = release_history_state( + release.status, + is_running=release.version == self._launcher_version, + local_active=local_active, local_retained=local is not None, is_older=is_older, ) @@ -517,14 +642,23 @@ class LauncherUpdateDialog: for index, release in enumerate(self._installed_history.releases): if release.version in remote_versions or not channel_visible(release.channel): continue + displayed_versions.add(release.version) try: is_older = ( - current_semantic is not None - and SemanticVersion.parse(release.version) < current_semantic + installed_current_semantic is not None + and SemanticVersion.parse(release.version) + < installed_current_semantic ) except ReleaseModelError: is_older = False - state = "Current" if release.active else "Retained locally" + state = release_history_state( + "active", + is_running=release.version == self._launcher_version, + local_active=release.active, + local_retained=True, + is_older=is_older, + ) + can_rollback = is_older and not release.active iid = f"local:{index}:{release.version}" self._history.insert( "", @@ -541,8 +675,30 @@ class LauncherUpdateDialog: release.version, release.channel, None, - is_older, + can_rollback, ) + if ( + self._launcher_version not in displayed_versions + and channel_visible(running_channel) + ): + iid = f"running:{self._launcher_version}" + self._history.insert( + "", + 0, + iid=iid, + values=( + self._launcher_version, + running_channel.title(), + "Current", + "Running now", + ), + ) + self._history_items[iid] = ( + self._launcher_version, + running_channel, + None, + False, + ) if not self._history.get_children(): iid = "placeholder" self._history.insert( @@ -567,24 +723,6 @@ class LauncherUpdateDialog: self._history.focus(selected_iid) self._refresh_history_actions(busy=busy) - @staticmethod - def _remote_release_state( - release: LauncherRemoteRelease, - *, - local_active: bool, - local_retained: bool, - is_older: bool, - ) -> str: - if local_active: - return "Current" - if release.status != "active": - return release.status.title() - if local_retained: - return "Retained locally" - if is_older: - return "Available rollback" - return "Published" - def _history_selected(self, _event: object | None = None) -> None: self._refresh_history_actions(busy=self._is_busy()) @@ -621,6 +759,7 @@ class LauncherUpdateDialog: assert self._check_button is not None self._status_value.set("The signed update channel could not be checked.") self._details_value.set(message) + self._set_release_notes_preview("") self._check_button.configure(state="normal") def _check_again(self) -> None: @@ -630,6 +769,7 @@ class LauncherUpdateDialog: assert self._install_button is not None self._status_value.set("Checking the signed update channel…") self._details_value.set("Authenticating the selected channel manifests and signatures.") + self._set_release_notes_preview("") self._check_button.configure(state="disabled") self._install_button.configure(state="disabled") self._request_check() @@ -637,8 +777,9 @@ class LauncherUpdateDialog: def _view_release_notes(self) -> None: selected = self._selected_history_item() if selected is not None: - if selected[2] is not None: - self._open_article(selected[2]) + notes_url = selected[2] + if notes_url is not None: + self._open_article(notes_url) return check = self._get_check() if check is not None and check.manifest.notes_url is not None: diff --git a/pyproject.toml b/pyproject.toml index e8ebb61..35984d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "PTEU-Linux-Launcher" -version = "0.9.8" +version = "0.9.11" description = "PristonTale EU Linux installer, clean launcher, and diagnostic harness" requires-python = ">=3.11" dependencies = [] diff --git a/updates/stable.json b/updates/stable.json index 563d650..a2ec57e 100644 --- a/updates/stable.json +++ b/updates/stable.json @@ -2,16 +2,16 @@ "application": "PristonTale-EU", "archive": { "layout": "wrapped", - "name": "PristonTale-EU-0.9.10.tar.gz", - "sha256": "76df8942d2f0b3b533eb967f8a2379b2a7bdd44fd58305d0f8bf061778fbd9ea", - "size": 1473687, - "url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v0.9.10/PristonTale-EU-0.9.10.tar.gz" + "name": "PristonTale-EU-0.9.11.tar.gz", + "sha256": "60b13d6fbc8fe27714c7fe1a65fa73785a13962d96d03decfac1aa62c4e3f6db", + "size": 1474010, + "url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v0.9.11/PristonTale-EU-0.9.11.tar.gz" }, - "archive_sha256": "76df8942d2f0b3b533eb967f8a2379b2a7bdd44fd58305d0f8bf061778fbd9ea", - "archive_size": 1473687, - "archive_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v0.9.10/PristonTale-EU-0.9.10.tar.gz", + "archive_sha256": "60b13d6fbc8fe27714c7fe1a65fa73785a13962d96d03decfac1aa62c4e3f6db", + "archive_size": 1474010, + "archive_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v0.9.11/PristonTale-EU-0.9.11.tar.gz", "channel": "stable", - "commit": "27ed039f327d4d14d1d04717e0fd4f20ad7b7dc9", + "commit": "a362b0bb8e2bf74db9075c5f58db5236b1dc98f6", "compatibility": { "config_schema": 1, "manifest_schema": 1, @@ -36,7 +36,7 @@ }, { "path": "ptlinux/__init__.py", - "sha256": "a80db24319120aa083e8abf0e099bacc8958bc04e59a2ac17ee270ae0f7094a3", + "sha256": "b4eaa89165a506ff593083dd32ad30fefe1d8ec7d09af67b45b29a3d13599df0", "size": 152 }, { @@ -306,8 +306,8 @@ }, { "path": "ptlinux/ui/components/update_dialog.py", - "sha256": "af5775b07b6872b5e5f465deb8d9f583ddfeeae1df79a126fb2d2fe967bb3d06", - "size": 22005 + "sha256": "1defc85b4cf30f15fa1c30bcfd0cbf961072ba2d2ab8a681ef981e34ef3598d4", + "size": 24138 }, { "path": "ptlinux/ui/dashboard.py", @@ -371,21 +371,21 @@ }, { "path": "release.json", - "sha256": "e1c3a4501457050f00ed98d254b83521f173bccb91cb6176bff2ca89aab99adc", + "sha256": "afc50744e1d2cd13c3062b6160cd21a0b25870da81270b75b0a35940ca33b23b", "size": 223 } ], "minimum_updater_version": "0.5.0", - "notes_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/tag/v0.9.10", - "published_at": "2026-09-09T21:01:49Z", + "notes_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/tag/v0.9.11", + "published_at": "2026-09-09T21:12:47Z", "release": { - "notes_markdown": "# PristonTale EU Launcher 0.9.10\n\nRestores automatic updates for existing launchers and completes the transition to flat release archives.\n\n## Changes\n\n- Updated to new release v0.9.9 (`27ed039`)\n- Added better support for kubuntu and fixed some issues with telemetry. (`290d1a5`)\n- Updated diagnostic message. (`6e62191`)\n- Updated the update system to support multi channels (stable / pre-release). (`5770969`)\n- Released a new version, 0.9.8. (`2a53a59`)\n- Fixed more issues with Cinnamon window resizing and popups. (`76bef94`)\n- Fixed issues with telemetry. (`1a120bb`)\n- Released new stable version 0.9.7. (`108ea94`)\n- Added a fix for X11 / Cinnamon window popups and maximize button not visible. (`1cfe862`)\n- Added telemetry system to get information about issues. (`f06fc2e`)\n- Upload files to \"updates\" (`a1ff479`)\n- Delete updates/stable.json.sig (`6eea3b2`)\n- Update updates/stable.json (`ee9bb06`)\n- Fixed issues with gamescope not properly launching. (`bea9d6f`)\n\n---\n\nVersion: `0.9.10` \nChannel: `stable` \nTag: `v0.9.10` \nSource commit: `27ed039f327d4d14d1d04717e0fd4f20ad7b7dc9`\n", - "release_notes_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/tag/v0.9.10", - "summary": "Restores automatic updates for existing launchers and completes the transition to flat release archives.", - "title": "PristonTale EU Launcher 0.9.10" + "notes_markdown": "# PristonTale EU Launcher 0.9.11\n\nFixes the update window layout so release notes no longer cover version history or controls.\n\n---\n\nVersion: `0.9.11` \nChannel: `stable` \nTag: `v0.9.11` \nSource commit: `a362b0bb8e2bf74db9075c5f58db5236b1dc98f6`\n", + "release_notes_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/tag/v0.9.11", + "summary": "Fixes the update window layout so release notes no longer cover version history or controls.", + "title": "PristonTale EU Launcher 0.9.11" }, "schema": 1, "status": "active", - "tag": "v0.9.10", - "version": "0.9.10" + "tag": "v0.9.11", + "version": "0.9.11" } diff --git a/updates/stable.json.sig b/updates/stable.json.sig index 21533fb0ccc80bcfb17d9de0a3920fd5b1dc16f7..8b075059ec3ef31aa80dee18796a1b0e30904304 100644 GIT binary patch literal 384 zcmV-`0e}8AsR#_QBi`cHHxPC5W30mhuXi_N+axVH#_--|whw3rMBy}?oG6zo)WZPv z{0$XCJpHcI>B;KQexTd)tVXOzeDJVlGO|k_Q^jj4%3Oo`4Zx#F{JE2syC5UO&kl=6 z7E0s0Dpj7^wWWQxDN&GOXx2ClQlT(*xNl|0hy?FqRn-6*X-*~lpMJ3lV~JY570A`| zlwv_l?6!Fyt+L){zC^l}j{+f2)x)9N+pU}95fH8)vH3mPjVvYzQ&3 z>LH0L*1XT2Odd+fIDHi`o-2U*kH@m2?cCT}L}9WVfNBe2sD_pO2L4oMiTBZWx0Lk# z{z`|P0s{9%eF8e&Z%}@5cQbG@_{aCzd=T4xg}9F#i|{rwL2}tNwXOt3#h}Pl43A-~ zg)7fQMkO2=TlGl@`>ul#5!nBRcS+=7t6Y13;)*|l@tz8NhR72Pkqq8$g&ssqXc=Z< e5)71Veg{GrFpOyWutqc2Cq_5~AZl0E35VlVFuGO% literal 384 zcmV-`0e}8zG(5wNhFCXN(Gksb072*>35^9R7v-LaWwyZSPliGMm_Q2g=h!@eIBtP0 zMC*O5(#+5xBC?UQ7_JoZO8K1M(4w@`OOQaq7t`LYNc zS22>0P%J8lJj#ETDU@JhtdaHF3V`oIgG`$_x?yTQ5nc$v8RA`QV5VWd1ekV%dh3PT zFiy71sFA)~nGjmpc6Sn<{|>AQ%{54lY-n*3cJPhoQ%BtO2x(GE(Qdf^MoxlU^-v1Y zn@)Av%oZonHOp=#Nnzm?uvubPbEq+R=AldvFyxT8*XWRPJn)Y<$+7yZfmk!RG!+Lr zW%uBqWw*N@V}sI)dkmYu$}aJ(<;yle|Bsp`$KbTka?5ZlH%@nPm=72&>(*Q>*_;|q zUS;ey8rk(isNsKx6!4;0pJB-&FG2H9@IA6zi%r+Ylf`k9cKB~emVz}wQq2=_WOE#k egW{fX1=pw$;IiqVGt*A8<&V?}K?53C{FK%N|Gmlp