diff --git a/wlroots/ffi_build.py b/wlroots/ffi_build.py index f5606844..ece5ecb1 100644 --- a/wlroots/ffi_build.py +++ b/wlroots/ffi_build.py @@ -155,6 +155,9 @@ def has_xwayland() -> bool: const float projection[static 9], int x, int y, float alpha); bool wlr_render_texture_with_matrix(struct wlr_renderer *r, struct wlr_texture *texture, const float matrix[static 9], float alpha); +bool wlr_render_subtexture_with_matrix(struct wlr_renderer *r, + struct wlr_texture *texture, const struct wlr_fbox *box, + const float matrix[static 9], float alpha); void wlr_render_rect(struct wlr_renderer *r, const struct wlr_box *box, const float color[static 4], const float projection[static 9]); @@ -180,6 +183,11 @@ def has_xwayland() -> bool: # wlr/render/wlr_texture.h CDEF += """ +struct wlr_texture { + uint32_t width, height; + ...; +}; + struct wlr_texture *wlr_texture_from_pixels(struct wlr_renderer *renderer, uint32_t fmt, uint32_t stride, uint32_t width, uint32_t height, const void *data); @@ -192,6 +200,45 @@ def has_xwayland() -> bool: struct wlr_buffer *buffer); """ +# wlr/render/gles2.h +CDEF += """ +struct wlr_renderer *wlr_gles2_renderer_create_with_drm_fd(int drm_fd); +struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl); + +struct wlr_egl *wlr_gles2_renderer_get_egl(struct wlr_renderer *renderer); +bool wlr_gles2_renderer_check_ext(struct wlr_renderer *renderer, + const char *ext); + +typedef int... GLuint; +typedef int... GLenum; + +GLuint wlr_gles2_renderer_get_current_fbo(struct wlr_renderer *wlr_renderer); +bool wlr_renderer_is_gles2(struct wlr_renderer *wlr_renderer); +bool wlr_texture_is_gles2(struct wlr_texture *texture); + +struct wlr_gles2_texture_attribs { + GLenum target; + GLuint tex; + + bool has_alpha; +}; + +void wlr_gles2_texture_get_attribs(struct wlr_texture *texture, + struct wlr_gles2_texture_attribs *attribs); +""" + +# wlr/render/egl.h +CDEF += """ +typedef void *EGLDisplay; +typedef void *EGLContext; + +struct wlr_egl *wlr_egl_create_with_context(EGLDisplay display, + EGLContext context); + +EGLDisplay wlr_egl_get_display(struct wlr_egl *egl); +EGLContext wlr_egl_get_context(struct wlr_egl *egl); +""" + # types/wlr_box.h CDEF += """ struct wlr_box { @@ -223,9 +270,19 @@ def has_xwayland() -> bool: # types/wlr_buffer.h CDEF += """ struct wlr_buffer { + int width, height; + ...; +}; + +struct wlr_client_buffer { + struct wlr_buffer base; + struct wlr_texture *texture; + struct wlr_buffer *source; ...; }; +struct wlr_client_buffer *wlr_client_buffer_get(struct wlr_buffer *buffer); + void wlr_buffer_drop(struct wlr_buffer *buffer); enum wlr_buffer_data_ptr_access_flag { @@ -493,6 +550,14 @@ def has_xwayland() -> bool: struct pixman_region32 current; ...; }; + +void wlr_damage_ring_get_buffer_damage(struct wlr_damage_ring *ring, + int buffer_age, struct pixman_region32 *damage); + +void wlr_damage_ring_rotate(struct wlr_damage_ring *ring); +void wlr_damage_ring_add_whole(struct wlr_damage_ring *ring); +bool wlr_damage_ring_add_box(struct wlr_damage_ring *ring, + const struct wlr_box *box); """ # types/wlr_data_control_v1.h @@ -1062,7 +1127,34 @@ def has_xwayland() -> bool: struct pixman_box32* pixman_region32_rectangles(struct pixman_region32 *region, int *n_rects); +struct pixman_box32* pixman_region32_extents(struct pixman_region32 *region); + bool pixman_region32_not_empty(struct pixman_region32 *region); + +bool pixman_region32_copy(struct pixman_region32 *dest, + const struct pixman_region32 *source); + +void pixman_region32_translate(struct pixman_region32 *region, int x, int y); + +bool pixman_region32_intersect_rect(struct pixman_region32 *dest, + struct pixman_region32 *source, + int x, int y, unsigned int width, unsigned int height); + +bool pixman_region32_intersect(struct pixman_region32 *new_reg, + const struct pixman_region32 *reg1, const struct pixman_region32 *reg2); + +bool pixman_region32_union(struct pixman_region32 *new_reg, + const struct pixman_region32 *reg1, const struct pixman_region32 *reg2); + +bool pixman_region32_union_rect(struct pixman_region32 *dest, + const struct pixman_region32 *source, int x, int y, int width, int height); + +bool pixman_region32_subtract(struct pixman_region32 *reg_d, + const struct pixman_region32 *reg_m, + const struct pixman_region32 *reg_s); + + +void pixman_region32_clear(struct pixman_region32 *region); """ # types/wlr_output.h @@ -1132,6 +1224,11 @@ def has_xwayland() -> bool: struct wlr_output_cursor *hardware_cursor; int software_cursor_locks; + struct wlr_allocator *allocator; + struct wlr_renderer *renderer; + struct wlr_swapchain *swapchain; + struct wlr_buffer *back_buffer; + struct wl_listener display_destroy; void *data; @@ -1769,6 +1866,9 @@ def has_xwayland() -> bool: struct wlr_scene_tree tree; struct wl_list outputs; struct wlr_presentation *presentation; + + // private... + bool calculate_visibility; ...; }; @@ -1798,6 +1898,13 @@ def has_xwayland() -> bool: wlr_scene_buffer_point_accepts_input_func_t point_accepts_input; struct wlr_scene_output *primary_output; + + // private state... + + struct wlr_texture *texture; + struct wlr_fbox src_box; + int dst_width, dst_height; + enum wl_output_transform transform; ...; }; @@ -2752,8 +2859,17 @@ def has_xwayland() -> bool: # util/region.h CDEF += """ +void wlr_region_scale(struct pixman_region32 *dst, const struct pixman_region32 *src, + float scale); + +void wlr_region_scale_xy(struct pixman_region32 *dst, const struct pixman_region32 *src, + float scale_x, float scale_y); + void wlr_region_transform(struct pixman_region32 *dst, struct pixman_region32 *src, enum wl_output_transform transform, int width, int height); + +void wlr_region_expand(struct pixman_region32 *dst, const struct pixman_region32 *src, + int distance); """ # backend/headless.h @@ -2783,6 +2899,8 @@ def has_xwayland() -> bool: #include #include #include +#include +#include #include #include #include diff --git a/wlroots/renderer.py b/wlroots/renderer.py index 904f5ac2..17984d79 100644 --- a/wlroots/renderer.py +++ b/wlroots/renderer.py @@ -86,6 +86,17 @@ def render_texture_with_matrix( # TODO: get a better exception type raise Exception("Bad render") + def render_subtexture_with_matrix( + self, texture: Texture, box: Ptr, matrix: Matrix, alpha: float + ) -> None: + """Renders the requested subtexture using the provided matrix""" + ret = lib.wlr_render_subtexture_with_matrix( + self._ptr, texture._ptr, box, matrix._ptr, alpha + ) + if not ret: + # TODO: get a better exception type + raise Exception("Bad render") + def render_rect(self, box: Box, color: ColorType, projection: Matrix) -> None: """Renders a solid rectangle in the specified color.""" if not isinstance(color, ffi.CData): diff --git a/wlroots/util/box.py b/wlroots/util/box.py index 250d67c1..b4b82883 100644 --- a/wlroots/util/box.py +++ b/wlroots/util/box.py @@ -3,6 +3,8 @@ from __future__ import annotations +from pywayland.protocol.wayland import WlOutput + from wlroots import ffi, lib @@ -52,6 +54,15 @@ def __init__( def __repr__(self) -> str: return f"Box({self.x}, {self.y}, {self.width}, {self.height})" + def copy(self) -> Box: + return Box(self.x, self.y, self.width, self.height) + + def copy_from(self, other: Box) -> None: + self.x = other.x + self.y = other.y + self.width = other.width + self.height = other.height + def closest_point(self, x: float, y: float) -> tuple[float, float]: xy_ptr = ffi.new("double[2]") lib.wlr_box_closest_point(self._ptr, x, y, xy_ptr, xy_ptr + 1) @@ -59,3 +70,9 @@ def closest_point(self, x: float, y: float) -> tuple[float, float]: def contains_point(self, x: float, y: float) -> bool: return lib.wlr_box_contains_point(self._ptr, x, y) + + def transform(self, box: Box, transform: WlOutput.transform, width: int, height: int): + lib.wlr_box_transform(self._ptr, box._ptr, transform, width, height) + + def intersection(self, box_a: Box, box_b: Box) -> bool: + return lib.wlr_box_intersection(self._ptr, box_a._ptr, box_b._ptr) diff --git a/wlroots/util/region.py b/wlroots/util/region.py index c2a1f299..2a4e9cbe 100644 --- a/wlroots/util/region.py +++ b/wlroots/util/region.py @@ -51,6 +51,11 @@ def rectangles_as_boxes(self) -> list[Box]: rects += 1 return boxes + @property + def extents(self) -> Box: + rect = lib.pixman_region32_extents(self._ptr) + return Box(rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1) + def transform( self, src: PixmanRegion32, @@ -68,3 +73,71 @@ def not_empty(self) -> bool: Wrapper around pixman_region32_not_empty """ return lib.pixman_region32_not_empty(self._ptr) + + def intersect_rect(self, other: PixmanRegion32, + x: int, y: int, width: int, height: int) -> bool: + """ + Wrapper around pixman_region32_intersect_rect + """ + return lib.pixman_region32_intersect_rect(self._ptr, other._ptr, x, y, width, height) + + def union_rect(self, other: PixmanRegion32, + x: int, y: int, width: int, height: int) -> bool: + """ + Wrapper around pixman_region32_union_rect + """ + return lib.pixman_region32_union_rect(self._ptr, other._ptr, x, y, width, height) + + def intersect(self, reg1: PixmanRegion32, reg2: PixmanRegion32) -> bool: + """ + Wrapper around pixman_region32_intersect + """ + return lib.pixman_region32_intersect(self._ptr, reg1._ptr, reg2._ptr) + + def union(self, reg1: PixmanRegion32, reg2: PixmanRegion32) -> bool: + """ + Wrapper around pixman_region32_union + """ + return lib.pixman_region32_union(self._ptr, reg1._ptr, reg2._ptr) + + def subtract(self, reg1: PixmanRegion32, reg2: PixmanRegion32) -> bool: + """ + Wrapper around pixman_region32_subtract + """ + return lib.pixman_region32_subtract(self._ptr, reg1._ptr, reg2._ptr) + + def copy_from(self, other: PixmanRegion32) -> bool: + """ + Wrapper around pixman_region32_copy + """ + return lib.pixman_region32_copy(self._ptr, other._ptr) + + def translate(self, x: int, y: int) -> None: + """ + Wrapper around pixman_region32_translate + """ + lib.pixman_region32_translate(self._ptr, x, y) + + def scale(self, other: PixmanRegion32, scale: float) -> None: + """ + Wrapper around wlr_region_scale + """ + lib.wlr_region_scale(self._ptr, other._ptr, scale) + + def scale_xy(self, other: PixmanRegion32, scale_x: float, scale_y: float) -> None: + """ + Wrapper around wlr_region_scale_xy + """ + lib.wlr_region_scale_xy(self._ptr, other._ptr, scale_x, scale_y) + + def expand(self, other: PixmanRegion32, distance: int) -> None: + """ + Wrapper around wlr_region_expand + """ + lib.wlr_region_expand(self._ptr, other._ptr, distance) + + def clear(self) -> None: + """ + Wrapper around pixman_region32_clear + """ + lib.pixman_region32_clear(self._ptr) diff --git a/wlroots/wlr_types/buffer.py b/wlroots/wlr_types/buffer.py index cc0065ba..ec791d16 100644 --- a/wlroots/wlr_types/buffer.py +++ b/wlroots/wlr_types/buffer.py @@ -16,6 +16,14 @@ class Buffer(Ptr): def __init__(self, ptr) -> None: self._ptr = ptr + @property + def width(self) -> int: + return self._ptr.width; + + @property + def height(self) -> int: + return self._ptr.height; + def drop(self) -> None: """Destroys this wlr_texture.""" lib.wlr_buffer_drop(self._ptr) diff --git a/wlroots/wlr_types/output.py b/wlroots/wlr_types/output.py index 21fac421..4b9e857f 100644 --- a/wlroots/wlr_types/output.py +++ b/wlroots/wlr_types/output.py @@ -100,6 +100,14 @@ def current_mode(self) -> OutputMode | None: return None return OutputMode(self._ptr.current_mode) + @property + def width(self) -> int: + return self._ptr.width + + @property + def height(self) -> int: + return self._ptr.height + @property def scale(self) -> float: return self._ptr.scale @@ -113,6 +121,10 @@ def transform_matrix(self) -> Matrix: """The transform matrix giving the projection of the output""" return Matrix(self._ptr.transform_matrix) + @property + def needs_frame(self) -> bool: + return self._ptr.needs_frame + def enable(self, *, enable: bool = True) -> None: """Enables or disables the output @@ -178,14 +190,16 @@ def init_render(self, allocator: Allocator, renderer: Renderer) -> None: "Output capabilities must match the capabilities of the output's backend." ) - def attach_render(self) -> None: + def attach_render(self) -> int: """Attach the renderer's buffer to the output Compositors must call this function before rendering. After they are done rendering, they should call `.commit()` to submit the new frame. """ - if not lib.wlr_output_attach_render(self._ptr, ffi.NULL): + buffer_age_ptr = ffi.new("int *") + if not lib.wlr_output_attach_render(self._ptr, buffer_age_ptr): raise RuntimeError("Unable to attach render") + return buffer_age_ptr[0] def commit(self) -> None: """Commit the pending output state diff --git a/wlroots/wlr_types/scene.py b/wlroots/wlr_types/scene.py index 3a99d2f9..e8b58ddd 100644 --- a/wlroots/wlr_types/scene.py +++ b/wlroots/wlr_types/scene.py @@ -5,14 +5,21 @@ import enum from typing import TYPE_CHECKING, Callable, TypeVar +from pywayland.protocol.wayland import WlOutput +from pywayland.utils import wl_list_for_each, wl_container_of +from pywayland.server import Signal + from wlroots import Ptr, PtrHasData, ffi, lib +from wlroots.util.box import Box from wlroots.util.region import PixmanRegion32 -from wlroots.wlr_types import Surface +from wlroots.wlr_types import Surface, Buffer + +from .texture import Texture if TYPE_CHECKING: - from wlroots.util.box import Box + from typing import Iterator from wlroots.util.clock import Timespec - from wlroots.wlr_types import Buffer, Output, OutputLayout + from wlroots.wlr_types import Output, OutputLayout from wlroots.wlr_types.layer_shell_v1 import LayerSurfaceV1 from wlroots.wlr_types.presentation_time import Presentation from wlroots.wlr_types.xdg_shell import XdgSurface @@ -112,6 +119,14 @@ def set_position(self, lx: int, ly: int) -> None: """Set the output's position in the scene-graph.""" lib.wlr_scene_output_set_position(self._ptr, lx, ly) + @property + def x(self): + return self._ptr.x + + @property + def y(self): + return self._ptr.y + class SceneTree(PtrHasData): def __init__(self, ptr) -> None: @@ -124,6 +139,16 @@ def node(self) -> SceneNode: ptr = ffi.addressof(self._ptr.node) return SceneNode(ptr) + @property + def children(self) -> Iterator[SceneNode]: + for ptr in wl_list_for_each( + "struct wlr_scene_node *", + self._ptr.children, + "link", + ffi=ffi + ): + yield SceneNode(ptr) + @classmethod def create(cls, parent: SceneTree) -> SceneTree: return SceneTree(lib.wlr_scene_tree_create(parent._ptr)) @@ -159,6 +184,32 @@ def node(self) -> SceneNode: ptr = ffi.addressof(self._ptr.node) return SceneNode(ptr) + @property + def buffer(self) -> Buffer | None: + ptr = self._ptr.buffer + return Buffer(ptr) if ptr != ffi.NULL else None + + @property + def texture(self) -> Texture | None: + ptr = self._ptr.texture + return Texture(ptr) if ptr != ffi.NULL else None + + @texture.setter + def texture(self, texture: Texture | None): + self._ptr.texture = ffi.NULL if texture is None else texture._ptr + + @property + def transform(self) -> WlOutput.transform: + return WlOutput.transform(self._ptr.transform) + + @property + def dst_width(self) -> int: + return self._ptr.dst_width + + @property + def dst_height(self) -> int: + return self._ptr.dst_height + def set_buffer(self, buffer: Buffer | None) -> None: buffer_ptr = buffer._ptr if buffer else ffi.NULL lib.wlr_scene_buffer_set_buffer(self._ptr, buffer_ptr) @@ -187,6 +238,7 @@ class SceneNode(PtrHasData): def __init__(self, ptr) -> None: """A node is an object in the scene.""" self._ptr = ptr + self.destroy_event = Signal(ptr=ffi.addressof(self._ptr.events.destroy)) @property def type(self) -> SceneNodeType: @@ -206,10 +258,60 @@ def x(self) -> int: def y(self) -> int: return self._ptr.y + @property + def visible(self) -> PixmanRegion32: + return PixmanRegion32(ffi.addressof(self._ptr.visible)) + + @property + def size(self) -> tuple[int, int]: + """Port of scene_node_get_size in wlr_scene.c.""" + if self.type == SceneNodeType.RECT: + rect = self.as_rect + return rect.width, rect.height + if self.type == SceneNodeType.BUFFER: + scene_buffer = self.as_buffer + if scene_buffer.dst_width > 0 and scene_buffer.dst_height > 0: + return scene_buffer.dst_width, scene_buffer.dst_height + buffer = scene_buffer.buffer + if buffer: + if scene_buffer.transform & WlOutput.transform.transform_90: + return buffer.height, buffer.width + else: + return buffer.width, buffer.height + return 0, 0 + @property def enabled(self) -> bool: return self._ptr.enabled + @property + def invisible(self) -> bool: + if self.type == SceneNodeType.TREE: + return True + elif self.type == SceneNodeType.RECT: + return self.as_rect.color[3] == 0.0 + elif self.type == SceneNodeType.BUFFER: + return self.as_buffer.buffer is None + assert False + + @property + def as_tree(self) -> SceneTree: + assert self.type == SceneNodeType.TREE + ptr = wl_container_of(self._ptr, "struct wlr_scene_tree *", "node", ffi=ffi) + return SceneTree(ptr) + + @property + def as_rect(self) -> SceneRect: + assert self.type == SceneNodeType.RECT + ptr = wl_container_of(self._ptr, "struct wlr_scene_rect *", "node", ffi=ffi) + return SceneRect(ptr=ptr) + + @property + def as_buffer(self) -> SceneBuffer: + assert self.type == SceneNodeType.BUFFER + ptr = wl_container_of(self._ptr, "struct wlr_scene_buffer *", "node", ffi=ffi) + return SceneBuffer(ptr) + def destroy(self) -> None: """Immediately destroy the scene-graph node.""" lib.wlr_scene_node_destroy(self._ptr) @@ -273,6 +375,17 @@ def for_each_buffer( self._ptr, lib.buffer_iterator_callback, handle ) + def coords(self) -> tuple[bool, int, int]: + """Get the node's layout-local coordinates. + + The first entry of the return value is True if the node and all of its + ancestors are enabled. + """ + x_ptr = ffi.new("int *") + y_ptr = ffi.new("int *") + enabled = lib.wlr_scene_node_coords(self._ptr, x_ptr, y_ptr) + return enabled, x_ptr[0], y_ptr[0] + class SceneSurface(Ptr): def __init__(self, ptr) -> None: @@ -293,10 +406,17 @@ def surface(self) -> Surface: class SceneRect(Ptr): def __init__( - self, parent: SceneTree, width: int, height: int, color: ffi.CData + self, + parent: SceneTree | None = None, + width: int | None = None, + height: int | None = None, + color: ffi.CData | None = None, + ptr = None, ) -> None: """A scene-graph node displaying a solid-colored rectangle""" - self._ptr = lib.wlr_scene_rect_create(parent._ptr, width, height, color) + if ptr is None: + ptr = lib.wlr_scene_rect_create(parent._ptr, width, height, color) + self._ptr = ptr @property def node(self) -> SceneNode: @@ -304,6 +424,19 @@ def node(self) -> SceneNode: ptr = ffi.addressof(self._ptr.node) return SceneNode(ptr) + @property + def width(self) -> int: + return self._ptr.width + + @property + def height(self) -> int: + return self._ptr.height + + @property + def color(self) -> tuple[int, int, int, int]: + c = self._ptr.color + return c[0], c[1], c[2], c[3] + def set_size(self, width: int, height: int) -> None: """Change the width and height of an existing rectangle node.""" lib.wlr_scene_rect_set_size(self._ptr, width, height) diff --git a/wlroots/wlr_types/texture.py b/wlroots/wlr_types/texture.py index e3651a50..4f036911 100644 --- a/wlroots/wlr_types/texture.py +++ b/wlroots/wlr_types/texture.py @@ -51,6 +51,14 @@ def from_buffer( ptr = ffi.gc(ptr, lib.wlr_texture_destroy) return Texture(ptr) + @property + def width(self): + return self._ptr.width + + @property + def height(self): + return self._ptr.height + def update_from_buffer( self, buffer: Buffer,