From 32443881c5f4bd79b064aa71496e6e0af2fb30ac Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 20 Dec 2024 12:24:52 +0100 Subject: [PATCH] Added Output events, updated OutputState properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added support for various wlr_output_event… structs (fixes #209) * Python API exposes more wlr_output_state properties * Renamed OutputEventRequestState to OutputRequestStateEvent and added an alias for the previous name (fixes #210) * Updated CHANGES --- CHANGES.rst | 12 ++- wlroots/ffi_build.py | 90 +++++++++++++--- wlroots/util/clock.py | 4 +- wlroots/wlr_types/output.py | 202 ++++++++++++++++++++++++++++++++++-- 4 files changed, 283 insertions(+), 25 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index b34ef4a8..f9e54718 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,15 @@ +0.17.1 -- unreleased +-------------------- +* Added more properties to ``OutputState`` +* Added support for various ``Output`` events + (`#209 `_): +* Renamed ``OutputEventRequestState`` to ``OutputRequestStateEvent`` + (the previous name is still available as an alias + `#210 `_) + + 0.17.0 -- 2024-05-12 ------- +-------------------- * Support for wlroots 0.17.x * **Breaking change** Rename all declarations of XdgTopLevel.* to XdgToplevel.* diff --git a/wlroots/ffi_build.py b/wlroots/ffi_build.py index 8a0a0747..f1e48afd 100644 --- a/wlroots/ffi_build.py +++ b/wlroots/ffi_build.py @@ -738,21 +738,21 @@ def has_xwayland() -> bool: # types/wlr_fractional_scale_v1.h CDEF += """ struct wlr_fractional_scale_manager_v1 { - struct wl_global *global; + struct wl_global *global; - struct { - struct wl_signal destroy; - } events; + struct { + struct wl_signal destroy; + } events; - ...; + ...; }; void wlr_fractional_scale_v1_notify_scale( - struct wlr_surface *surface, double scale); + struct wlr_surface *surface, double scale); struct wlr_fractional_scale_manager_v1 *wlr_fractional_scale_manager_v1_create( - struct wl_display *display, uint32_t version); + struct wl_display *display, uint32_t version); """ # types/wlr_gamma_control_v1.h @@ -1116,6 +1116,58 @@ def has_xwayland() -> bool: ...; }; +struct wlr_output_event_damage { + struct wlr_output *output; + const pixman_region32_t *damage; // output-buffer-local coordinates + ...; +}; + +struct wlr_output_event_precommit { + struct wlr_output *output; + struct timespec *when; + const struct wlr_output_state *state; + ...; +}; + +struct wlr_output_event_commit { + struct wlr_output *output; + struct timespec *when; + const struct wlr_output_state *state; + ...; +}; + +enum wlr_output_present_flag { + WLR_OUTPUT_PRESENT_VSYNC = ..., + WLR_OUTPUT_PRESENT_HW_CLOCK = ..., + WLR_OUTPUT_PRESENT_HW_COMPLETION = ..., + WLR_OUTPUT_PRESENT_ZERO_COPY = ..., + ... +}; + +struct wlr_output_event_present { + struct wlr_output *output; + // Frame submission for which this presentation event is for (see + // wlr_output.commit_seq). + uint32_t commit_seq; + // Whether the frame was presented at all. + bool presented; + // Time when the content update turned into light the first time. + struct timespec *when; + // Vertical retrace counter. Zero if unavailable. + unsigned seq; + // Prediction of how many nanoseconds after `when` the very next output + // refresh may occur. Zero if unknown. + int refresh; // nsec + uint32_t flags; // enum wlr_output_present_flag + ...; +}; + +struct wlr_output_event_bind { + struct wlr_output *output; + struct wl_resource *resource; + ...; +}; + struct wlr_output_event_request_state { struct wlr_output *output; const struct wlr_output_state *state; @@ -1172,6 +1224,16 @@ def has_xwayland() -> bool: uint32_t format); void wlr_output_state_set_subpixel(struct wlr_output_state *state, enum wl_output_subpixel subpixel); +void wlr_output_state_set_buffer(struct wlr_output_state *state, + struct wlr_buffer *buffer); +bool wlr_output_state_set_gamma_lut(struct wlr_output_state *state, + size_t ramp_size, const uint16_t *r, const uint16_t *g, const uint16_t *b); +void wlr_output_state_set_damage(struct wlr_output_state *state, + const pixman_region32_t *damage); +void wlr_output_state_set_layers(struct wlr_output_state *state, + struct wlr_output_layer_state *layers, size_t layers_len); +bool wlr_output_state_copy(struct wlr_output_state *dst, + const struct wlr_output_state *src); void wlr_output_render_software_cursors(struct wlr_output *output, struct pixman_region32 *damage); @@ -1837,7 +1899,7 @@ def has_xwayland() -> bool: struct wlr_scene_buffer *scene_buffer); struct wlr_scene_rect *wlr_scene_rect_create(struct wlr_scene_tree *parent, - int width, int height, const float color[static 4]); + int width, int height, const float color[static 4]); void wlr_scene_rect_set_size(struct wlr_scene_rect *rect, int width, int height); @@ -1899,7 +1961,7 @@ def has_xwayland() -> bool: struct wlr_scene_tree *parent, struct wlr_surface *surface); void wlr_scene_subsurface_tree_set_clip(struct wlr_scene_node *node, - struct wlr_box *clip); + struct wlr_box *clip); struct wlr_scene_tree *wlr_scene_xdg_surface_create( struct wlr_scene_tree *parent, struct wlr_xdg_surface *xdg_surface); @@ -2266,15 +2328,15 @@ def has_xwayland() -> bool: }; struct wlr_session_lock_surface_v1_state { - uint32_t width, height; - uint32_t configure_serial; + uint32_t width, height; + uint32_t configure_serial; }; struct wlr_session_lock_surface_v1_configure { - struct wl_list link; // wlr_session_lock_surface_v1.configure_list - uint32_t serial; + struct wl_list link; // wlr_session_lock_surface_v1.configure_list + uint32_t serial; - uint32_t width, height; + uint32_t width, height; }; struct wlr_session_lock_surface_v1 { diff --git a/wlroots/util/clock.py b/wlroots/util/clock.py index 25947e6d..c32a1bd7 100644 --- a/wlroots/util/clock.py +++ b/wlroots/util/clock.py @@ -10,8 +10,8 @@ def __init__(self, ptr) -> None: """A wrapper aronud a timespec struct""" self._ptr = ptr - @classmethod - def get_monotonic_time(cls) -> Timespec: + @staticmethod + def get_monotonic_time() -> Timespec: """Get the current monotonic time""" timespec = ffi.new("struct timespec *") ret = lib.clock_gettime(lib.CLOCK_MONOTONIC, timespec) diff --git a/wlroots/wlr_types/output.py b/wlroots/wlr_types/output.py index cc4f8480..556e1235 100644 --- a/wlroots/wlr_types/output.py +++ b/wlroots/wlr_types/output.py @@ -3,13 +3,24 @@ from __future__ import annotations +import enum from typing import TYPE_CHECKING, NamedTuple from pywayland.protocol.wayland import WlOutput from pywayland.server import Signal from pywayland.utils import wl_list_for_each -from wlroots import Ptr, PtrHasData, ffi, lib, ptr_or_null, str_or_none +from wlroots import ( + Ptr, + PtrHasData, + ffi, + instance_or_none, + lib, + ptr_or_null, + str_or_none, +) +from wlroots.wlr_types.buffer import Buffer +from wlroots.util.clock import Timespec from wlroots.util.region import PixmanRegion32 from .matrix import Matrix @@ -44,14 +55,21 @@ def __init__(self, ptr) -> None: self.frame_event = Signal(ptr=ffi.addressof(self._ptr.events.frame)) self.damage_event = Signal(ptr=ffi.addressof(self._ptr.events.damage)) self.needs_frame_event = Signal(ptr=ffi.addressof(self._ptr.events.needs_frame)) - self.precommit_event = Signal(ptr=ffi.addressof(self._ptr.events.precommit)) - self.commit_event = Signal(ptr=ffi.addressof(self._ptr.events.commit)) + self.precommit_event = Signal( + ptr=ffi.addressof(self._ptr.events.precommit), + data_wrapper=OutputPrecommitEvent, + ) + self.commit_event = Signal( + ptr=ffi.addressof(self._ptr.events.commit), data_wrapper=OutputCommitEvent + ) self.present_event = Signal(ptr=ffi.addressof(self._ptr.events.present)) - self.bind_event = Signal(ptr=ffi.addressof(self._ptr.events.bind)) + self.bind_event = Signal( + ptr=ffi.addressof(self._ptr.events.bind), data_wrapper=OutputBindEvent + ) self.description_event = Signal(ptr=ffi.addressof(self._ptr.events.description)) self.request_state_event = Signal( ptr=ffi.addressof(self._ptr.events.request_state), - data_wrapper=OutputEventRequestState, + data_wrapper=OutputRequestStateEvent, ) self.destroy_event = Signal(ptr=ffi.addressof(self._ptr.events.destroy)) @@ -115,6 +133,13 @@ def transform_matrix(self) -> Matrix: """The transform matrix giving the projection of the output""" return Matrix(self._ptr.transform_matrix) + @property + def commit_seq(self) -> int: + """ + Commit sequence number. Incremented on each commit, may overflow. + """ + return self._ptr.commit_seq + def enable(self, *, enable: bool = True) -> None: """Enables or disables the output @@ -342,6 +367,10 @@ class CustomMode(NamedTuple): class OutputState(Ptr): + """ + Holds the double-buffered output state. + """ + def __init__(self, ptr: ffi.CData | None = None) -> None: if ptr is None: ptr = ffi.new("struct wlr_output_state *") @@ -355,6 +384,36 @@ def enabled(self) -> bool: def set_enabled(self, enabled: bool) -> None: lib.wlr_output_state_set_enabled(self._ptr, enabled) + @property + def damage(self) -> PixmanRegion32 | None: + return instance_or_none(PixmanRegion32, self._ptr.damage) + + def set_damage(self, damage: PixmanRegion32) -> None: + """ + Sets the damage region for an output. + + This is used as a hint to the backend and can be used to reduce power consumption or increase performance on some devices. + + This should be called in along with ``OutputState.set_buffer()``. + This state will be applied once ``Output.commit(output_state)`` is called. + """ + lib.wlr_output_state_set_damage(self._ptr, damage._ptr) + + @property + def buffer(self) -> Buffer | None: + return instance_or_none(Buffer, self._ptr.buffer) + + def set_buffer(self, buffer: Buffer) -> None: + """ + Sets the buffer for an output. + + The buffer contains the contents of the screen. + + If the compositor wishes to present a new frame, they must commit with a buffer. + This state will be applied once ``Output.commit(output_state)`` is called. + """ + lib.wlr_output_state_set_buffer(self._ptr, buffer._ptr) + @property def scale(self) -> float: return self._ptr.scale @@ -412,9 +471,35 @@ def finish(self) -> None: lib.wlr_output_state_finish(self._ptr) -class OutputEventRequestState(Ptr): - def __init__(self, ptr) -> None: - self._ptr = ffi.cast("struct wlr_output_event_request_state *", ptr) +class OutputPresentFlag(enum.IntFlag): + WLR_OUTPUT_PRESENT_VSYNC = lib.WLR_OUTPUT_PRESENT_VSYNC + """ + The presentation was synchronized to the "vertical retrace" by the + display hardware such that tearing does not happen. + """ + + WLR_OUTPUT_PRESENT_HW_CLOCK = lib.WLR_OUTPUT_PRESENT_HW_CLOCK + """ + The display hardware provided measurements that the hardware driver + converted into a presentation timestamp. + """ + + WLR_OUTPUT_PRESENT_HW_COMPLETION = lib.WLR_OUTPUT_PRESENT_HW_COMPLETION + """ + The display hardware signalled that it started using the new image + content. + """ + + WLR_OUTPUT_PRESENT_ZERO_COPY = lib.WLR_OUTPUT_PRESENT_ZERO_COPY + """ + The presentation of this update was done zero-copy. + """ + + +class _OutputStateAwareEvent(Ptr): + """\ + Common event superclass which provides access to the output and output state. + """ @property def output(self) -> Output: @@ -423,3 +508,104 @@ def output(self) -> Output: @property def state(self) -> OutputState: return OutputState(self._ptr.state) + + +class OutputRequestStateEvent(_OutputStateAwareEvent): + def __init__(self, ptr) -> None: + self._ptr = ffi.cast("struct wlr_output_event_request_state *", ptr) + + +OutputEventRequestState = OutputRequestStateEvent + + +class OutputPrecommitEvent(_OutputStateAwareEvent): + def __init__(self, ptr) -> None: + self._ptr = ffi.cast("struct wlr_output_event_precommit *", ptr) + + +class OutputCommitEvent(_OutputStateAwareEvent): + def __init__(self, ptr) -> None: + self._ptr = ffi.cast("struct wlr_output_event_commit *", ptr) + + @property + def when(self) -> Timespec: + return Timespec(self._ptr.when) + + +class OutputDamageEvent(Ptr): + def __init__(self, ptr) -> None: + self._ptr = ffi.cast("struct wlr_output_event_damage *", ptr) + + @property + def output(self) -> Output: + return Output(self._ptr.output) + + @property + def damage(self) -> PixmanRegion32: + """ + Output-buffer-local coordinates. + """ + return PixmanRegion32(self._ptr.damage) + + +class OutputBindEvent(Ptr): + def __init__(self, ptr) -> None: + self._ptr = ffi.cast("struct wlr_output_event_bind *", ptr) + + @property + def output(self) -> Output: + return Output(self._ptr.output) + + @property + def resource(self) -> ffi.CData: # This should be WlResource + return self._ptr.resource + + +class OutputPresentEvent(Ptr): + def __init__(self, ptr) -> None: + self._ptr = ffi.cast("struct wlr_output_event_present *", ptr) + + @property + def output(self) -> Output: + return Output(self._ptr.output) + + @property + def commit_seq(self) -> int: + """ + Frame submission for which this presentation event is for + (see Output.commit_seq). + """ + return self._ptr.commit_seq + + @property + def presented(self) -> bool: + """ + Whether the frame was presented at all. + """ + return self._ptr.presented + + @property + def when(self) -> Timespec: + """ + Time when the content update turned into light the first time. + """ + return Timespec(self._ptr.when) + + @property + def seq(self) -> int: + """ + Vertical retrace counter. Zero if unavailable. + """ + return self._ptr.seq + + @property + def refresh_nsec(self) -> int: + """ + Prediction of how many nanoseconds after `when` the very next output + refresh may occur. Zero if unknown. + """ + return self._ptr.refresh + + @property + def flags(self) -> OutputPresentFlag: + return OutputPresentFlag(self._ptr.flags)