Hotfixed a issue with resolution in borderless mode.
This commit is contained in:
@@ -19,6 +19,8 @@ from .desktop_identity import (
|
|||||||
WINDOW_TITLE = APPLICATION_NAME
|
WINDOW_TITLE = APPLICATION_NAME
|
||||||
WINDOW_DISCOVERY_TIMEOUT_SECONDS = 60.0
|
WINDOW_DISCOVERY_TIMEOUT_SECONDS = 60.0
|
||||||
WINDOW_READY_DELAY_SECONDS = 0.5
|
WINDOW_READY_DELAY_SECONDS = 0.5
|
||||||
|
WINDOW_RESTORE_DELAY_SECONDS = 0.15
|
||||||
|
WINDOW_REMAP_DELAY_SECONDS = 0.05
|
||||||
_X_ERROR_HANDLER = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p)
|
_X_ERROR_HANDLER = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p)
|
||||||
|
|
||||||
|
|
||||||
@@ -117,17 +119,24 @@ class _X11:
|
|||||||
title = self._window_title(window)
|
title = self._window_title(window)
|
||||||
if title is not None:
|
if title is not None:
|
||||||
windows.append((window, title))
|
windows.append((window, title))
|
||||||
return _select_game_window(windows, title_fragment, excluded_window_ids)
|
selected = _select_game_window(windows, title_fragment, excluded_window_ids)
|
||||||
|
if selected is None:
|
||||||
|
return None
|
||||||
|
window, title = selected
|
||||||
|
return self._client_window(window), title
|
||||||
|
|
||||||
def _root_windows(self) -> list[int]:
|
def _root_windows(self) -> list[int]:
|
||||||
root = self.library.XDefaultRootWindow(self.display)
|
root = self.library.XDefaultRootWindow(self.display)
|
||||||
|
return self._child_windows(root)
|
||||||
|
|
||||||
|
def _child_windows(self, window: int) -> list[int]:
|
||||||
root_return = ctypes.c_ulong()
|
root_return = ctypes.c_ulong()
|
||||||
parent_return = ctypes.c_ulong()
|
parent_return = ctypes.c_ulong()
|
||||||
children = ctypes.POINTER(ctypes.c_ulong)()
|
children = ctypes.POINTER(ctypes.c_ulong)()
|
||||||
child_count = ctypes.c_uint()
|
child_count = ctypes.c_uint()
|
||||||
status = self.library.XQueryTree(
|
status = self.library.XQueryTree(
|
||||||
self.display,
|
self.display,
|
||||||
root,
|
window,
|
||||||
ctypes.byref(root_return),
|
ctypes.byref(root_return),
|
||||||
ctypes.byref(parent_return),
|
ctypes.byref(parent_return),
|
||||||
ctypes.byref(children),
|
ctypes.byref(children),
|
||||||
@@ -141,12 +150,86 @@ class _X11:
|
|||||||
if children:
|
if children:
|
||||||
self.library.XFree(ctypes.cast(children, ctypes.c_void_p))
|
self.library.XFree(ctypes.cast(children, ctypes.c_void_p))
|
||||||
|
|
||||||
|
def _client_window(self, window: int) -> int:
|
||||||
|
"""Resolve a window-manager frame to the ICCCM client it contains."""
|
||||||
|
|
||||||
|
queue = [window]
|
||||||
|
visited: set[int] = set()
|
||||||
|
while queue and len(visited) < 64:
|
||||||
|
candidate = queue.pop(0)
|
||||||
|
if candidate in visited:
|
||||||
|
continue
|
||||||
|
visited.add(candidate)
|
||||||
|
if self._has_wm_state(candidate):
|
||||||
|
return candidate
|
||||||
|
queue.extend(self._child_windows(candidate))
|
||||||
|
return window
|
||||||
|
|
||||||
|
def _has_wm_state(self, window: int) -> bool:
|
||||||
|
wm_state = self.library.XInternAtom(self.display, b"WM_STATE", 1)
|
||||||
|
if wm_state == 0:
|
||||||
|
return False
|
||||||
|
actual_type = ctypes.c_ulong()
|
||||||
|
actual_format = ctypes.c_int()
|
||||||
|
item_count = ctypes.c_ulong()
|
||||||
|
bytes_after = ctypes.c_ulong()
|
||||||
|
value = ctypes.POINTER(ctypes.c_ubyte)()
|
||||||
|
status = self.library.XGetWindowProperty(
|
||||||
|
self.display,
|
||||||
|
window,
|
||||||
|
wm_state,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
ctypes.byref(actual_type),
|
||||||
|
ctypes.byref(actual_format),
|
||||||
|
ctypes.byref(item_count),
|
||||||
|
ctypes.byref(bytes_after),
|
||||||
|
ctypes.byref(value),
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
return status == 0 and actual_type.value != 0
|
||||||
|
finally:
|
||||||
|
if value:
|
||||||
|
self.library.XFree(ctypes.cast(value, ctypes.c_void_p))
|
||||||
|
|
||||||
def map_window(self, window: int, *, borderless: bool) -> None:
|
def map_window(self, window: int, *, borderless: bool) -> None:
|
||||||
if borderless:
|
if borderless:
|
||||||
self._remove_decorations(window)
|
self._remove_decorations(window)
|
||||||
self.library.XMapRaised(self.display, window)
|
self.library.XMapRaised(self.display, window)
|
||||||
self.library.XFlush(self.display)
|
self.library.XFlush(self.display)
|
||||||
|
|
||||||
|
def refresh_render_surface(self, window: int) -> bool:
|
||||||
|
"""Recreate the minimize/restore event sequence after borderless styling.
|
||||||
|
|
||||||
|
Some Wine/DXVK combinations retain the host desktop-sized render surface
|
||||||
|
after the top-level game window becomes borderless. A normal user-driven
|
||||||
|
minimize and restore makes Wine reconcile the surface with the real window.
|
||||||
|
Request that same standard X11 lifecycle once without changing geometry.
|
||||||
|
"""
|
||||||
|
|
||||||
|
screen = self.library.XDefaultScreen(self.display)
|
||||||
|
if self.library.XIconifyWindow(self.display, window, screen) == 0:
|
||||||
|
return False
|
||||||
|
self.library.XSync(self.display, 0)
|
||||||
|
try:
|
||||||
|
time.sleep(WINDOW_RESTORE_DELAY_SECONDS)
|
||||||
|
finally:
|
||||||
|
# Mutter keeps an iconified client's own X window mapped beneath an
|
||||||
|
# unmapped frame, so XMapRaised alone cannot deiconify it. An explicit
|
||||||
|
# client unmap/remap generates a fresh MapRequest for the window manager.
|
||||||
|
# Always finish the remap if either short wait is interrupted, so the
|
||||||
|
# game cannot be stranded as a hidden window.
|
||||||
|
self.library.XUnmapWindow(self.display, window)
|
||||||
|
self.library.XSync(self.display, 0)
|
||||||
|
try:
|
||||||
|
time.sleep(WINDOW_REMAP_DELAY_SECONDS)
|
||||||
|
finally:
|
||||||
|
self.library.XMapRaised(self.display, window)
|
||||||
|
self.library.XSync(self.display, 0)
|
||||||
|
return True
|
||||||
|
|
||||||
def set_window_identity(
|
def set_window_identity(
|
||||||
self,
|
self,
|
||||||
window: int,
|
window: int,
|
||||||
@@ -270,6 +353,8 @@ class _X11:
|
|||||||
self.library.XSetErrorHandler.restype = ctypes.c_void_p
|
self.library.XSetErrorHandler.restype = ctypes.c_void_p
|
||||||
self.library.XOpenDisplay.argtypes = [ctypes.c_char_p]
|
self.library.XOpenDisplay.argtypes = [ctypes.c_char_p]
|
||||||
self.library.XOpenDisplay.restype = ctypes.c_void_p
|
self.library.XOpenDisplay.restype = ctypes.c_void_p
|
||||||
|
self.library.XDefaultScreen.argtypes = [ctypes.c_void_p]
|
||||||
|
self.library.XDefaultScreen.restype = ctypes.c_int
|
||||||
self.library.XDefaultRootWindow.argtypes = [ctypes.c_void_p]
|
self.library.XDefaultRootWindow.argtypes = [ctypes.c_void_p]
|
||||||
self.library.XDefaultRootWindow.restype = ctypes.c_ulong
|
self.library.XDefaultRootWindow.restype = ctypes.c_ulong
|
||||||
self.library.XQueryTree.argtypes = [
|
self.library.XQueryTree.argtypes = [
|
||||||
@@ -304,6 +389,21 @@ class _X11:
|
|||||||
ctypes.c_int,
|
ctypes.c_int,
|
||||||
]
|
]
|
||||||
self.library.XChangeProperty.restype = ctypes.c_int
|
self.library.XChangeProperty.restype = ctypes.c_int
|
||||||
|
self.library.XGetWindowProperty.argtypes = [
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.c_ulong,
|
||||||
|
ctypes.c_ulong,
|
||||||
|
ctypes.c_long,
|
||||||
|
ctypes.c_long,
|
||||||
|
ctypes.c_int,
|
||||||
|
ctypes.c_ulong,
|
||||||
|
ctypes.POINTER(ctypes.c_ulong),
|
||||||
|
ctypes.POINTER(ctypes.c_int),
|
||||||
|
ctypes.POINTER(ctypes.c_ulong),
|
||||||
|
ctypes.POINTER(ctypes.c_ulong),
|
||||||
|
ctypes.POINTER(ctypes.POINTER(ctypes.c_ubyte)),
|
||||||
|
]
|
||||||
|
self.library.XGetWindowProperty.restype = ctypes.c_int
|
||||||
self.library.XSetClassHint.argtypes = [
|
self.library.XSetClassHint.argtypes = [
|
||||||
ctypes.c_void_p,
|
ctypes.c_void_p,
|
||||||
ctypes.c_ulong,
|
ctypes.c_ulong,
|
||||||
@@ -326,6 +426,14 @@ class _X11:
|
|||||||
self.library.XSetWMProtocols.restype = ctypes.c_int
|
self.library.XSetWMProtocols.restype = ctypes.c_int
|
||||||
self.library.XMapRaised.argtypes = [ctypes.c_void_p, ctypes.c_ulong]
|
self.library.XMapRaised.argtypes = [ctypes.c_void_p, ctypes.c_ulong]
|
||||||
self.library.XMapRaised.restype = ctypes.c_int
|
self.library.XMapRaised.restype = ctypes.c_int
|
||||||
|
self.library.XUnmapWindow.argtypes = [ctypes.c_void_p, ctypes.c_ulong]
|
||||||
|
self.library.XUnmapWindow.restype = ctypes.c_int
|
||||||
|
self.library.XIconifyWindow.argtypes = [
|
||||||
|
ctypes.c_void_p,
|
||||||
|
ctypes.c_ulong,
|
||||||
|
ctypes.c_int,
|
||||||
|
]
|
||||||
|
self.library.XIconifyWindow.restype = ctypes.c_int
|
||||||
self.library.XSync.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
self.library.XSync.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
||||||
self.library.XSync.restype = ctypes.c_int
|
self.library.XSync.restype = ctypes.c_int
|
||||||
self.library.XFlush.argtypes = [ctypes.c_void_p]
|
self.library.XFlush.argtypes = [ctypes.c_void_p]
|
||||||
@@ -368,6 +476,31 @@ def _prepare_game_window(
|
|||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
mapper.map_window(window, borderless=borderless)
|
mapper.map_window(window, borderless=borderless)
|
||||||
|
if not borderless:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
refreshed = mapper.refresh_render_surface(window)
|
||||||
|
except Exception as exc:
|
||||||
|
print(
|
||||||
|
f"Warning: could not synchronize the borderless game surface ({exc}); "
|
||||||
|
"continuing normally",
|
||||||
|
file=sys.stderr,
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if refreshed:
|
||||||
|
print(
|
||||||
|
"Borderless game surface synchronized with its window.",
|
||||||
|
file=sys.stderr,
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"Warning: the window manager declined the borderless surface "
|
||||||
|
"refresh; continuing normally",
|
||||||
|
file=sys.stderr,
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _apply_game_window_identity(mapper: _X11, window: int) -> None:
|
def _apply_game_window_identity(mapper: _X11, window: int) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user