Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f53f4d0b08 | |||
| 3cbe4f72b0 | |||
| 6eab4b961f |
@@ -1,6 +1,6 @@
|
|||||||
# PristonTale EU for Linux
|
# PristonTale EU for Linux
|
||||||
|
|
||||||
Version 0.9.3 installs, updates, repairs, and launches PristonTale EU through a
|
Version 0.9.5 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-3.tar.gz.sig \
|
-signature pristontale-eu-linux-installer-v0-9-5.tar.gz.sig \
|
||||||
pristontale-eu-linux-installer-v0-9-3.tar.gz
|
pristontale-eu-linux-installer-v0-9-5.tar.gz
|
||||||
sha256sum -c pristontale-eu-linux-installer-v0-9-3.tar.gz.sha256
|
sha256sum -c pristontale-eu-linux-installer-v0-9-5.tar.gz.sha256
|
||||||
tar -xzf pristontale-eu-linux-installer-v0-9-3.tar.gz
|
tar -xzf pristontale-eu-linux-installer-v0-9-5.tar.gz
|
||||||
cd pristontale-eu-linux-installer-v0-9-3
|
cd pristontale-eu-linux-installer-v0-9-5
|
||||||
./install.sh
|
./install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ from .paths import AppPaths
|
|||||||
|
|
||||||
__all__ = ["AppPaths", "__version__"]
|
__all__ = ["AppPaths", "__version__"]
|
||||||
|
|
||||||
__version__ = "0.9.3"
|
__version__ = "0.9.5"
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ _NET_WM_STATE_REMOVE = 0
|
|||||||
_NET_WM_STATE_SOURCE_APPLICATION = 1
|
_NET_WM_STATE_SOURCE_APPLICATION = 1
|
||||||
_NET_WM_BYPASS_COMPOSITOR_PREFER_COMPOSITING = 2
|
_NET_WM_BYPASS_COMPOSITOR_PREFER_COMPOSITING = 2
|
||||||
WINDOW_FOCUS_REPAIR_INTERVAL_SECONDS = 1.0
|
WINDOW_FOCUS_REPAIR_INTERVAL_SECONDS = 1.0
|
||||||
|
GAMESCOPE_FOCUS_REPAIR_INTERVAL_SECONDS = 0.25
|
||||||
_P_MIN_SIZE = 1 << 4
|
_P_MIN_SIZE = 1 << 4
|
||||||
_P_MAX_SIZE = 1 << 5
|
_P_MAX_SIZE = 1 << 5
|
||||||
_P_RESIZE_INC = 1 << 6
|
_P_RESIZE_INC = 1 << 6
|
||||||
@@ -971,6 +972,11 @@ class _X11:
|
|||||||
"""Raise the mapped game client and give it keyboard focus."""
|
"""Raise the mapped game client and give it keyboard focus."""
|
||||||
|
|
||||||
self.library.XMapRaised(self.display, window)
|
self.library.XMapRaised(self.display, window)
|
||||||
|
self.restore_input_focus(window)
|
||||||
|
|
||||||
|
def restore_input_focus(self, window: int) -> None:
|
||||||
|
"""Give a mapped client keyboard focus without raising its host window."""
|
||||||
|
|
||||||
self.library.XSetInputFocus(
|
self.library.XSetInputFocus(
|
||||||
self.display,
|
self.display,
|
||||||
window,
|
window,
|
||||||
@@ -1436,6 +1442,27 @@ def _repair_game_window_focus(mapper: _X11, window: int) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _repair_gamescope_game_window_focus(mapper: _X11, window: int) -> bool:
|
||||||
|
"""Restore nested focus without asking the desktop to activate Gamescope."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
if mapper.window_is_focused(window):
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
mapper.restore_input_focus(window)
|
||||||
|
return mapper.window_is_focused(window)
|
||||||
|
except Exception as exc:
|
||||||
|
print(
|
||||||
|
f"Warning: could not restore nested game input focus ({exc}); "
|
||||||
|
"continuing normally",
|
||||||
|
file=sys.stderr,
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _enable_game_window_scaling(mapper: _X11, window: int) -> bool:
|
def _enable_game_window_scaling(mapper: _X11, window: int) -> bool:
|
||||||
"""Enable confirmed resize/maximize actions without risking game startup."""
|
"""Enable confirmed resize/maximize actions without risking game startup."""
|
||||||
|
|
||||||
@@ -1556,6 +1583,28 @@ def _maintain_game_window_focus(
|
|||||||
return int(process.wait())
|
return int(process.wait())
|
||||||
|
|
||||||
|
|
||||||
|
def _maintain_gamescope_game_window_focus(
|
||||||
|
mapper: _X11,
|
||||||
|
process: subprocess.Popen[bytes],
|
||||||
|
window: int,
|
||||||
|
) -> int:
|
||||||
|
"""Restore nested keyboard focus after host resize/maximize operations."""
|
||||||
|
|
||||||
|
next_focus_repair_at = (
|
||||||
|
time.monotonic() + GAMESCOPE_FOCUS_REPAIR_INTERVAL_SECONDS
|
||||||
|
)
|
||||||
|
while process.poll() is None:
|
||||||
|
time.sleep(GAMESCOPE_FOCUS_REPAIR_INTERVAL_SECONDS)
|
||||||
|
now = time.monotonic()
|
||||||
|
if now < next_focus_repair_at:
|
||||||
|
continue
|
||||||
|
_repair_gamescope_game_window_focus(mapper, window)
|
||||||
|
next_focus_repair_at = (
|
||||||
|
time.monotonic() + GAMESCOPE_FOCUS_REPAIR_INTERVAL_SECONDS
|
||||||
|
)
|
||||||
|
return int(process.wait())
|
||||||
|
|
||||||
|
|
||||||
def _guard_game_window_startup(
|
def _guard_game_window_startup(
|
||||||
mapper: _X11,
|
mapper: _X11,
|
||||||
process: subprocess.Popen[bytes],
|
process: subprocess.Popen[bytes],
|
||||||
@@ -1732,7 +1781,11 @@ def run_game_window(
|
|||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
return int(process.wait())
|
return _maintain_gamescope_game_window_focus(
|
||||||
|
mapper,
|
||||||
|
process,
|
||||||
|
game_window,
|
||||||
|
)
|
||||||
_prepare_game_window(
|
_prepare_game_window(
|
||||||
mapper,
|
mapper,
|
||||||
game_window,
|
game_window,
|
||||||
|
|||||||
@@ -146,13 +146,13 @@ def _bundled_gamescope_host_supported() -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def _bundled_gamescope_is_usable() -> bool:
|
def _bundled_gamescope_is_usable() -> bool:
|
||||||
"""Verify the launcher-local executable before selecting it."""
|
"""Verify the launcher-local executable and restore its safe archive mode."""
|
||||||
|
|
||||||
if not _bundled_gamescope_host_supported():
|
if not _bundled_gamescope_host_supported():
|
||||||
return False
|
return False
|
||||||
path = _bundled_gamescope_path()
|
path = _bundled_gamescope_path()
|
||||||
try:
|
try:
|
||||||
if not path.is_file() or not os.access(path, os.X_OK):
|
if path.is_symlink() or not path.is_file():
|
||||||
return False
|
return False
|
||||||
digest = hashlib.sha256()
|
digest = hashlib.sha256()
|
||||||
with path.open("rb") as handle:
|
with path.open("rb") as handle:
|
||||||
@@ -160,7 +160,14 @@ def _bundled_gamescope_is_usable() -> bool:
|
|||||||
digest.update(chunk)
|
digest.update(chunk)
|
||||||
except OSError:
|
except OSError:
|
||||||
return False
|
return False
|
||||||
return digest.hexdigest() == BUNDLED_GAMESCOPE_SHA256
|
if digest.hexdigest() != BUNDLED_GAMESCOPE_SHA256:
|
||||||
|
return False
|
||||||
|
if not os.access(path, os.X_OK):
|
||||||
|
try:
|
||||||
|
path.chmod(0o755)
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
return os.access(path, os.X_OK)
|
||||||
|
|
||||||
|
|
||||||
def _select_wayland_gamescope(executable: str) -> tuple[str, bool, bool]:
|
def _select_wayland_gamescope(executable: str) -> tuple[str, bool, bool]:
|
||||||
|
|||||||
@@ -776,6 +776,7 @@ def _stage_and_install_release(
|
|||||||
raise LauncherUpdateError(
|
raise LauncherUpdateError(
|
||||||
f"launcher release destination is not a directory: {destination}"
|
f"launcher release destination is not a directory: {destination}"
|
||||||
)
|
)
|
||||||
|
_validate_release_directory(destination)
|
||||||
if _tree_digest(destination) != _tree_digest(staging):
|
if _tree_digest(destination) != _tree_digest(staging):
|
||||||
raise LauncherUpdateError(
|
raise LauncherUpdateError(
|
||||||
f"launcher release {manifest.version} already exists with different contents"
|
f"launcher release {manifest.version} already exists with different contents"
|
||||||
@@ -916,6 +917,16 @@ def _validate_release_directory(release: Path) -> str:
|
|||||||
):
|
):
|
||||||
if not (release / required).is_file():
|
if not (release / required).is_file():
|
||||||
raise LauncherUpdateError(f"launcher release is missing {required}")
|
raise LauncherUpdateError(f"launcher release is missing {required}")
|
||||||
|
bundled_gamescope = (
|
||||||
|
release
|
||||||
|
/ "ptlinux/native/gamescope-3.16.20-ubuntu26.04-amd64"
|
||||||
|
)
|
||||||
|
if bundled_gamescope.exists():
|
||||||
|
mode = bundled_gamescope.stat().st_mode
|
||||||
|
if not stat.S_ISREG(mode) or not mode & 0o111:
|
||||||
|
raise LauncherUpdateError(
|
||||||
|
"launcher release contains a non-executable Gamescope compatibility binary"
|
||||||
|
)
|
||||||
return version
|
return version
|
||||||
|
|
||||||
|
|
||||||
@@ -1019,15 +1030,20 @@ def _archive_matches(path: Path, manifest: UpdateManifest) -> bool:
|
|||||||
|
|
||||||
def _tree_digest(root: Path) -> str:
|
def _tree_digest(root: Path) -> str:
|
||||||
digest = hashlib.sha256()
|
digest = hashlib.sha256()
|
||||||
|
root_mode = root.stat().st_mode
|
||||||
|
digest.update(b"R\x00" + str(root_mode & 0o777).encode("ascii") + b"\x00")
|
||||||
for item in sorted(root.rglob("*"), key=lambda value: value.relative_to(root).as_posix()):
|
for item in sorted(root.rglob("*"), key=lambda value: value.relative_to(root).as_posix()):
|
||||||
relative = item.relative_to(root).as_posix().encode("utf-8")
|
relative = item.relative_to(root).as_posix().encode("utf-8")
|
||||||
if item.is_symlink():
|
if item.is_symlink():
|
||||||
raise LauncherUpdateError(f"launcher release contains a symbolic link: {item}")
|
raise LauncherUpdateError(f"launcher release contains a symbolic link: {item}")
|
||||||
if item.is_dir():
|
state = item.stat()
|
||||||
digest.update(b"D\x00" + relative + b"\x00")
|
mode = str(state.st_mode & 0o777).encode("ascii")
|
||||||
elif item.is_file():
|
if stat.S_ISDIR(state.st_mode):
|
||||||
|
digest.update(b"D\x00" + relative + b"\x00" + mode + b"\x00")
|
||||||
|
elif stat.S_ISREG(state.st_mode):
|
||||||
digest.update(b"F\x00" + relative + b"\x00")
|
digest.update(b"F\x00" + relative + b"\x00")
|
||||||
digest.update(str(item.stat().st_size).encode("ascii") + b"\x00")
|
digest.update(mode + b"\x00")
|
||||||
|
digest.update(str(state.st_size).encode("ascii") + b"\x00")
|
||||||
with item.open("rb") as handle:
|
with item.open("rb") as handle:
|
||||||
for chunk in iter(lambda: handle.read(READ_SIZE), b""):
|
for chunk in iter(lambda: handle.read(READ_SIZE), b""):
|
||||||
digest.update(chunk)
|
digest.update(chunk)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "PTEU-Linux-Launcher"
|
name = "PTEU-Linux-Launcher"
|
||||||
version = "0.9.3"
|
version = "0.9.5"
|
||||||
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 = []
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"archive_sha256": "b60a8a3202eb2cbacee1c0accd8ced9b06f8feaf0ac19b2501fff46b221630b2",
|
"archive_sha256": "890443aab4cd0e33bb2e2a44ea86b3fd929488eba045b803c2c0662ce68c08ff",
|
||||||
"archive_size": 1409799,
|
"archive_size": 1410277,
|
||||||
"archive_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v0.9.3/pristontale-eu-linux-update-v0.9.3.tar.gz",
|
"archive_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v0.9.5/pristontale-eu-linux-update-v0.9.5.tar.gz",
|
||||||
"channel": "stable",
|
"channel": "stable",
|
||||||
"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.3",
|
"notes_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/tag/v0.9.5",
|
||||||
"published_at": "2026-09-06T19:03:48Z",
|
"published_at": "2026-09-06T20:06:31Z",
|
||||||
"schema": 1,
|
"schema": 1,
|
||||||
"version": "0.9.3"
|
"version": "0.9.5"
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user