Files
kefu/wechat_rpa/.venv/Lib/site-packages/mss/linux/xcb.py
T
2026-09-21 10:34:06 +08:00

393 lines
12 KiB
Python

from __future__ import annotations
import contextlib
from ctypes import _Pointer, addressof, c_int
from typing import Literal, overload
from mss.linux import xcbgen
# We import these just so they're re-exported to our users.
# ruff: noqa: F401
from mss.linux.xcbgen import (
RANDR_MAJOR_VERSION,
RANDR_MINOR_VERSION,
RENDER_MAJOR_VERSION,
RENDER_MINOR_VERSION,
SHM_MAJOR_VERSION,
SHM_MINOR_VERSION,
XFIXES_MAJOR_VERSION,
XFIXES_MINOR_VERSION,
Atom,
BackingStore,
Colormap,
Depth,
DepthIterator,
Drawable,
Format,
GetGeometryReply,
GetImageReply,
GetPropertyReply,
ImageFormat,
ImageOrder,
Keycode,
Pixmap,
RandrConnection,
RandrCrtc,
RandrGetCrtcInfoReply,
RandrGetMonitorsReply,
RandrGetOutputInfoReply,
RandrGetOutputPrimaryReply,
RandrGetOutputPropertyReply,
RandrGetScreenResourcesCurrentReply,
RandrGetScreenResourcesReply,
RandrMode,
RandrModeInfo,
RandrMonitorInfo,
RandrMonitorInfoIterator,
RandrOutput,
RandrQueryVersionReply,
RandrSetConfig,
RenderDirectformat,
RenderPictdepth,
RenderPictdepthIterator,
RenderPictformat,
RenderPictforminfo,
RenderPictscreen,
RenderPictscreenIterator,
RenderPictType,
RenderPictvisual,
RenderQueryPictFormatsReply,
RenderQueryVersionReply,
RenderSubPixel,
Screen,
ScreenIterator,
Setup,
SetupIterator,
ShmCreateSegmentReply,
ShmGetImageReply,
ShmQueryVersionReply,
ShmSeg,
Timestamp,
VisualClass,
Visualid,
Visualtype,
Window,
XfixesGetCursorImageReply,
XfixesQueryVersionReply,
depth_visuals,
get_geometry,
get_image,
get_image_data,
get_property,
get_property_value,
no_operation,
randr_get_crtc_info,
randr_get_crtc_info_outputs,
randr_get_crtc_info_possible,
randr_get_monitors,
randr_get_monitors_monitors,
randr_get_output_info,
randr_get_output_info_clones,
randr_get_output_info_crtcs,
randr_get_output_info_modes,
randr_get_output_info_name,
randr_get_output_primary,
randr_get_output_property,
randr_get_output_property_data,
randr_get_screen_resources,
randr_get_screen_resources_crtcs,
randr_get_screen_resources_current,
randr_get_screen_resources_current_crtcs,
randr_get_screen_resources_current_modes,
randr_get_screen_resources_current_names,
randr_get_screen_resources_current_outputs,
randr_get_screen_resources_modes,
randr_get_screen_resources_names,
randr_get_screen_resources_outputs,
randr_monitor_info_outputs,
randr_query_version,
render_pictdepth_visuals,
render_pictscreen_depths,
render_query_pict_formats,
render_query_pict_formats_formats,
render_query_pict_formats_screens,
render_query_pict_formats_subpixels,
render_query_version,
screen_allowed_depths,
setup_pixmap_formats,
setup_roots,
setup_vendor,
shm_attach_fd,
shm_create_segment,
shm_create_segment_reply_fds,
shm_detach,
shm_get_image,
shm_query_version,
xfixes_get_cursor_image,
xfixes_get_cursor_image_cursor_image,
xfixes_query_version,
)
# These are also here to re-export.
from mss.linux.xcbhelpers import LIB, XID, Connection, InternAtomReply, QueryExtensionReply, XcbExtension, XError
XCB_CONN_ERROR = 1
XCB_CONN_CLOSED_EXT_NOTSUPPORTED = 2
XCB_CONN_CLOSED_MEM_INSUFFICIENT = 3
XCB_CONN_CLOSED_REQ_LEN_EXCEED = 4
XCB_CONN_CLOSED_PARSE_ERR = 5
XCB_CONN_CLOSED_INVALID_SCREEN = 6
XCB_CONN_CLOSED_FDPASSING_FAILED = 7
# I don't know of error descriptions for the XCB connection errors being accessible through a library (a la strerror),
# and the ones in xcb.h's comments aren't too great, so I wrote these.
XCB_CONN_ERRMSG = {
XCB_CONN_ERROR: "connection lost or could not be established",
XCB_CONN_CLOSED_EXT_NOTSUPPORTED: "extension not supported",
XCB_CONN_CLOSED_MEM_INSUFFICIENT: "memory exhausted",
XCB_CONN_CLOSED_REQ_LEN_EXCEED: "request length longer than server accepts",
XCB_CONN_CLOSED_PARSE_ERR: "display is unset or invalid (check $DISPLAY)",
XCB_CONN_CLOSED_INVALID_SCREEN: "server does not have a screen matching the requested display",
XCB_CONN_CLOSED_FDPASSING_FAILED: "could not pass file descriptor",
}
#### High-level XCB function wrappers
# XCB_NONE is the universal null resource or null atom parameter value for many core X requests
XCB_NONE = Atom(0)
# Some atoms are defined by the spec, to avoid apps having to look them up. It's fine to look them up anyway.
_PREDEFINED_ATOMS = {
"PRIMARY": Atom(1),
"SECONDARY": Atom(2),
"ARC": Atom(3),
"ATOM": Atom(4),
"BITMAP": Atom(5),
"CARDINAL": Atom(6),
"COLORMAP": Atom(7),
"CURSOR": Atom(8),
"CUT_BUFFER0": Atom(9),
"CUT_BUFFER1": Atom(10),
"CUT_BUFFER2": Atom(11),
"CUT_BUFFER3": Atom(12),
"CUT_BUFFER4": Atom(13),
"CUT_BUFFER5": Atom(14),
"CUT_BUFFER6": Atom(15),
"CUT_BUFFER7": Atom(16),
"DRAWABLE": Atom(17),
"FONT": Atom(18),
"INTEGER": Atom(19),
"PIXMAP": Atom(20),
"POINT": Atom(21),
"RECTANGLE": Atom(22),
"RESOURCE_MANAGER": Atom(23),
"RGB_COLOR_MAP": Atom(24),
"RGB_BEST_MAP": Atom(25),
"RGB_BLUE_MAP": Atom(26),
"RGB_DEFAULT_MAP": Atom(27),
"RGB_GRAY_MAP": Atom(28),
"RGB_GREEN_MAP": Atom(29),
"RGB_RED_MAP": Atom(30),
"STRING": Atom(31),
"VISUALID": Atom(32),
"WINDOW": Atom(33),
"WM_COMMAND": Atom(34),
"WM_HINTS": Atom(35),
"WM_CLIENT_MACHINE": Atom(36),
"WM_ICON_NAME": Atom(37),
"WM_ICON_SIZE": Atom(38),
"WM_NAME": Atom(39),
"WM_NORMAL_HINTS": Atom(40),
"WM_SIZE_HINTS": Atom(41),
"WM_ZOOM_HINTS": Atom(42),
"MIN_SPACE": Atom(43),
"NORM_SPACE": Atom(44),
"MAX_SPACE": Atom(45),
"END_SPACE": Atom(46),
"SUPERSCRIPT_X": Atom(47),
"SUPERSCRIPT_Y": Atom(48),
"SUBSCRIPT_X": Atom(49),
"SUBSCRIPT_Y": Atom(50),
"UNDERLINE_POSITION": Atom(51),
"UNDERLINE_THICKNESS": Atom(52),
"STRIKEOUT_ASCENT": Atom(53),
"STRIKEOUT_DESCENT": Atom(54),
"ITALIC_ANGLE": Atom(55),
"X_HEIGHT": Atom(56),
"QUAD_WIDTH": Atom(57),
"WEIGHT": Atom(58),
"POINT_SIZE": Atom(59),
"RESOLUTION": Atom(60),
"COPYRIGHT": Atom(61),
"NOTICE": Atom(62),
"FONT_NAME": Atom(63),
"FAMILY_NAME": Atom(64),
"FULL_NAME": Atom(65),
"CAP_HEIGHT": Atom(66),
"WM_CLASS": Atom(67),
"WM_TRANSIENT_FOR": Atom(68),
}
# The atom cache needs to be per-connection. Rather than keying on a (connection, name) tuple, we use a two-level
# cache keyed by the integer address of the underlying XCB connection (see ctypes.addressof in intern_atom).
_ATOM_CACHE: dict[int, dict[str, Atom]] = {}
@overload
def intern_atom(
xcb_conn: Connection | _Pointer[Connection],
name: str,
*,
only_if_exists: Literal[False] = False,
) -> Atom: ...
@overload
def intern_atom(
xcb_conn: Connection | _Pointer[Connection],
name: str,
*,
only_if_exists: Literal[True] = True,
) -> Atom | None: ...
@overload
def intern_atom(
xcb_conn: Connection | _Pointer[Connection],
name: str,
*,
only_if_exists: bool,
) -> Atom | None: ...
def intern_atom(
xcb_conn: Connection | _Pointer[Connection],
name: str,
*,
only_if_exists: bool = False,
) -> Atom | None:
if name in _PREDEFINED_ATOMS:
return _PREDEFINED_ATOMS[name]
if isinstance(xcb_conn, _Pointer):
# Dereference the pointer before using the cache.
xcb_conn = xcb_conn.contents
cache_key = addressof(xcb_conn)
if cache_key not in _ATOM_CACHE:
# This can happen if the connection was closed and its cache cleared, but some code still has a reference to
# the connection object. We could re-create the cache entry, but it's safer to just fail instead of silently
# allowing lookups to succeed when they shouldn't.
msg = "Connection to X server is closed"
raise XError(msg)
if name in _ATOM_CACHE[cache_key]:
return _ATOM_CACHE[cache_key][name]
# Atom names are required to be Latin-1, per the X protocol spec, although anything that's not in the XPCS (a
# subset of ASCII) is vendor-defined.
name_encoded = name.encode("latin_1", errors="strict")
cookie = LIB.xcb.xcb_intern_atom(xcb_conn, 1 if only_if_exists else 0, len(name_encoded), name_encoded)
atom_as_xid = cookie.reply(xcb_conn).atom
if atom_as_xid.value == 0:
if not only_if_exists:
# This shouldn't be possible. We at least need to have a path for the type-checker to be happy, though.
msg = f"X server failed to intern atom '{name}'"
raise XError(msg)
# We don't do negative caching, since any app might intern the atom at any time.
return None
atom = Atom(atom_as_xid.value)
_ATOM_CACHE[cache_key][name] = atom
return atom
def get_extension_data(
xcb_conn: Connection | _Pointer[Connection], ext: XcbExtension | _Pointer[XcbExtension]
) -> QueryExtensionReply:
"""Get extension data for the given extension.
Returns the extension data, which includes whether the extension is present
and its opcode information.
"""
reply_p = LIB.xcb.xcb_get_extension_data(xcb_conn, ext)
return reply_p.contents
def prefetch_extension_data(
xcb_conn: Connection | _Pointer[Connection], ext: XcbExtension | _Pointer[XcbExtension]
) -> None:
"""Prefetch extension data for the given extension.
This is a performance hint to XCB to fetch the extension data
asynchronously.
"""
LIB.xcb.xcb_prefetch_extension_data(xcb_conn, ext)
def generate_id(xcb_conn: Connection | _Pointer[Connection]) -> XID:
"""Generate a new unique X resource ID.
Returns an XID that can be used to create new X resources.
"""
return LIB.xcb.xcb_generate_id(xcb_conn)
def get_setup(xcb_conn: Connection | _Pointer[Connection]) -> Setup:
"""Get the connection setup information.
Returns the setup structure containing information about the X server,
including available screens, pixmap formats, etc.
"""
setup_p = LIB.xcb.xcb_get_setup(xcb_conn)
return setup_p.contents
# Connection management
def initialize() -> None:
LIB.initialize(callbacks=[xcbgen.initialize])
def connect(display: str | bytes | None = None) -> tuple[Connection, int]:
if isinstance(display, str):
display = display.encode("utf-8")
initialize()
pref_screen_num = c_int()
conn_p = LIB.xcb.xcb_connect(display, pref_screen_num)
# We still get a connection object even if the connection fails.
conn_err = LIB.xcb.xcb_connection_has_error(conn_p)
if conn_err != 0:
# XCB won't free its connection structures until we disconnect, even in the event of an error.
LIB.xcb.xcb_disconnect(conn_p)
msg = "Cannot connect to display: "
conn_errmsg = XCB_CONN_ERRMSG.get(conn_err)
msg += conn_errmsg or f"error code {conn_err}"
raise XError(msg)
# Prefetch extension data for all extensions we support to populate XCB's internal cache.
prefetch_extension_data(conn_p, LIB.randr_id)
prefetch_extension_data(conn_p, LIB.render_id)
prefetch_extension_data(conn_p, LIB.shm_id)
prefetch_extension_data(conn_p, LIB.xfixes_id)
_ATOM_CACHE[addressof(conn_p.contents)] = {}
return conn_p.contents, pref_screen_num.value
def disconnect(xcb_conn: Connection | _Pointer[Connection]) -> None:
if isinstance(xcb_conn, _Pointer):
# Dereference the pointer before using the cache.
xcb_conn = xcb_conn.contents
# The cache might already be cleared if the connection had an error, or if disconnect was called multiple times.
with contextlib.suppress(KeyError):
del _ATOM_CACHE[addressof(xcb_conn)]
conn_err = LIB.xcb.xcb_connection_has_error(xcb_conn)
# XCB won't free its connection structures until we disconnect, even in the event of an error.
LIB.xcb.xcb_disconnect(xcb_conn)
if conn_err != 0:
msg = "Connection to X server closed: "
conn_errmsg = XCB_CONN_ERRMSG.get(conn_err)
msg += conn_errmsg or f"error code {conn_err}"
raise XError(msg)