diff --git a/tiny/server.py b/tiny/server.py index 5d9dbd00..0bd4d9a6 100644 --- a/tiny/server.py +++ b/tiny/server.py @@ -22,6 +22,7 @@ Keyboard, Output, OutputLayout, + OutputState, Scene, SceneBuffer, SceneNodeType, @@ -346,9 +347,12 @@ def server_new_xdg_surface(self, listener, xdg_surface: XdgSurface) -> None: def server_new_output(self, listener, output: Output) -> None: output.init_render(self._allocator, self._renderer) - output.set_mode(output.preferred_mode()) - output.enable() - output.commit() + state = OutputState() + state.enabled = True + state.mode = output.preferred_mode() + state.adaptive_sync_enabled = True # Will be disabled if unsupported + + output.commit(state) self.outputs.append(output) self._output_layout.add_auto(output) diff --git a/wlroots/ffi_build.py b/wlroots/ffi_build.py index 28251f16..f02c740a 100644 --- a/wlroots/ffi_build.py +++ b/wlroots/ffi_build.py @@ -1082,6 +1082,17 @@ def has_xwayland() -> bool: }; struct wlr_output_state { + bool enabled; + float scale; + enum wl_output_transform transform; + bool adaptive_sync_enabled; + uint32_t render_format; + enum wl_output_subpixel subpixel; + struct wlr_output_mode *mode; + struct { + int32_t width, height; + int32_t refresh; // mHz, may be zero + } custom_mode; ...; }; @@ -1172,12 +1183,36 @@ def has_xwayland() -> bool: bool wlr_output_test(struct wlr_output *output); bool wlr_output_commit(struct wlr_output *output); void wlr_output_rollback(struct wlr_output *output); +bool wlr_output_test_state(struct wlr_output *output, + const struct wlr_output_state *state); +bool wlr_output_commit_state(struct wlr_output *output, + const struct wlr_output_state *state); + +void wlr_output_state_set_enabled(struct wlr_output_state *state, + bool enabled); +void wlr_output_state_set_mode(struct wlr_output_state *state, + struct wlr_output_mode *mode); +void wlr_output_state_set_custom_mode(struct wlr_output_state *state, + int32_t width, int32_t height, int32_t refresh); +void wlr_output_state_set_scale(struct wlr_output_state *state, float scale); +void wlr_output_state_set_transform(struct wlr_output_state *state, + enum wl_output_transform transform); +void wlr_output_state_set_adaptive_sync_enabled(struct wlr_output_state *state, + bool enabled); +void wlr_output_state_set_render_format(struct wlr_output_state *state, + uint32_t format); +void wlr_output_state_set_subpixel(struct wlr_output_state *state, + enum wl_output_subpixel subpixel); void wlr_output_render_software_cursors(struct wlr_output *output, struct pixman_region32 *damage); enum wl_output_transform wlr_output_transform_invert( enum wl_output_transform tr); + +enum wl_output_transform wlr_output_transform_compose( + enum wl_output_transform tr_a, enum wl_output_transform tr_b); + """ # types/wlr_output_damage.h @@ -1341,6 +1376,10 @@ def has_xwayland() -> bool: struct wlr_output_configuration_v1 *config, struct wlr_output *output); void wlr_output_configuration_v1_destroy( struct wlr_output_configuration_v1 *config); + +void wlr_output_head_v1_state_apply( + const struct wlr_output_head_v1_state *head_state, + struct wlr_output_state *output_state); """ # types/wlr_output_powewr_management_v1.h @@ -2166,23 +2205,23 @@ def has_xwayland() -> bool: struct wlr_keyboard *wlr_seat_get_keyboard(struct wlr_seat *seat); void wlr_seat_touch_point_focus(struct wlr_seat *seat, - struct wlr_surface *surface, uint32_t time_msec, - int32_t touch_id, double sx, double sy); + struct wlr_surface *surface, uint32_t time_msec, + int32_t touch_id, double sx, double sy); void wlr_seat_touch_point_clear_focus(struct wlr_seat *seat, uint32_t time_msec, - int32_t touch_id); + int32_t touch_id); uint32_t wlr_seat_touch_notify_down(struct wlr_seat *seat, - struct wlr_surface *surface, uint32_t time_msec, - int32_t touch_id, double sx, double sy); + struct wlr_surface *surface, uint32_t time_msec, + int32_t touch_id, double sx, double sy); void wlr_seat_touch_notify_up(struct wlr_seat *seat, uint32_t time_msec, - int32_t touch_id); + int32_t touch_id); void wlr_seat_touch_notify_motion(struct wlr_seat *seat, uint32_t time_msec, - int32_t touch_id, double sx, double sy); + int32_t touch_id, double sx, double sy); void wlr_seat_touch_notify_cancel(struct wlr_seat *seat, - struct wlr_surface *surface); + struct wlr_surface *surface); void wlr_seat_touch_notify_frame(struct wlr_seat *seat); int wlr_seat_touch_num_points(struct wlr_seat *seat); void wlr_seat_touch_start_grab(struct wlr_seat *wlr_seat, - struct wlr_seat_touch_grab *grab); + struct wlr_seat_touch_grab *grab); void wlr_seat_touch_end_grab(struct wlr_seat *wlr_seat); bool wlr_seat_touch_has_grab(struct wlr_seat *seat); @@ -2238,57 +2277,57 @@ def has_xwayland() -> bool: # types/wlr_touch.h CDEF += """ struct wlr_touch { - struct wlr_input_device base; - const struct wlr_touch_impl *impl; - char *output_name; - double width_mm, height_mm; - struct { - struct wl_signal down; // struct wlr_touch_down_event - struct wl_signal up; // struct wlr_touch_up_event - struct wl_signal motion; // struct wlr_touch_motion_event - struct wl_signal cancel; // struct wlr_touch_cancel_event - struct wl_signal frame; - } events; + struct wlr_input_device base; + const struct wlr_touch_impl *impl; + char *output_name; + double width_mm, height_mm; + struct { + struct wl_signal down; // struct wlr_touch_down_event + struct wl_signal up; // struct wlr_touch_up_event + struct wl_signal motion; // struct wlr_touch_motion_event + struct wl_signal cancel; // struct wlr_touch_cancel_event + struct wl_signal frame; + } events; - void *data; + void *data; ...; }; struct wlr_touch_down_event { - struct wlr_touch *touch; - uint32_t time_msec; - int32_t touch_id; - // From 0..1 - double x, y; + struct wlr_touch *touch; + uint32_t time_msec; + int32_t touch_id; + // From 0..1 + double x, y; ...; }; struct wlr_touch_up_event { - struct wlr_touch *touch; - uint32_t time_msec; - int32_t touch_id; + struct wlr_touch *touch; + uint32_t time_msec; + int32_t touch_id; ...; }; struct wlr_touch_motion_event { - struct wlr_touch *touch; - uint32_t time_msec; - int32_t touch_id; - // From 0..1 - double x, y; + struct wlr_touch *touch; + uint32_t time_msec; + int32_t touch_id; + // From 0..1 + double x, y; ...; }; struct wlr_touch_cancel_event { - struct wlr_touch *touch; - uint32_t time_msec; - int32_t touch_id; + struct wlr_touch *touch; + uint32_t time_msec; + int32_t touch_id; ...; }; struct wlr_touch *wlr_touch_from_input_device( - struct wlr_input_device *input_device); + struct wlr_input_device *input_device); """ # types/wlr_virtual_keyboard_v1.h diff --git a/wlroots/wlr_types/__init__.py b/wlroots/wlr_types/__init__.py index 96f1261a..78952fdf 100644 --- a/wlroots/wlr_types/__init__.py +++ b/wlroots/wlr_types/__init__.py @@ -19,7 +19,7 @@ from .keyboard import Keyboard # noqa: F401 from .layer_shell_v1 import LayerShellV1 # noqa: F401 from .matrix import Matrix # noqa: F401 -from .output import Output # noqa: F401 +from .output import Output, OutputState # noqa: F401 from .output_damage import OutputDamage # noqa: F401 from .output_layout import OutputLayout # noqa: F401 from .pointer import ( # noqa: F401 diff --git a/wlroots/wlr_types/output.py b/wlroots/wlr_types/output.py index c61faec2..98bb21de 100644 --- a/wlroots/wlr_types/output.py +++ b/wlroots/wlr_types/output.py @@ -3,13 +3,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +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, str_or_none +from wlroots import Ptr, PtrHasData, ffi, lib, ptr_or_null, str_or_none from wlroots.util.region import PixmanRegion32 from .matrix import Matrix @@ -136,10 +136,7 @@ def set_mode(self, mode: OutputMode | None) -> None: The output needs to be enabled. """ - if mode is None: - lib.wlr_output_set_mode(self._ptr, ffi.NULL) - else: - lib.wlr_output_set_mode(self._ptr, mode._ptr) + lib.wlr_output_set_mode(self._ptr, ptr_or_null(mode)) def set_custom_mode(self, width: int, height: int, refresh: int) -> None: """ @@ -188,13 +185,16 @@ def attach_render(self) -> None: if not lib.wlr_output_attach_render(self._ptr, ffi.NULL): raise RuntimeError("Unable to attach render") - def commit(self) -> bool: + def commit(self, output_state: OutputState | None = None) -> bool: """Commit the pending output state If `.attach_render` has been called, the pending frame will be submitted for display. """ - return lib.wlr_output_commit(self._ptr) + if output_state is None: + return lib.wlr_output_commit(self._ptr) + else: + return lib.wlr_output_commit_state(self._ptr, output_state._ptr) def rollback(self) -> None: """Discard the pending output state""" @@ -223,16 +223,23 @@ def render_software_cursors(self, damage: PixmanRegion32 | None = None) -> None: This is a utility function that can be called when compositors render. """ - if damage is None: - lib.wlr_output_render_software_cursors(self._ptr, ffi.NULL) - else: - lib.wlr_output_render_software_cursors(self._ptr, damage._ptr) + lib.wlr_output_render_software_cursors(self._ptr, ptr_or_null(damage)) @staticmethod def transform_invert(transform: WlOutput.transform) -> WlOutput.transform: """Returns the transform that, when composed with transform gives transform.normal""" return WlOutput.transform(lib.wlr_output_transform_invert(transform)) + @staticmethod + def transform_compose( + tr_a: WlOutput.transform, tr_b: WlOutput.transform + ) -> WlOutput.transform: + """ + Returns a transform that, when applied, has the same effect as applying + sequentially `tr_a` and `tr_b`. + """ + return WlOutput.transform(lib.wlr_output_transform_compose(tr_a, tr_b)) + def set_damage(self, damage: PixmanRegion32) -> None: """ Set the damage region for the frame to be submitted. This is the region of @@ -263,7 +270,7 @@ def set_scale(self, scale: float) -> None: """ lib.wlr_output_set_scale(self._ptr, scale) - def test(self) -> bool: + def test(self, output_state: OutputState | None = None) -> bool: """ Test whether the pending output state would be accepted by the backend. If this function returns true, `wlr_output_commit` can only fail due to a @@ -271,7 +278,10 @@ def test(self) -> bool: This function doesn't mutate the pending state. """ - return lib.wlr_output_test(self._ptr) + if output_state is None: + return lib.wlr_output_test(self._ptr) + else: + return lib.wlr_output_test_state(self._ptr, output_state._ptr) def enable_adaptive_sync(self, *, enable: bool = True) -> None: """ @@ -312,3 +322,91 @@ def refresh_mhz(self) -> int: @property def preferred(self) -> int: return self._ptr.preferred + + +class CustomMode(NamedTuple): + """ + Custom mode which specifies the width and height, and the refresh rate + of an Output. + + If refresh is zero (default), the backend uses a reasonable default value. + """ + + width: int + height: int + refresh: int = 0 + + +class OutputState(Ptr): + def __init__(self, ptr: ffi.CData | None = None) -> None: + if ptr is None: + ptr = ffi.new("struct wlr_output_state *") + self._ptr = ptr + + @property + def enabled(self) -> bool: + return self._ptr.enabled + + @enabled.setter + def enabled(self, enabled: bool) -> None: + lib.wlr_output_state_set_enabled(self._ptr, enabled) + + @property + def scale(self) -> float: + return self._ptr.scale + + @scale.setter + def scale(self, scale: float) -> None: + lib.wlr_output_state_set_scale(self._ptr, scale) + + @property + def transform(self) -> WlOutput.transform: + return WlOutput.transform(self._ptr.transform) + + @transform.setter + def transform(self, transform: WlOutput.transform) -> None: + lib.wlr_output_state_set_transform(self._ptr, transform) + + @property + def adaptive_sync_enabled(self) -> bool: + return self._ptr.adaptive_sync_enabled + + @adaptive_sync_enabled.setter + def adaptive_sync_enabled(self, enabled: bool) -> None: + lib.wlr_output_state_set_adaptive_sync_enabled(self._ptr, enabled) + + @property + def render_format(self) -> int: + return self._ptr.render_format + + @render_format.setter + def render_format(self, format: int) -> None: + lib.wlr_output_state_set_render_format(format) + + @property + def subpixel(self) -> WlOutput.subpixel: + return WlOutput.subpixel(self._ptr.subpixel) + + @subpixel.setter + def subpixel(self, subpixel: WlOutput.subpixel) -> None: + lib.wlr_output_state_set_subpixel(self._ptr, subpixel) + + @property + def mode(self) -> OutputMode | None: + mode_ptr = self._ptr.mode + return OutputMode(mode_ptr) if mode_ptr != ffi.NULL else None + + @mode.setter + def mode(self, mode: OutputMode | None) -> None: + lib.wlr_output_state_set_mode(self._ptr, ptr_or_null(mode)) + + @property + def custom_mode(self) -> CustomMode: + mode = self._ptr.custom_mode + return CustomMode(width=mode.width, height=mode.height, refresh=mode.refresh) + + @custom_mode.setter + def custom_mode(self, mode: CustomMode) -> None: + lib.wlr_output_state_set_custom_mode( + self._ptr, mode.width, mode.height, mode.refresh + ) diff --git a/wlroots/wlr_types/output_management_v1.py b/wlroots/wlr_types/output_management_v1.py index d28c7ed9..cbbba981 100644 --- a/wlroots/wlr_types/output_management_v1.py +++ b/wlroots/wlr_types/output_management_v1.py @@ -1,25 +1,15 @@ # Copyright (c) Matt Colligan 2021 from __future__ import annotations -from dataclasses import dataclass from typing import Iterator from pywayland.protocol.wayland import WlOutput from pywayland.server import Display, Signal from pywayland.utils import wl_list_for_each -from wlroots import Ptr, ffi, lib +from wlroots import Ptr, PtrHasData, ffi, lib, ptr_or_null -from .output import Output, OutputMode - - -@dataclass -class CustomMode: - """The custom_mode struct member of wlr_output_state""" - - width: int - height: int - refresh: int +from .output import CustomMode, Output, OutputMode, OutputState class OutputHeadV1State(Ptr): @@ -71,24 +61,18 @@ def mode(self) -> OutputMode | None: @mode.setter def mode(self, mode: OutputMode | None) -> None: - if mode is None: - self._ptr.mode = ffi.NULL - else: - self._ptr.mode = mode._ptr + self._ptr.mode = ptr_or_null(mode) @property - def custom_mode(self): - width = self._ptr.custom_mode.width - height = self._ptr.custom_mode.height - refresh = self._ptr.custom_mode.refresh - return CustomMode(width, height, refresh) + def custom_mode(self) -> CustomMode: + mode = self._ptr.custom_mode + return CustomMode(width=mode.width, height=mode.height, refresh=mode.refresh) @custom_mode.setter - def custom_mode(self): - width = self._ptr.custom_mode.width - height = self._ptr.custom_mode.height - refresh = self._ptr.custom_mode.refresh - return CustomMode(width, height, refresh) + def custom_mode(self, mode: CustomMode): + self._ptr.custom_mode.width = mode.width + self._ptr.custom_mode.height = mode.height + self._ptr.custom_mode.refresh = mode.refresh @property def transform(self) -> WlOutput.transform: @@ -106,6 +90,17 @@ def adaptive_sync_enabled(self) -> bool: def adaptive_sync_enabled(self, value: bool) -> None: self._ptr.adaptive_sync_enabled = value + def apply(self, output_state: OutputState) -> None: + """ + Apply the head state on the supplied OutputState. + + Compositors can then pass the resulting OutputState to + Output.commit(output_state) or Output.test(output_state). + + The position needs to be applied manually by the caller. + """ + lib.wlr_output_head_v1_state_apply(self._ptr, output_state._ptr) + class OutputConfigurationV1(Ptr): def __init__(self, ptr=None) -> None: @@ -166,7 +161,7 @@ def state(self) -> OutputHeadV1State: return OutputHeadV1State(self._ptr.state) -class OutputManagerV1(Ptr): +class OutputManagerV1(PtrHasData): def __init__(self, display: Display) -> None: """Create a wlr_output_manager_v1 diff --git a/wlroots/wlr_types/pointer.py b/wlroots/wlr_types/pointer.py index 63d0c16c..68280576 100644 --- a/wlroots/wlr_types/pointer.py +++ b/wlroots/wlr_types/pointer.py @@ -6,7 +6,7 @@ import enum from weakref import WeakKeyDictionary -from wlroots import Ptr, ffi, lib, str_or_none +from wlroots import Ptr, PtrHasData, ffi, lib, str_or_none from .input_device import ButtonState, InputDevice @@ -27,7 +27,7 @@ class AxisOrientation(enum.IntEnum): HORIZONTAL = lib.WLR_AXIS_ORIENTATION_HORIZONTAL -class Pointer(Ptr): +class Pointer(PtrHasData): def __init__(self, ptr) -> None: self._ptr = ptr