Updated the update system to support multi channels (stable / pre-release).

This commit is contained in:
2026-09-09 20:52:00 +02:00
parent 2a53a59cef
commit 5770969629
12 changed files with 3329 additions and 254 deletions

View File

@@ -18,6 +18,7 @@ from .desktop_identity import (
APPLICATION_WM_CLASS,
APPLICATION_WM_INSTANCE,
)
from .window_diagnostics import WindowDiagnosticRecorder
WINDOW_TITLE = APPLICATION_NAME
WINDOW_DISCOVERY_TIMEOUT_SECONDS = 60.0
@@ -120,6 +121,22 @@ def _motif_value_with_enabled_action(value: int, action: int) -> int:
return value | action
def _motif_value_allows_action(value: int, action: int) -> bool:
"""Interpret a Motif inclusive or all-except action mask."""
if value & _MOTIF_ALL:
return value & action == 0
return value & action != 0
def _wm_state_name(value: int | None) -> str:
return {
_X_WM_STATE_WITHDRAWN: "withdrawn",
_X_WM_STATE_NORMAL: "normal",
_X_WM_STATE_ICONIC: "iconic",
}.get(value, "unknown" if value is None else f"other_{value}")
def _motif_hints_with_scaling(hints: Sequence[int]) -> tuple[int, ...]:
"""Add native resize/maximize actions while retaining every other hint."""
@@ -483,6 +500,147 @@ class _X11:
for atom in self._screen_filling_state_atoms()
)
def window_net_wm_state_flags(self, window: int) -> dict[str, bool | None]:
"""Read EWMH state flags that distinguish minimize, remap, and raise."""
states = self._long_property(window, b"_NET_WM_STATE", 64)
def state(name: bytes) -> bool | None:
atom = self._intern_atom(name, only_if_exists=True)
if states is None or atom == 0:
return None
return atom in states
return {
"net_wm_state_present": states is not None,
"fullscreen": state(b"_NET_WM_STATE_FULLSCREEN"),
"maximized_horizontal": state(b"_NET_WM_STATE_MAXIMIZED_HORZ"),
"maximized_vertical": state(b"_NET_WM_STATE_MAXIMIZED_VERT"),
"hidden": state(b"_NET_WM_STATE_HIDDEN"),
"above": state(b"_NET_WM_STATE_ABOVE"),
"demands_attention": state(b"_NET_WM_STATE_DEMANDS_ATTENTION"),
"skip_taskbar": state(b"_NET_WM_STATE_SKIP_TASKBAR"),
}
def window_diagnostic_snapshot(self, window: int) -> dict[str, object]:
"""Read the WM properties needed to diagnose focus and maximize behavior."""
state = self.window_state(window)
size = self.window_size(window)
net_state_flags = self.window_net_wm_state_flags(window)
allowed_actions = self._long_property(
window,
b"_NET_WM_ALLOWED_ACTIONS",
64,
)
def contains(values: tuple[int, ...] | None, atom: int) -> bool | None:
if values is None or atom == 0:
return None
return atom in values
resize_action = self._intern_atom(
b"_NET_WM_ACTION_RESIZE",
only_if_exists=True,
)
maximize_horizontal_action = self._intern_atom(
b"_NET_WM_ACTION_MAXIMIZE_HORZ",
only_if_exists=True,
)
maximize_vertical_action = self._intern_atom(
b"_NET_WM_ACTION_MAXIMIZE_VERT",
only_if_exists=True,
)
minimize_action = self._intern_atom(
b"_NET_WM_ACTION_MINIMIZE",
only_if_exists=True,
)
normal_hints = self._long_property(window, b"WM_NORMAL_HINTS", 18)
normal_flags = normal_hints[0] if normal_hints else None
has_minimum = bool(normal_flags & _P_MIN_SIZE) if normal_flags is not None else None
has_maximum = bool(normal_flags & _P_MAX_SIZE) if normal_flags is not None else None
fixed_size = (
bool(
has_minimum
and has_maximum
and normal_hints is not None
and len(normal_hints) >= 9
and normal_hints[5:7] == normal_hints[7:9]
)
if normal_flags is not None
else None
)
motif_hints = self._long_property(window, b"_MOTIF_WM_HINTS", 5)
motif_flags = motif_hints[0] if motif_hints and len(motif_hints) >= 3 else None
motif_functions = motif_hints[1] if motif_flags is not None else None
motif_decorations = motif_hints[2] if motif_flags is not None else None
return {
"wm_state": _wm_state_name(state),
"active": self.window_is_active(window),
"focused": self.window_is_focused(window),
"width": size[0] if size is not None else None,
"height": size[1] if size is not None else None,
"borderless": self.window_is_borderless(window),
**net_state_flags,
"allowed_actions_present": allowed_actions is not None,
"allows_resize": contains(allowed_actions, resize_action),
"allows_minimize": contains(allowed_actions, minimize_action),
"allows_maximize_horizontal": contains(
allowed_actions,
maximize_horizontal_action,
),
"allows_maximize_vertical": contains(
allowed_actions,
maximize_vertical_action,
),
"normal_hints_present": normal_hints is not None,
"normal_minimum_size": has_minimum,
"normal_maximum_size": has_maximum,
"normal_fixed_size": fixed_size,
"normal_resize_increment": (
bool(normal_flags & _P_RESIZE_INC)
if normal_flags is not None
else None
),
"motif_hints_present": motif_flags is not None,
"motif_allows_resize": (
_motif_value_allows_action(motif_functions, _MOTIF_FUNC_RESIZE)
if (
motif_flags is not None
and motif_flags & _MOTIF_HINTS_FUNCTIONS
and motif_functions is not None
)
else None
),
"motif_allows_maximize": (
_motif_value_allows_action(motif_functions, _MOTIF_FUNC_MAXIMIZE)
if (
motif_flags is not None
and motif_flags & _MOTIF_HINTS_FUNCTIONS
and motif_functions is not None
)
else None
),
"motif_resize_decoration": (
_motif_value_allows_action(motif_decorations, _MOTIF_DECOR_RESIZE)
if (
motif_flags is not None
and motif_flags & _MOTIF_HINTS_DECORATIONS
and motif_decorations is not None
)
else None
),
"motif_maximize_decoration": (
_motif_value_allows_action(motif_decorations, _MOTIF_DECOR_MAXIMIZE)
if (
motif_flags is not None
and motif_flags & _MOTIF_HINTS_DECORATIONS
and motif_decorations is not None
)
else None
),
}
def force_windowed_state(self, window: int) -> bool:
"""Remove fullscreen/maximized EWMH state and wait for WM acknowledgement.
@@ -1283,6 +1441,25 @@ class _X11:
self.library.XCloseDisplay.restype = ctypes.c_int
def _window_diagnostic_details(mapper: _X11, window: int) -> dict[str, object]:
"""Keep diagnostic reads from affecting window supervision."""
try:
return mapper.window_diagnostic_snapshot(window)
except Exception as exc:
return {"snapshot_error": type(exc).__name__}
def _sample_window_diagnostic_details(
diagnostics: WindowDiagnosticRecorder | None,
mapper: _X11,
window: int,
) -> dict[str, object]:
if diagnostics is None or not diagnostics.accepts_samples:
return {}
return _window_diagnostic_details(mapper, window)
def _prepare_game_window(
mapper: _X11,
window: int,
@@ -1404,22 +1581,54 @@ def _focus_game_window(mapper: _X11, window: int) -> None:
)
def _repair_game_window_focus(mapper: _X11, window: int) -> bool:
def _repair_game_window_focus(
mapper: _X11,
window: int,
*,
diagnostics: WindowDiagnosticRecorder | None = None,
) -> bool:
"""Repair Wine's input focus only while its desktop window remains active."""
try:
if mapper.window_state(window) == _X_WM_STATE_ICONIC:
state = mapper.window_state(window)
active = mapper.window_is_active(window)
focused = mapper.window_is_focused(window) if active else False
if diagnostics is not None:
diagnostics.transition(
"focus_gate",
(state, active, focused),
"focus_state_changed",
window_id=window,
wm_state=_wm_state_name(state),
active=active,
focused=focused,
)
if state == _X_WM_STATE_ICONIC:
return False
if not mapper.window_is_active(window):
if not active:
return False
if mapper.window_is_focused(window):
if focused:
return True
except Exception:
return False
try:
mapper.restore_input_focus(window)
return mapper.window_is_focused(window)
repaired = mapper.window_is_focused(window)
if diagnostics is not None:
diagnostics.record(
"focus_repair",
window_id=window,
succeeded=repaired,
**_sample_window_diagnostic_details(diagnostics, mapper, window),
)
return repaired
except Exception as exc:
if diagnostics is not None:
diagnostics.record(
"focus_repair_failed",
window_id=window,
error_type=type(exc).__name__,
)
print(
f"Warning: could not repair game-window focus ({exc}); continuing "
"normally",
@@ -1429,22 +1638,62 @@ def _repair_game_window_focus(mapper: _X11, window: int) -> bool:
return False
def _repair_gamescope_game_window_focus(mapper: _X11, window: int) -> bool:
def _repair_gamescope_game_window_focus(
mapper: _X11,
window: int,
*,
diagnostics: WindowDiagnosticRecorder | None = None,
) -> bool:
"""Restore nested focus without asking the desktop to activate Gamescope."""
if not _gamescope_host_focus_is_allowed():
host_focus = _gamescope_host_focus_status()
if diagnostics is not None:
diagnostics.transition(
"host_focus_bridge",
(host_focus.get("allowed"), host_focus.get("bridge")),
"host_focus_bridge_changed",
**host_focus,
)
if host_focus["allowed"] is not True:
return False
try:
if mapper.window_state(window) == _X_WM_STATE_ICONIC:
state = mapper.window_state(window)
focused = mapper.window_is_focused(window)
if diagnostics is not None:
diagnostics.transition(
"nested_focus_gate",
(state, focused),
"nested_focus_state_changed",
window_id=window,
wm_state=_wm_state_name(state),
focused=focused,
)
if state == _X_WM_STATE_ICONIC:
return False
if mapper.window_is_focused(window):
if focused:
return True
except Exception:
return False
try:
mapper.restore_input_focus(window)
return mapper.window_is_focused(window)
repaired = mapper.window_is_focused(window)
if diagnostics is not None:
diagnostics.record(
"nested_focus_repair",
window_id=window,
succeeded=repaired,
host_focus=host_focus,
**_sample_window_diagnostic_details(diagnostics, mapper, window),
)
return repaired
except Exception as exc:
if diagnostics is not None:
diagnostics.record(
"nested_focus_repair_failed",
window_id=window,
error_type=type(exc).__name__,
host_focus=host_focus,
)
print(
f"Warning: could not restore nested game input focus ({exc}); "
"continuing normally",
@@ -1461,22 +1710,47 @@ def _gamescope_host_focus_is_allowed(
) -> bool:
"""Honor the outer X11 window's current focus without trusting stale state."""
return _gamescope_host_focus_status(
environment=environment,
now=now,
)["allowed"] is True
def _gamescope_host_focus_status(
*,
environment: Mapping[str, str] | None = None,
now: float | None = None,
) -> dict[str, object]:
"""Explain whether the nested supervisor may repair input focus."""
env = os.environ if environment is None else environment
state_path = env.get(GAMESCOPE_HOST_FOCUS_STATE_ENV, "").strip()
if not state_path:
# Direct invocations and native Wayland Gamescope have no X11 host-state
# bridge. Retain their existing nested focus behavior.
return True
return {"allowed": True, "bridge": "absent"}
if state_path == "unavailable":
return {"allowed": False, "bridge": "unavailable"}
try:
fields = Path(state_path).read_text(encoding="ascii").split()
if len(fields) != 2 or fields[0] != "1":
return False
if len(fields) != 2 or fields[0] not in {"0", "1"}:
return {"allowed": False, "bridge": "invalid"}
published_at = float(fields[1])
except (OSError, UnicodeError, ValueError):
return False
return {"allowed": False, "bridge": "unreadable"}
observed_at = time.monotonic() if now is None else now
age = observed_at - published_at
return 0.0 <= age <= GAMESCOPE_HOST_FOCUS_STATE_MAX_AGE_SECONDS
age_ms = round(age * 1000)
if age < 0.0:
return {"allowed": False, "bridge": "future", "age_ms": age_ms}
if age > GAMESCOPE_HOST_FOCUS_STATE_MAX_AGE_SECONDS:
return {"allowed": False, "bridge": "stale", "age_ms": age_ms}
active = fields[0] == "1"
return {
"allowed": active,
"bridge": "active" if active else "inactive",
"age_ms": age_ms,
}
def _resolve_gamescope_game_window(
@@ -1485,6 +1759,7 @@ def _resolve_gamescope_game_window(
*,
window_title: str,
excluded_window_ids: Collection[int],
diagnostics: WindowDiagnosticRecorder | None = None,
) -> int | None:
"""Return a valid nested client across Wine's login-window transition."""
@@ -1492,6 +1767,14 @@ def _resolve_gamescope_game_window(
state = mapper.window_state(window)
except Exception:
state = None
if diagnostics is not None:
diagnostics.transition(
"nested_window_state",
(window, state),
"nested_window_state_changed",
window_id=window,
wm_state=_wm_state_name(state),
)
if state == _X_WM_STATE_ICONIC:
# IconicState can be a real desktop minimize request. Monitoring must never
# turn that user-controlled state back into a visible window.
@@ -1510,6 +1793,13 @@ def _resolve_gamescope_game_window(
return None
replacement, title = found
_apply_game_window_identity(mapper, replacement)
if diagnostics is not None:
diagnostics.record(
"nested_window_replaced",
previous_window_id=window,
window_id=replacement,
**_sample_window_diagnostic_details(diagnostics, mapper, replacement),
)
print(
f"Game login window changed; following it inside Gamescope: {title}",
file=sys.stderr,
@@ -1518,12 +1808,24 @@ def _resolve_gamescope_game_window(
return replacement
def _enable_game_window_scaling(mapper: _X11, window: int) -> bool:
def _enable_game_window_scaling(
mapper: _X11,
window: int,
*,
diagnostics: WindowDiagnosticRecorder | None = None,
) -> bool:
"""Enable confirmed resize/maximize actions without risking game startup."""
try:
enabled = mapper.enable_window_scaling(window)
except Exception as exc:
if diagnostics is not None:
diagnostics.record(
"scaling_configuration_failed",
window_id=window,
error_type=type(exc).__name__,
**_sample_window_diagnostic_details(diagnostics, mapper, window),
)
print(
f"Warning: could not enable game-window scaling ({exc}); "
"the fixed-size window will continue normally",
@@ -1531,6 +1833,13 @@ def _enable_game_window_scaling(mapper: _X11, window: int) -> bool:
flush=True,
)
return False
if diagnostics is not None:
diagnostics.record(
"scaling_configured",
window_id=window,
enabled=enabled,
**_sample_window_diagnostic_details(diagnostics, mapper, window),
)
if enabled:
print(
"Game window is resizable with persistent maximize support.",
@@ -1551,12 +1860,20 @@ def _maintain_game_window_scaling(
mapper: _X11,
process: subprocess.Popen[bytes],
window: int,
*,
diagnostics: WindowDiagnosticRecorder | None = None,
) -> int:
"""Keep host scaling hints across Wine maximize/restore transitions."""
try:
mapper.select_scaling_hint_events(window)
except Exception as exc:
if diagnostics is not None:
diagnostics.record(
"scaling_monitor_failed",
window_id=window,
error_type=type(exc).__name__,
)
print(
f"Warning: could not monitor game-window scaling hints ({exc}); "
"leaving the game window fixed-size for consistency",
@@ -1565,7 +1882,16 @@ def _maintain_game_window_scaling(
)
return int(process.wait())
if not _enable_game_window_scaling(mapper, window):
scaling_enabled = (
_enable_game_window_scaling(mapper, window)
if diagnostics is None
else _enable_game_window_scaling(
mapper,
window,
diagnostics=diagnostics,
)
)
if not scaling_enabled:
return int(process.wait())
repair_failures = 0
@@ -1584,7 +1910,14 @@ def _maintain_game_window_scaling(
break
now = time.monotonic()
if now >= next_focus_repair_at:
_repair_game_window_focus(mapper, window)
if diagnostics is None:
_repair_game_window_focus(mapper, window)
else:
_repair_game_window_focus(
mapper,
window,
diagnostics=diagnostics,
)
next_focus_repair_at = time.monotonic() + WINDOW_FOCUS_REPAIR_INTERVAL_SECONDS
if not hints_changed and now < next_scaling_repair_at:
continue
@@ -1595,6 +1928,13 @@ def _maintain_game_window_scaling(
)
except Exception as exc:
repair_failures += 1
if diagnostics is not None:
diagnostics.record(
"scaling_maintenance_failed",
window_id=window,
error_type=type(exc).__name__,
repair_failures=repair_failures,
)
print(
f"Warning: could not restore game-window scaling hints ({exc})",
file=sys.stderr,
@@ -1610,6 +1950,12 @@ def _maintain_game_window_scaling(
break
else:
repair_failures = 0
if hints_changed and diagnostics is not None:
diagnostics.record(
"scaling_hints_repaired",
window_id=window,
**_sample_window_diagnostic_details(diagnostics, mapper, window),
)
next_scaling_repair_at = time.monotonic() + WINDOW_SCALING_REPAIR_INTERVAL_SECONDS
return int(process.wait())
@@ -1618,6 +1964,8 @@ def _maintain_game_window_focus(
mapper: _X11,
process: subprocess.Popen[bytes],
window: int,
*,
diagnostics: WindowDiagnosticRecorder | None = None,
) -> int:
"""Keep the keyboard-focus repair while retaining Wine's fixed-size hints."""
@@ -1633,7 +1981,14 @@ def _maintain_game_window_focus(
now = time.monotonic()
if now < next_focus_repair_at:
continue
_repair_game_window_focus(mapper, window)
if diagnostics is None:
_repair_game_window_focus(mapper, window)
else:
_repair_game_window_focus(
mapper,
window,
diagnostics=diagnostics,
)
next_focus_repair_at = time.monotonic() + WINDOW_FOCUS_REPAIR_INTERVAL_SECONDS
return int(process.wait())
@@ -1645,6 +2000,7 @@ def _maintain_gamescope_game_window_focus(
*,
window_title: str = WINDOW_TITLE,
existing_window_ids: Collection[int] = (),
diagnostics: WindowDiagnosticRecorder | None = None,
) -> int:
"""Keep a valid, focused nested client through login and host resizing."""
@@ -1658,12 +2014,21 @@ def _maintain_gamescope_game_window_focus(
now = time.monotonic()
if now < next_focus_repair_at:
continue
resolved_window = _resolve_gamescope_game_window(
mapper,
active_window,
window_title=window_title,
excluded_window_ids=retired_window_ids,
)
if diagnostics is None:
resolved_window = _resolve_gamescope_game_window(
mapper,
active_window,
window_title=window_title,
excluded_window_ids=retired_window_ids,
)
else:
resolved_window = _resolve_gamescope_game_window(
mapper,
active_window,
window_title=window_title,
excluded_window_ids=retired_window_ids,
diagnostics=diagnostics,
)
if resolved_window is None:
next_focus_repair_at = (
time.monotonic() + GAMESCOPE_FOCUS_REPAIR_INTERVAL_SECONDS
@@ -1673,7 +2038,14 @@ def _maintain_gamescope_game_window_focus(
if resolved_window_id != active_window:
retired_window_ids.add(active_window)
active_window = resolved_window_id
_repair_gamescope_game_window_focus(mapper, active_window)
if diagnostics is None:
_repair_gamescope_game_window_focus(mapper, active_window)
else:
_repair_gamescope_game_window_focus(
mapper,
active_window,
diagnostics=diagnostics,
)
next_focus_repair_at = (
time.monotonic() + GAMESCOPE_FOCUS_REPAIR_INTERVAL_SECONDS
)
@@ -1686,6 +2058,7 @@ def _guard_game_window_startup(
window: int,
*,
borderless: bool,
diagnostics: WindowDiagnosticRecorder | None = None,
) -> bool:
"""Recover Wine's login-screen window transition without hiding the client."""
@@ -1698,6 +2071,12 @@ def _guard_game_window_startup(
# initial preparation immediately before this guard requested borderless.
borderless_was_applied = borderless and initial_borderless is not None
except Exception as exc:
if diagnostics is not None:
diagnostics.record(
"startup_monitor_failed",
window_id=window,
error_type=type(exc).__name__,
)
print(
f"Warning: could not monitor the game startup window ({exc}); "
"continuing normally",
@@ -1705,6 +2084,13 @@ def _guard_game_window_startup(
flush=True,
)
return False
if diagnostics is not None:
diagnostics.record(
"startup_guard_started",
window_id=window,
borderless_requested=borderless,
**_sample_window_diagnostic_details(diagnostics, mapper, window),
)
recovery_count = 0
while process.poll() is None:
if time.monotonic() >= deadline:
@@ -1716,6 +2102,12 @@ def _guard_game_window_startup(
mapper.window_is_borderless(window) if borderless else None
)
except Exception as exc:
if diagnostics is not None:
diagnostics.record(
"startup_monitor_failed",
window_id=window,
error_type=type(exc).__name__,
)
print(
f"Warning: could not inspect the game startup window ({exc}); "
"continuing normally",
@@ -1726,6 +2118,16 @@ def _guard_game_window_startup(
became_iconic = state == _X_WM_STATE_ICONIC
entered_screen_filling_mode = screen_filling is True
login_style_reset = borderless_was_applied and borderless_now is False
if diagnostics is not None:
diagnostics.transition(
"startup_window_state",
(state, screen_filling, borderless_now),
"startup_state_changed",
window_id=window,
wm_state=_wm_state_name(state),
screen_filling=screen_filling,
borderless=borderless_now,
)
if became_iconic:
print(
"Game startup window was minimized; leaving it minimized.",
@@ -1750,6 +2152,14 @@ def _guard_game_window_startup(
file=sys.stderr,
flush=True,
)
if diagnostics is not None:
diagnostics.record(
"startup_recovery_requested",
window_id=window,
reason=reason,
recovery_count=recovery_count,
**_sample_window_diagnostic_details(diagnostics, mapper, window),
)
try:
mapper.activate_window(window)
time.sleep(WINDOW_ACTIVATION_DELAY_SECONDS)
@@ -1766,6 +2176,13 @@ def _guard_game_window_startup(
mapper.window_is_borderless(window) is True
)
except Exception as exc:
if diagnostics is not None:
diagnostics.record(
"startup_recovery_failed",
window_id=window,
reason=reason,
error_type=type(exc).__name__,
)
print(
f"Warning: could not recover the game startup window ({exc}); "
"continuing normally",
@@ -1773,6 +2190,13 @@ def _guard_game_window_startup(
flush=True,
)
return False
if diagnostics is not None:
diagnostics.record(
"startup_recovery_completed",
window_id=window,
reason=reason,
**_sample_window_diagnostic_details(diagnostics, mapper, window),
)
if recovery_count >= WINDOW_STARTUP_RECOVERY_LIMIT:
print(
"Warning: the game repeatedly changed its startup window; "
@@ -1796,6 +2220,16 @@ def run_game_window(
) -> int:
if not command:
raise GameboxRuntimeError("game command cannot be empty")
diagnostics = WindowDiagnosticRecorder("game_window")
diagnostics.record(
"runtime_configured",
borderless=borderless,
allow_window_resizing=allow_window_resizing,
gamescope_scaling=gamescope_scaling,
suppress_wm_ping=suppress_wm_ping,
display_available=bool(os.environ.get("DISPLAY")),
)
diagnostic_sink = diagnostics if diagnostics.enabled else None
mapper: _X11 | None = None
existing_window_ids: set[int] = set()
try:
@@ -1803,19 +2237,32 @@ def run_game_window(
existing_window_ids = created_mapper.root_window_ids()
mapper = created_mapper
except GameboxRuntimeError as error:
diagnostics.record(
"x11_unavailable",
error_type=type(error).__name__,
)
print(
f"Warning: {error}; continuing without X11 window styling",
file=sys.stderr,
flush=True,
)
process = subprocess.Popen([str(value) for value in command])
try:
process = subprocess.Popen([str(value) for value in command])
except BaseException:
if mapper is not None:
mapper.close()
diagnostics.close(process_started=False, managed=mapper is not None)
raise
if mapper is None:
print(
f"Game window ready: unmanaged {window_title}",
file=sys.stderr,
flush=True,
)
return int(process.wait())
try:
return int(process.wait())
finally:
diagnostics.close(process_started=True, managed=False)
game_window: int | None = None
game_title: str | None = None
game_window_created_at: float | None = None
@@ -1834,6 +2281,16 @@ def run_game_window(
game_title = title
game_window_created_at = now
_apply_game_window_identity(mapper, window)
if diagnostic_sink is not None:
diagnostic_sink.record(
"game_window_discovered",
window_id=window,
**_sample_window_diagnostic_details(
diagnostic_sink,
mapper,
window,
),
)
print(
f"Game window created; preparing it: {title}",
file=sys.stderr,
@@ -1853,6 +2310,18 @@ def run_game_window(
file=sys.stderr,
flush=True,
)
if diagnostic_sink is not None:
diagnostic_sink.record(
"game_window_ready",
window_id=game_window,
gamescope_scaling=True,
host_focus=_gamescope_host_focus_status(),
**_sample_window_diagnostic_details(
diagnostic_sink,
mapper,
game_window,
),
)
if _gamescope_host_focus_is_allowed():
_focus_game_window(mapper, game_window)
print(
@@ -1867,6 +2336,11 @@ def run_game_window(
game_window,
window_title=window_title,
existing_window_ids=existing_window_ids,
**(
{"diagnostics": diagnostic_sink}
if diagnostic_sink is not None
else {}
),
)
_prepare_game_window(
mapper,
@@ -1879,12 +2353,28 @@ def run_game_window(
file=sys.stderr,
flush=True,
)
if diagnostic_sink is not None:
diagnostic_sink.record(
"game_window_ready",
window_id=game_window,
gamescope_scaling=False,
**_sample_window_diagnostic_details(
diagnostic_sink,
mapper,
game_window,
),
)
_focus_game_window(mapper, game_window)
startup_stable = _guard_game_window_startup(
mapper,
process,
game_window,
borderless=borderless,
**(
{"diagnostics": diagnostic_sink}
if diagnostic_sink is not None
else {}
),
)
if (
allow_window_resizing
@@ -1896,6 +2386,11 @@ def run_game_window(
mapper,
process,
game_window,
**(
{"diagnostics": diagnostic_sink}
if diagnostic_sink is not None
else {}
),
)
if not borderless:
if allow_window_resizing and not startup_stable:
@@ -1909,6 +2404,11 @@ def run_game_window(
mapper,
process,
game_window,
**(
{"diagnostics": diagnostic_sink}
if diagnostic_sink is not None
else {}
),
)
return int(process.wait())
time.sleep(0.1)
@@ -1927,6 +2427,12 @@ def run_game_window(
)
return int(process.wait())
finally:
diagnostics.close(
process_started=True,
managed=True,
window_discovered=game_window is not None,
gamescope_scaling=gamescope_scaling,
)
mapper.close()