1 Commits

Author SHA1 Message Date
9644de3ff5 Fixed some UI issues with updater. 2026-09-10 00:13:05 +02:00
6 changed files with 214 additions and 73 deletions

View File

@@ -1,6 +1,6 @@
# PristonTale EU for Linux # 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 native Linux dashboard. Game files come directly from the publisher and are not
included in this archive. included in this archive.
@@ -15,11 +15,11 @@ Then run:
```text ```text
openssl pkey -pubin -in launcher-update-public.pem -outform DER | sha256sum openssl pkey -pubin -in launcher-update-public.pem -outform DER | sha256sum
openssl dgst -sha256 -verify launcher-update-public.pem \ openssl dgst -sha256 -verify launcher-update-public.pem \
-signature pristontale-eu-linux-installer-v0-9-8.tar.gz.sig \ -signature pristontale-eu-linux-installer-v0-9-11.tar.gz.sig \
pristontale-eu-linux-installer-v0-9-8.tar.gz pristontale-eu-linux-installer-v0-9-11.tar.gz
sha256sum -c pristontale-eu-linux-installer-v0-9-8.tar.gz.sha256 sha256sum -c pristontale-eu-linux-installer-v0-9-11.tar.gz.sha256
tar -xzf pristontale-eu-linux-installer-v0-9-8.tar.gz tar -xzf pristontale-eu-linux-installer-v0-9-11.tar.gz
cd pristontale-eu-linux-installer-v0-9-8 cd pristontale-eu-linux-installer-v0-9-11
./install.sh ./install.sh
``` ```

View File

@@ -4,4 +4,4 @@ from .paths import AppPaths
__all__ = ["AppPaths", "__version__"] __all__ = ["AppPaths", "__version__"]
__version__ = "0.9.8" __version__ = "0.9.11"

View File

@@ -7,7 +7,6 @@ from typing import Any, Callable
from ...paths import AppPaths from ...paths import AppPaths
from ...self_update import ( from ...self_update import (
LauncherRemoteHistory, LauncherRemoteHistory,
LauncherRemoteRelease,
LauncherUpdateCheck, LauncherUpdateCheck,
LauncherUpdateHistory, LauncherUpdateHistory,
inspect_launcher_update_history, inspect_launcher_update_history,
@@ -52,6 +51,46 @@ def release_notes_preview(markdown: str | None) -> str:
return preview 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: class LauncherUpdateDialog:
"""Own the update window and expose small refresh/error entry points.""" """Own the update window and expose small refresh/error entry points."""
@@ -97,6 +136,8 @@ class LauncherUpdateDialog:
self._history: Any = None self._history: Any = None
self._history_filter_value: Any = None self._history_filter_value: Any = None
self._history_items: dict[str, tuple[str, str, str | None, bool]] = {} 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._release_notes_button: Any = None
self._check_button: Any = None self._check_button: Any = None
self._rollback_button: Any = None self._rollback_button: Any = None
@@ -120,7 +161,7 @@ class LauncherUpdateDialog:
dialog.withdraw() dialog.withdraw()
self._window = dialog self._window = dialog
dialog.title("PristonTale EU — Launcher Updates") dialog.title("PristonTale EU — Launcher Updates")
dialog.resizable(False, False) dialog.resizable(False, True)
dialog.configure(background="#0c0908") dialog.configure(background="#0c0908")
dialog.transient(self._root) dialog.transient(self._root)
self._apply_window_icon(dialog) self._apply_window_icon(dialog)
@@ -201,6 +242,53 @@ class LauncherUpdateDialog:
sticky="e", sticky="e",
padx=(12, 0), 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( history_card = ttk.LabelFrame(
content, content,
@@ -244,7 +332,7 @@ class LauncherUpdateDialog:
history_card, history_card,
columns=("version", "channel", "state", "installed"), columns=("version", "channel", "state", "installed"),
show="headings", show="headings",
height=6, height=4,
style="Update.Treeview", style="Update.Treeview",
selectmode="browse", selectmode="browse",
) )
@@ -333,8 +421,8 @@ class LauncherUpdateDialog:
self._check_again() self._check_again()
dialog.update_idletasks() dialog.update_idletasks()
updates_width, updates_height = fitted_window_size( updates_width, updates_height = fitted_window_size(
760, 820,
620, 680,
scale=self._ui_scale, scale=self._ui_scale,
screen_width=dialog.winfo_screenwidth(), screen_width=dialog.winfo_screenwidth(),
screen_height=dialog.winfo_screenheight(), screen_height=dialog.winfo_screenheight(),
@@ -365,6 +453,8 @@ class LauncherUpdateDialog:
assert self._status_value is not None assert self._status_value is not None
assert self._details_value is not None assert self._details_value is not None
assert self._history 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._release_notes_button is not None
assert self._check_button is not None assert self._check_button is not None
assert self._rollback_button is not None assert self._rollback_button is not None
@@ -381,10 +471,11 @@ class LauncherUpdateDialog:
if check is None: if check is None:
self._status_value.set("Checking the signed update channel…") self._status_value.set("Checking the signed update channel…")
self._details_value.set( self._details_value.set(
"Installed launcher " "Running launcher "
+ self._launcher_version + self._launcher_version
+ " · release information pending" + " · release information pending"
) )
self._set_release_notes_preview("")
self._install_button.configure(state="disabled") self._install_button.configure(state="disabled")
self._refresh_history_actions(busy=busy) self._refresh_history_actions(busy=busy)
return return
@@ -400,13 +491,14 @@ class LauncherUpdateDialog:
for release in ( for release in (
remote_history.releases if remote_history is not None else () 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, None,
) )
if installed_status is not None: if installed_status is not None:
status_prefix += ( 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" f"{installed_status}; install the next active eligible release.\n"
) )
self._status_value.set(status_prefix + check.reason) self._status_value.set(status_prefix + check.reason)
@@ -418,18 +510,24 @@ class LauncherUpdateDialog:
if check.manifest.channel == "prerelease" if check.manifest.channel == "prerelease"
else "" 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 = ( details = (
f"{check.current_version} → {check.latest_version}\n" f"{version_line}\n"
f"{release_title}{prerelease_label}\n" f"{release_title}{prerelease_label}\n"
f"{release_summary}\n" f"{summary_line}"
f"Channel: {check.manifest.channel.title()} · " f"Channel: {check.manifest.channel.title()} · "
f"{format_bytes(check.manifest.archive_size)} · " f"{format_bytes(check.manifest.archive_size)} · "
f"published {check.manifest.published_at} · " f"published {check.manifest.published_at} · "
f"checked {check.checked_at or 'time unavailable'}" 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._details_value.set(details)
self._set_release_notes_preview(notes_preview)
self._install_button.configure( self._install_button.configure(
state=( state=(
"normal" "normal"
@@ -442,6 +540,20 @@ class LauncherUpdateDialog:
) )
self._refresh_history_actions(busy=busy) 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: def _populate_history(self, *, busy: bool) -> None:
assert self._history is not None assert self._history is not None
assert self._history_filter_value is not None assert self._history_filter_value is not None
@@ -461,16 +573,22 @@ class LauncherUpdateDialog:
else () else ()
) )
} }
current_version = ( installed_current_version = (
self._installed_history.current_version self._installed_history.current_version
if self._installed_history is not None if self._installed_history is not None
and self._installed_history.current_version is not None and self._installed_history.current_version is not None
else self._launcher_version else self._launcher_version
) )
try: try:
current_semantic = SemanticVersion.parse(current_version) installed_current_semantic = SemanticVersion.parse(
installed_current_version
)
except ReleaseModelError: 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() filter_name = str(self._history_filter_value.get()).casefold()
def channel_visible(channel: str) -> bool: def channel_visible(channel: str) -> bool:
@@ -478,20 +596,27 @@ class LauncherUpdateDialog:
remote_history = self._get_remote_history() remote_history = self._get_remote_history()
remote_versions: set[str] = set() remote_versions: set[str] = set()
displayed_versions: set[str] = set()
if remote_history is not None: if remote_history is not None:
for index, release in enumerate(remote_history.releases): for index, release in enumerate(remote_history.releases):
if not channel_visible(release.channel): if not channel_visible(release.channel):
continue continue
remote_versions.add(release.version) remote_versions.add(release.version)
displayed_versions.add(release.version)
local = installed.get(release.version) local = installed.get(release.version)
is_older = ( is_older = (
current_semantic is not None installed_current_semantic is not None
and SemanticVersion.parse(release.version) < current_semantic and SemanticVersion.parse(release.version)
< installed_current_semantic
) )
can_rollback = release.status == "active" and is_older local_active = bool(local is not None and local.active)
state = self._remote_release_state( can_rollback = (
release, release.status == "active" and is_older and not local_active
local_active=bool(local is not None and 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, local_retained=local is not None,
is_older=is_older, is_older=is_older,
) )
@@ -517,14 +642,23 @@ class LauncherUpdateDialog:
for index, release in enumerate(self._installed_history.releases): for index, release in enumerate(self._installed_history.releases):
if release.version in remote_versions or not channel_visible(release.channel): if release.version in remote_versions or not channel_visible(release.channel):
continue continue
displayed_versions.add(release.version)
try: try:
is_older = ( is_older = (
current_semantic is not None installed_current_semantic is not None
and SemanticVersion.parse(release.version) < current_semantic and SemanticVersion.parse(release.version)
< installed_current_semantic
) )
except ReleaseModelError: except ReleaseModelError:
is_older = False 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}" iid = f"local:{index}:{release.version}"
self._history.insert( self._history.insert(
"", "",
@@ -541,7 +675,29 @@ class LauncherUpdateDialog:
release.version, release.version,
release.channel, release.channel,
None, 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(): if not self._history.get_children():
iid = "placeholder" iid = "placeholder"
@@ -567,24 +723,6 @@ class LauncherUpdateDialog:
self._history.focus(selected_iid) self._history.focus(selected_iid)
self._refresh_history_actions(busy=busy) 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: def _history_selected(self, _event: object | None = None) -> None:
self._refresh_history_actions(busy=self._is_busy()) self._refresh_history_actions(busy=self._is_busy())
@@ -621,6 +759,7 @@ class LauncherUpdateDialog:
assert self._check_button is not None assert self._check_button is not None
self._status_value.set("The signed update channel could not be checked.") self._status_value.set("The signed update channel could not be checked.")
self._details_value.set(message) self._details_value.set(message)
self._set_release_notes_preview("")
self._check_button.configure(state="normal") self._check_button.configure(state="normal")
def _check_again(self) -> None: def _check_again(self) -> None:
@@ -630,6 +769,7 @@ class LauncherUpdateDialog:
assert self._install_button is not None assert self._install_button is not None
self._status_value.set("Checking the signed update channel…") self._status_value.set("Checking the signed update channel…")
self._details_value.set("Authenticating the selected channel manifests and signatures.") self._details_value.set("Authenticating the selected channel manifests and signatures.")
self._set_release_notes_preview("")
self._check_button.configure(state="disabled") self._check_button.configure(state="disabled")
self._install_button.configure(state="disabled") self._install_button.configure(state="disabled")
self._request_check() self._request_check()
@@ -637,8 +777,9 @@ class LauncherUpdateDialog:
def _view_release_notes(self) -> None: def _view_release_notes(self) -> None:
selected = self._selected_history_item() selected = self._selected_history_item()
if selected is not None: if selected is not None:
if selected[2] is not None: notes_url = selected[2]
self._open_article(selected[2]) if notes_url is not None:
self._open_article(notes_url)
return return
check = self._get_check() check = self._get_check()
if check is not None and check.manifest.notes_url is not None: if check is not None and check.manifest.notes_url is not None:

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "PTEU-Linux-Launcher" name = "PTEU-Linux-Launcher"
version = "0.9.8" version = "0.9.11"
description = "PristonTale EU Linux installer, clean launcher, and diagnostic harness" description = "PristonTale EU Linux installer, clean launcher, and diagnostic harness"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [] dependencies = []

View File

@@ -2,16 +2,16 @@
"application": "PristonTale-EU", "application": "PristonTale-EU",
"archive": { "archive": {
"layout": "wrapped", "layout": "wrapped",
"name": "PristonTale-EU-0.9.10.tar.gz", "name": "PristonTale-EU-0.9.11.tar.gz",
"sha256": "76df8942d2f0b3b533eb967f8a2379b2a7bdd44fd58305d0f8bf061778fbd9ea", "sha256": "60b13d6fbc8fe27714c7fe1a65fa73785a13962d96d03decfac1aa62c4e3f6db",
"size": 1473687, "size": 1474010,
"url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v0.9.10/PristonTale-EU-0.9.10.tar.gz" "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_sha256": "60b13d6fbc8fe27714c7fe1a65fa73785a13962d96d03decfac1aa62c4e3f6db",
"archive_size": 1473687, "archive_size": 1474010,
"archive_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v0.9.10/PristonTale-EU-0.9.10.tar.gz", "archive_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v0.9.11/PristonTale-EU-0.9.11.tar.gz",
"channel": "stable", "channel": "stable",
"commit": "27ed039f327d4d14d1d04717e0fd4f20ad7b7dc9", "commit": "a362b0bb8e2bf74db9075c5f58db5236b1dc98f6",
"compatibility": { "compatibility": {
"config_schema": 1, "config_schema": 1,
"manifest_schema": 1, "manifest_schema": 1,
@@ -36,7 +36,7 @@
}, },
{ {
"path": "ptlinux/__init__.py", "path": "ptlinux/__init__.py",
"sha256": "a80db24319120aa083e8abf0e099bacc8958bc04e59a2ac17ee270ae0f7094a3", "sha256": "b4eaa89165a506ff593083dd32ad30fefe1d8ec7d09af67b45b29a3d13599df0",
"size": 152 "size": 152
}, },
{ {
@@ -306,8 +306,8 @@
}, },
{ {
"path": "ptlinux/ui/components/update_dialog.py", "path": "ptlinux/ui/components/update_dialog.py",
"sha256": "af5775b07b6872b5e5f465deb8d9f583ddfeeae1df79a126fb2d2fe967bb3d06", "sha256": "1defc85b4cf30f15fa1c30bcfd0cbf961072ba2d2ab8a681ef981e34ef3598d4",
"size": 22005 "size": 24138
}, },
{ {
"path": "ptlinux/ui/dashboard.py", "path": "ptlinux/ui/dashboard.py",
@@ -371,21 +371,21 @@
}, },
{ {
"path": "release.json", "path": "release.json",
"sha256": "e1c3a4501457050f00ed98d254b83521f173bccb91cb6176bff2ca89aab99adc", "sha256": "afc50744e1d2cd13c3062b6160cd21a0b25870da81270b75b0a35940ca33b23b",
"size": 223 "size": 223
} }
], ],
"minimum_updater_version": "0.5.0", "minimum_updater_version": "0.5.0",
"notes_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/tag/v0.9.10", "notes_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/tag/v0.9.11",
"published_at": "2026-09-09T21:01:49Z", "published_at": "2026-09-09T21:12:47Z",
"release": { "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", "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.10", "release_notes_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/tag/v0.9.11",
"summary": "Restores automatic updates for existing launchers and completes the transition to flat release archives.", "summary": "Fixes the update window layout so release notes no longer cover version history or controls.",
"title": "PristonTale EU Launcher 0.9.10" "title": "PristonTale EU Launcher 0.9.11"
}, },
"schema": 1, "schema": 1,
"status": "active", "status": "active",
"tag": "v0.9.10", "tag": "v0.9.11",
"version": "0.9.10" "version": "0.9.11"
} }

Binary file not shown.