Fixed keyboard / input issues with resizing.

This commit is contained in:
2026-09-06 21:52:07 +02:00
parent 0963ae34b9
commit 6eab4b961f
3 changed files with 58 additions and 8 deletions

View File

@@ -48,6 +48,7 @@ _NET_WM_STATE_REMOVE = 0
_NET_WM_STATE_SOURCE_APPLICATION = 1
_NET_WM_BYPASS_COMPOSITOR_PREFER_COMPOSITING = 2
WINDOW_FOCUS_REPAIR_INTERVAL_SECONDS = 1.0
GAMESCOPE_FOCUS_REPAIR_INTERVAL_SECONDS = 0.25
_P_MIN_SIZE = 1 << 4
_P_MAX_SIZE = 1 << 5
_P_RESIZE_INC = 1 << 6
@@ -1556,6 +1557,28 @@ def _maintain_game_window_focus(
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_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(
mapper: _X11,
process: subprocess.Popen[bytes],
@@ -1732,7 +1755,11 @@ def run_game_window(
file=sys.stderr,
flush=True,
)
return int(process.wait())
return _maintain_gamescope_game_window_focus(
mapper,
process,
game_window,
)
_prepare_game_window(
mapper,
game_window,

View File

@@ -146,13 +146,13 @@ def _bundled_gamescope_host_supported() -> 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():
return False
path = _bundled_gamescope_path()
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
digest = hashlib.sha256()
with path.open("rb") as handle:
@@ -160,7 +160,14 @@ def _bundled_gamescope_is_usable() -> bool:
digest.update(chunk)
except OSError:
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]:

View File

@@ -776,6 +776,7 @@ def _stage_and_install_release(
raise LauncherUpdateError(
f"launcher release destination is not a directory: {destination}"
)
_validate_release_directory(destination)
if _tree_digest(destination) != _tree_digest(staging):
raise LauncherUpdateError(
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():
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
@@ -1019,15 +1030,20 @@ def _archive_matches(path: Path, manifest: UpdateManifest) -> bool:
def _tree_digest(root: Path) -> str:
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()):
relative = item.relative_to(root).as_posix().encode("utf-8")
if item.is_symlink():
raise LauncherUpdateError(f"launcher release contains a symbolic link: {item}")
if item.is_dir():
digest.update(b"D\x00" + relative + b"\x00")
elif item.is_file():
state = item.stat()
mode = str(state.st_mode & 0o777).encode("ascii")
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(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:
for chunk in iter(lambda: handle.read(READ_SIZE), b""):
digest.update(chunk)