Reworked some umu / proton 7 maximize window and keyboard issues.

This commit is contained in:
2026-09-04 23:39:43 +02:00
parent 8fe627b6cf
commit 09c8f165be
4 changed files with 242 additions and 14 deletions

View File

@@ -1046,6 +1046,7 @@ def _current_game_log(
"size": state.size,
"load_texture_to_material_failed": analysis.load_texture_to_material_failed,
"import_scene_threw": analysis.import_scene_threw,
"asset_pack_health_failed": analysis.asset_pack_health_failed,
"failed_import_scenes": analysis.failed_import_scenes,
"stage_without_new_stage": analysis.stage_without_new_stage,
"module_code_sync_error": analysis.module_code_sync_error,
@@ -1058,6 +1059,12 @@ def _current_game_log(
"native-game-log-only; server-provided login-screen text is not "
"written to this log"
),
"failed_file_find_count": analysis.failed_file_find_count,
"failed_file_load_count": analysis.failed_file_load_count,
"failed_file_read_count": analysis.failed_file_read_count,
"failed_asset_file_count": analysis.failed_asset_file_count,
"failed_texture_file_count": analysis.failed_texture_file_count,
"failed_effect_file_count": analysis.failed_effect_file_count,
"stage_without_new_stage_events": [
event.to_dict()
for event in analysis.events
@@ -1162,6 +1169,17 @@ def _print_interrupted_game_log(report: dict[str, object]) -> None:
"with publisher-pristine code in this launcher version.",
file=sys.stderr,
)
elif report["asset_pack_health_failed"]:
print(
"Detected a large number of missing game-asset files. This indicates a "
"corrupted or incomplete installation that needs repair.",
file=sys.stderr,
)
print(
"Run `pristontale-eu update --repair` and restart. If the issue "
"reappears, re-download the game files with a clean install path.",
file=sys.stderr,
)
elif report["load_texture_to_material_failed"]:
print(
"Detected a texture/material failure while loading the game world.",

View File

@@ -46,6 +46,7 @@ _X_SUBSTRUCTURE_REDIRECT_MASK = 1 << 20
_X_WM_STATE_ICONIC = 3
_NET_WM_STATE_REMOVE = 0
_NET_WM_STATE_SOURCE_APPLICATION = 1
WINDOW_FOCUS_REPAIR_INTERVAL_SECONDS = 1.0
_P_MIN_SIZE = 1 << 4
_P_MAX_SIZE = 1 << 5
_MOTIF_HINTS_FUNCTIONS = 1 << 0
@@ -466,7 +467,20 @@ class _X11:
and updated_normal_hints != normal_hints
)
motif_changed = motif_hints is not None and updated_motif_hints != motif_hints
if not normal_changed and not motif_changed:
allowed_actions = self._long_property(window, b"_NET_WM_ALLOWED_ACTIONS", 64)
allowed_actions_with_scaling = self._allowed_actions_with_scaling(
allowed_actions,
)
allowed_actions_changed = (
allowed_actions is not None
and allowed_actions_with_scaling is not None
and allowed_actions_with_scaling != allowed_actions
)
if (
not normal_changed
and not motif_changed
and not allowed_actions_changed
):
return not confirm or self.window_allows_scaling(window)
confirmed = False
@@ -485,6 +499,13 @@ class _X11:
b"_MOTIF_WM_HINTS",
updated_motif_hints,
)
if allowed_actions_changed and allowed_actions_with_scaling is not None:
self._replace_long_property(
window,
b"_NET_WM_ALLOWED_ACTIONS",
b"_ATOM",
allowed_actions_with_scaling,
)
if not confirm:
self.library.XFlush(self.display)
confirmed = True
@@ -515,6 +536,16 @@ class _X11:
)
except Exception as exc:
restore_errors.append(exc)
if allowed_actions_changed and allowed_actions is not None:
try:
self._replace_long_property(
window,
b"_NET_WM_ALLOWED_ACTIONS",
b"_ATOM",
allowed_actions,
)
except Exception as exc:
restore_errors.append(exc)
try:
self.library.XSync(self.display, 0)
except Exception as exc:
@@ -546,6 +577,36 @@ class _X11:
vertical,
}.issubset(actions)
def _allowed_actions_with_scaling(
self,
actions: tuple[int, ...] | None,
) -> tuple[int, ...] | None:
if actions is None:
return None
resize = self._intern_atom(
b"_NET_WM_ACTION_RESIZE",
only_if_exists=True,
)
horizontal = self._intern_atom(
b"_NET_WM_ACTION_MAXIMIZE_HORZ",
only_if_exists=True,
)
vertical = self._intern_atom(
b"_NET_WM_ACTION_MAXIMIZE_VERT",
only_if_exists=True,
)
if resize == 0 or horizontal == 0 or vertical == 0:
return actions
updated_actions = list(actions)
known_actions = set(actions)
for atom in (resize, horizontal, vertical):
if atom not in known_actions:
updated_actions.append(atom)
known_actions.add(atom)
return tuple(updated_actions)
def _wait_for_scaling_actions(self, window: int) -> bool:
deadline = time.monotonic() + WINDOW_ACTION_CONFIRM_TIMEOUT_SECONDS
while not self.window_allows_scaling(window):
@@ -595,6 +656,7 @@ class _X11:
watched_atoms = {
self._intern_atom(b"WM_NORMAL_HINTS", only_if_exists=False),
self._intern_atom(b"_MOTIF_WM_HINTS", only_if_exists=False),
self._intern_atom(b"_NET_WM_ALLOWED_ACTIONS", only_if_exists=False),
}
changed = False
events_read = 0
@@ -831,6 +893,17 @@ class _X11:
)
self.library.XSync(self.display, 0)
def window_is_focused(self, window: int) -> bool:
focused = ctypes.c_ulong()
revert_to = ctypes.c_int()
if self.library.XGetInputFocus(
self.display,
ctypes.byref(focused),
ctypes.byref(revert_to),
) != 1:
return False
return int(focused.value) == window
def activate_window(self, window: int) -> bool:
"""Ask the window manager to restore and activate one client."""
@@ -1111,6 +1184,12 @@ class _X11:
ctypes.c_ulong,
]
self.library.XSetInputFocus.restype = ctypes.c_int
self.library.XGetInputFocus.argtypes = [
ctypes.c_void_p,
ctypes.POINTER(ctypes.c_ulong),
ctypes.POINTER(ctypes.c_int),
]
self.library.XGetInputFocus.restype = ctypes.c_int
self.library.XSendEvent.argtypes = [
ctypes.c_void_p,
ctypes.c_ulong,
@@ -1250,6 +1329,27 @@ def _focus_game_window(mapper: _X11, window: int) -> None:
)
def _repair_game_window_focus(mapper: _X11, window: int) -> bool:
try:
if mapper.window_is_focused(window):
return True
except Exception:
return False
try:
mapper.activate_window(window)
time.sleep(WINDOW_ACTIVATION_DELAY_SECONDS)
mapper.focus_window(window)
return mapper.window_is_focused(window)
except Exception as exc:
print(
f"Warning: could not repair game-window focus ({exc}); continuing "
"normally",
file=sys.stderr,
flush=True,
)
return False
def _enable_game_window_scaling(mapper: _X11, window: int) -> bool:
"""Enable confirmed resize/maximize actions without risking game startup."""
@@ -1301,7 +1401,8 @@ def _maintain_game_window_scaling(
return int(process.wait())
repair_failures = 0
next_repair_at = 0.0
next_scaling_repair_at = 0.0
next_focus_repair_at = time.monotonic() + WINDOW_FOCUS_REPAIR_INTERVAL_SECONDS
while process.poll() is None:
try:
hints_changed = mapper.wait_for_scaling_hint_change(window)
@@ -1313,13 +1414,17 @@ def _maintain_game_window_scaling(
flush=True,
)
break
if not hints_changed:
continue
now = time.monotonic()
if now < next_repair_at:
time.sleep(next_repair_at - now)
if now >= next_focus_repair_at:
_repair_game_window_focus(mapper, window)
next_focus_repair_at = time.monotonic() + WINDOW_FOCUS_REPAIR_INTERVAL_SECONDS
if not hints_changed and now < next_scaling_repair_at:
continue
try:
mapper.enable_window_scaling(window, confirm=False)
mapper.enable_window_scaling(
window,
confirm=False,
)
except Exception as exc:
repair_failures += 1
print(
@@ -1337,7 +1442,7 @@ def _maintain_game_window_scaling(
break
else:
repair_failures = 0
next_repair_at = time.monotonic() + WINDOW_SCALING_REPAIR_INTERVAL_SECONDS
next_scaling_repair_at = time.monotonic() + WINDOW_SCALING_REPAIR_INTERVAL_SECONDS
return int(process.wait())
@@ -1517,7 +1622,7 @@ def run_game_window(
game_window,
borderless=borderless,
)
if startup_stable and not borderless:
if not borderless:
return _maintain_game_window_scaling(
mapper,
process,