Test files and initial data gathering completed
This commit is contained in:
+86
@@ -0,0 +1,86 @@
|
||||
"""X11 window capture for Eternal Lands analysis.
|
||||
|
||||
Captures a specific window's pixels even when it is not focused. On X11,
|
||||
focus only affects input routing, never pixel retrieval: XGetImage reads the
|
||||
window's own pixmap. Cinnamon's compositor keeps the redirected content
|
||||
available, so this works regardless of focus or occlusion.
|
||||
|
||||
If window-level capture is unavailable (e.g. compositing is disabled), we
|
||||
fall back to grabbing the visible region from the root window at the
|
||||
window's screen coordinates.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from Xlib import X, display
|
||||
from Xlib.error import XError
|
||||
|
||||
|
||||
class WindowNotFound(Exception):
|
||||
"""Raised when no window matching the title pattern is found."""
|
||||
|
||||
|
||||
class WindowCapture:
|
||||
def __init__(self, title_pattern, display_name=None):
|
||||
self.title_pattern = re.compile(title_pattern, re.IGNORECASE)
|
||||
self.display = display.Display(display_name)
|
||||
self.root = self.display.screen().root
|
||||
self.window = None
|
||||
|
||||
def find_window(self):
|
||||
"""Return the cached or freshly-searched X window for the title."""
|
||||
if self.window is not None:
|
||||
try:
|
||||
self.window.get_geometry()
|
||||
return self.window
|
||||
except XError:
|
||||
self.window = None
|
||||
self.window = self._search(self.root)
|
||||
return self.window
|
||||
|
||||
def _search(self, window, depth=0):
|
||||
if depth > 24:
|
||||
return None
|
||||
try:
|
||||
name = window.get_wm_name()
|
||||
except XError:
|
||||
name = None
|
||||
if name and self.title_pattern.search(name):
|
||||
try:
|
||||
window.get_geometry()
|
||||
return window
|
||||
except XError:
|
||||
return None
|
||||
try:
|
||||
children = window.query_tree().children
|
||||
except XError:
|
||||
return None
|
||||
for child in children:
|
||||
found = self._search(child, depth + 1)
|
||||
if found:
|
||||
return found
|
||||
return None
|
||||
|
||||
def capture(self):
|
||||
"""Capture the target window and return a PIL.Image."""
|
||||
window = self.find_window()
|
||||
if window is None:
|
||||
raise WindowNotFound(
|
||||
f"no window matching {self.title_pattern.pattern!r} "
|
||||
f"on display {self.display.display_string}"
|
||||
)
|
||||
geo = window.get_geometry()
|
||||
try:
|
||||
ximage = window.get_image(0, 0, geo.width, geo.height, X.ZPixmap, 0xFFFFFFFF)
|
||||
except XError:
|
||||
coords = self.root.translate_coords(window, 0, 0)
|
||||
ximage = self.root.get_image(
|
||||
coords.x, coords.y, geo.width, geo.height, X.ZPixmap, 0xFFFFFFFF
|
||||
)
|
||||
return _to_image(ximage, geo.width, geo.height)
|
||||
|
||||
|
||||
def _to_image(ximage, width, height):
|
||||
return Image.frombytes("RGBA", (width, height), ximage.data, "raw", "BGRA")
|
||||
Reference in New Issue
Block a user