From 538219d3892b793d2973fd5ddea43b71fac30dd4 Mon Sep 17 00:00:00 2001 From: DRAGONTOS Date: Mon, 1 Apr 2024 20:11:15 +0200 Subject: [PATCH] update: to hyprland version --- .builds/alpine.yml | 38 + .builds/archlinux.yml | 48 + .builds/freebsd.yml | 37 + .editorconfig | 14 + .gitignore | 1 + .gitlab-ci.yml | 7 + .gitlab/issue_templates/Default.md | 15 + .mailmap | 1 + CONTRIBUTING.md | 449 +++ LICENSE | 21 + README.md | 87 + backend/backend.c | 447 +++ backend/drm/atomic.c | 434 +++ backend/drm/backend.c | 262 ++ backend/drm/drm.c | 2069 +++++++++++++ backend/drm/fb.c | 258 ++ backend/drm/gen_pnpids.sh | 26 + backend/drm/legacy.c | 246 ++ backend/drm/libliftoff.c | 513 ++++ backend/drm/meson.build | 56 + backend/drm/monitor.c | 91 + backend/drm/properties.c | 209 ++ backend/drm/renderer.c | 172 ++ backend/drm/util.c | 279 ++ backend/headless/backend.c | 88 + backend/headless/meson.build | 4 + backend/headless/output.c | 146 + backend/libinput/backend.c | 245 ++ backend/libinput/events.c | 263 ++ backend/libinput/keyboard.c | 51 + backend/libinput/meson.build | 34 + backend/libinput/pointer.c | 286 ++ backend/libinput/switch.c | 49 + backend/libinput/tablet_pad.c | 201 ++ backend/libinput/tablet_tool.c | 277 ++ backend/libinput/touch.c | 83 + backend/meson.build | 29 + backend/multi/backend.c | 227 ++ backend/multi/meson.build | 1 + backend/session/meson.build | 17 + backend/session/session.c | 551 ++++ backend/wayland/backend.c | 701 +++++ backend/wayland/meson.build | 30 + backend/wayland/output.c | 932 ++++++ backend/wayland/pointer.c | 557 ++++ backend/wayland/seat.c | 387 +++ backend/wayland/tablet_v2.c | 929 ++++++ backend/x11/backend.c | 736 +++++ backend/x11/input_device.c | 331 +++ backend/x11/meson.build | 36 + backend/x11/output.c | 774 +++++ docs/architecture.md | 86 + docs/env_vars.md | 68 + examples/.gitignore | 1 + examples/cairo-buffer.c | 193 ++ examples/cat.c | 2641 +++++++++++++++++ examples/cat.h | 13 + examples/embedded.c | 212 ++ examples/fullscreen-shell.c | 258 ++ examples/meson.build | 68 + examples/output-layers.c | 340 +++ examples/output-layout.c | 304 ++ examples/pointer.c | 416 +++ examples/rotation.c | 288 ++ examples/scene-graph.c | 207 ++ examples/simple.c | 204 ++ examples/tablet.c | 416 +++ examples/touch.c | 311 ++ include/backend/backend.h | 13 + include/backend/drm/drm.h | 208 ++ include/backend/drm/fb.h | 24 + include/backend/drm/iface.h | 44 + include/backend/drm/monitor.h | 24 + include/backend/drm/properties.h | 84 + include/backend/drm/renderer.h | 40 + include/backend/drm/util.h | 43 + include/backend/headless.h | 30 + include/backend/libinput.h | 139 + include/backend/multi.h | 21 + include/backend/session/session.h | 20 + include/backend/wayland.h | 183 ++ include/backend/x11.h | 147 + include/interfaces/wlr_input_device.h | 20 + include/meson.build | 38 + include/render/allocator/allocator.h | 9 + include/render/allocator/drm_dumb.h | 37 + include/render/allocator/gbm.h | 34 + include/render/allocator/shm.h | 23 + include/render/dmabuf.h | 35 + include/render/drm_format_set.h | 23 + include/render/egl.h | 108 + include/render/gles2.h | 172 ++ include/render/pixel_format.h | 66 + include/render/pixman.h | 64 + include/render/vulkan.h | 456 +++ include/render/wlr_renderer.h | 23 + include/types/wlr_buffer.h | 77 + include/types/wlr_data_device.h | 43 + include/types/wlr_keyboard.h | 8 + include/types/wlr_matrix.h | 15 + include/types/wlr_output.h | 25 + include/types/wlr_region.h | 14 + include/types/wlr_scene.h | 10 + include/types/wlr_seat.h | 33 + include/types/wlr_subcompositor.h | 9 + include/types/wlr_tablet_v2.h | 94 + include/types/wlr_xdg_shell.h | 39 + include/util/array.h | 18 + include/util/env.h | 23 + include/util/global.h | 14 + include/util/rect_union.h | 76 + include/util/set.h | 29 + include/util/shm.h | 11 + include/util/time.h | 33 + include/util/token.h | 16 + include/util/utf8.h | 11 + include/wlr/backend.h | 65 + include/wlr/backend/drm.h | 103 + include/wlr/backend/headless.h | 31 + include/wlr/backend/interface.h | 33 + include/wlr/backend/libinput.h | 29 + include/wlr/backend/multi.h | 35 + include/wlr/backend/session.h | 148 + include/wlr/backend/wayland.h | 70 + include/wlr/backend/x11.h | 51 + include/wlr/config.h.in | 17 + include/wlr/interfaces/wlr_buffer.h | 48 + include/wlr/interfaces/wlr_keyboard.h | 34 + include/wlr/interfaces/wlr_output.h | 130 + include/wlr/interfaces/wlr_pointer.h | 22 + include/wlr/interfaces/wlr_switch.h | 22 + include/wlr/interfaces/wlr_tablet_pad.h | 30 + include/wlr/interfaces/wlr_tablet_tool.h | 22 + include/wlr/interfaces/wlr_touch.h | 22 + include/wlr/meson.build | 25 + include/wlr/render/allocator.h | 70 + include/wlr/render/dmabuf.h | 55 + include/wlr/render/drm_format_set.h | 97 + include/wlr/render/egl.h | 56 + include/wlr/render/gles2.h | 51 + include/wlr/render/interface.h | 93 + include/wlr/render/pass.h | 123 + include/wlr/render/pixman.h | 24 + include/wlr/render/swapchain.h | 56 + include/wlr/render/vulkan.h | 36 + include/wlr/render/wlr_renderer.h | 110 + include/wlr/render/wlr_texture.h | 85 + include/wlr/types/wlr_buffer.h | 164 + include/wlr/types/wlr_compositor.h | 527 ++++ include/wlr/types/wlr_content_type_v1.h | 36 + include/wlr/types/wlr_cursor.h | 218 ++ include/wlr/types/wlr_cursor_shape_v1.h | 60 + include/wlr/types/wlr_damage_ring.h | 109 + include/wlr/types/wlr_data_control_v1.h | 47 + include/wlr/types/wlr_data_device.h | 255 ++ include/wlr/types/wlr_drm.h | 57 + include/wlr/types/wlr_drm_lease_v1.h | 146 + include/wlr/types/wlr_export_dmabuf_v1.h | 43 + .../types/wlr_ext_foreign_toplevel_list_v1.h | 67 + .../wlr_foreign_toplevel_management_v1.h | 153 + include/wlr/types/wlr_fractional_scale_v1.h | 34 + include/wlr/types/wlr_fullscreen_shell_v1.h | 39 + include/wlr/types/wlr_gamma_control_v1.h | 50 + include/wlr/types/wlr_idle_inhibit_v1.h | 56 + include/wlr/types/wlr_idle_notify_v1.h | 44 + include/wlr/types/wlr_input_device.h | 51 + include/wlr/types/wlr_input_method_v2.h | 144 + include/wlr/types/wlr_keyboard.h | 142 + include/wlr/types/wlr_keyboard_group.h | 59 + .../types/wlr_keyboard_shortcuts_inhibit_v1.h | 85 + include/wlr/types/wlr_layer_shell_v1.h | 188 ++ include/wlr/types/wlr_linux_dmabuf_v1.h | 137 + include/wlr/types/wlr_matrix.h | 44 + include/wlr/types/wlr_output.h | 583 ++++ include/wlr/types/wlr_output_layer.h | 103 + include/wlr/types/wlr_output_layout.h | 166 ++ include/wlr/types/wlr_output_management_v1.h | 157 + .../types/wlr_output_power_management_v1.h | 41 + include/wlr/types/wlr_pointer.h | 147 + .../wlr/types/wlr_pointer_constraints_v1.h | 110 + include/wlr/types/wlr_pointer_gestures_v1.h | 82 + include/wlr/types/wlr_presentation_time.h | 106 + include/wlr/types/wlr_primary_selection.h | 68 + include/wlr/types/wlr_primary_selection_v1.h | 49 + include/wlr/types/wlr_region.h | 20 + include/wlr/types/wlr_relative_pointer_v1.h | 78 + include/wlr/types/wlr_scene.h | 594 ++++ include/wlr/types/wlr_screencopy_v1.h | 63 + include/wlr/types/wlr_seat.h | 752 +++++ include/wlr/types/wlr_security_context_v1.h | 55 + include/wlr/types/wlr_server_decoration.h | 84 + include/wlr/types/wlr_session_lock_v1.h | 108 + include/wlr/types/wlr_shm.h | 44 + .../wlr/types/wlr_single_pixel_buffer_v1.h | 19 + include/wlr/types/wlr_subcompositor.h | 83 + include/wlr/types/wlr_switch.h | 60 + include/wlr/types/wlr_tablet_pad.h | 103 + include/wlr/types/wlr_tablet_tool.h | 156 + include/wlr/types/wlr_tablet_v2.h | 332 +++ include/wlr/types/wlr_tearing_control_v1.h | 67 + include/wlr/types/wlr_text_input_v3.h | 99 + include/wlr/types/wlr_touch.h | 73 + include/wlr/types/wlr_transient_seat_v1.h | 63 + include/wlr/types/wlr_viewporter.h | 37 + include/wlr/types/wlr_virtual_keyboard_v1.h | 42 + include/wlr/types/wlr_virtual_pointer_v1.h | 50 + include/wlr/types/wlr_xcursor_manager.h | 59 + include/wlr/types/wlr_xdg_activation_v1.h | 89 + include/wlr/types/wlr_xdg_decoration_v1.h | 73 + include/wlr/types/wlr_xdg_foreign_registry.h | 75 + include/wlr/types/wlr_xdg_foreign_v1.h | 64 + include/wlr/types/wlr_xdg_foreign_v2.h | 64 + include/wlr/types/wlr_xdg_output_v1.h | 47 + include/wlr/types/wlr_xdg_shell.h | 556 ++++ include/wlr/util/addon.h | 44 + include/wlr/util/box.h | 114 + include/wlr/util/edges.h | 27 + include/wlr/util/log.h | 76 + include/wlr/util/region.h | 77 + include/wlr/util/transform.h | 33 + include/wlr/version.h.in | 12 + include/wlr/xcursor.h | 127 + include/wlr/xwayland.h | 2 + include/wlr/xwayland/server.h | 66 + include/wlr/xwayland/shell.h | 80 + include/wlr/xwayland/xwayland.h | 312 ++ include/xcursor/cursor_data.h | 570 ++++ include/xcursor/xcursor.h | 58 + include/xwayland/selection.h | 95 + include/xwayland/xwm.h | 165 + meson.build | 213 ++ meson_options.txt | 9 + protocol/drm.xml | 189 ++ protocol/input-method-unstable-v2.xml | 494 +++ protocol/meson.build | 98 + protocol/server-decoration.xml | 94 + protocol/virtual-keyboard-unstable-v1.xml | 113 + protocol/wlr-data-control-unstable-v1.xml | 278 ++ protocol/wlr-export-dmabuf-unstable-v1.xml | 203 ++ ...oreign-toplevel-management-unstable-v1.xml | 270 ++ protocol/wlr-gamma-control-unstable-v1.xml | 126 + protocol/wlr-layer-shell-unstable-v1.xml | 390 +++ .../wlr-output-management-unstable-v1.xml | 601 ++++ ...lr-output-power-management-unstable-v1.xml | 128 + protocol/wlr-screencopy-unstable-v1.xml | 232 ++ protocol/wlr-virtual-pointer-unstable-v1.xml | 152 + render/allocator/allocator.c | 186 ++ render/allocator/drm_dumb.c | 230 ++ render/allocator/gbm.c | 252 ++ render/allocator/meson.build | 25 + render/allocator/shm.c | 120 + render/dmabuf.c | 37 + render/dmabuf_fallback.c | 17 + render/dmabuf_linux.c | 99 + render/drm_format_set.c | 284 ++ render/egl.c | 1016 +++++++ render/gles2/meson.build | 17 + render/gles2/pass.c | 291 ++ render/gles2/pixel_format.c | 165 + render/gles2/renderer.c | 708 +++++ render/gles2/shaders/common.vert | 10 + render/gles2/shaders/embed.sh | 11 + render/gles2/shaders/meson.build | 22 + render/gles2/shaders/quad.frag | 13 + render/gles2/shaders/tex_external.frag | 15 + render/gles2/shaders/tex_rgba.frag | 13 + render/gles2/shaders/tex_rgbx.frag | 13 + render/gles2/texture.c | 441 +++ render/meson.build | 41 + render/pass.c | 76 + render/pixel_format.c | 277 ++ render/pixman/meson.build | 9 + render/pixman/pass.c | 213 ++ render/pixman/pixel_format.c | 131 + render/pixman/renderer.c | 362 +++ render/swapchain.c | 145 + render/vulkan/meson.build | 51 + render/vulkan/pass.c | 712 +++++ render/vulkan/pixel_format.c | 591 ++++ render/vulkan/renderer.c | 2261 ++++++++++++++ render/vulkan/shaders/common.vert | 18 + render/vulkan/shaders/meson.build | 24 + render/vulkan/shaders/output.frag | 29 + render/vulkan/shaders/quad.frag | 10 + render/vulkan/shaders/texture.frag | 47 + render/vulkan/texture.c | 851 ++++++ render/vulkan/util.c | 78 + render/vulkan/vulkan.c | 668 +++++ render/wlr_renderer.c | 337 +++ render/wlr_texture.c | 137 + tinywl/.gitignore | 3 + tinywl/LICENSE | 125 + tinywl/Makefile | 26 + tinywl/README.md | 45 + tinywl/meson.build | 5 + tinywl/tinywl.c | 1071 +++++++ types/buffer/buffer.c | 130 + types/buffer/client.c | 93 + types/buffer/dmabuf.c | 70 + types/buffer/readonly_data.c | 88 + types/buffer/resource.c | 61 + types/data_device/wlr_data_device.c | 316 ++ types/data_device/wlr_data_offer.c | 284 ++ types/data_device/wlr_data_source.c | 272 ++ types/data_device/wlr_drag.c | 515 ++++ types/meson.build | 101 + types/output/cursor.c | 436 +++ types/output/output.c | 887 ++++++ types/output/render.c | 223 ++ types/output/state.c | 152 + types/output/swapchain.c | 118 + types/scene/drag_icon.c | 93 + types/scene/layer_shell_v1.c | 186 ++ types/scene/output_layout.c | 141 + types/scene/subsurface_tree.c | 370 +++ types/scene/surface.c | 291 ++ types/scene/wlr_scene.c | 2010 +++++++++++++ types/scene/xdg_shell.c | 129 + types/seat/wlr_seat.c | 492 +++ types/seat/wlr_seat_keyboard.c | 478 +++ types/seat/wlr_seat_pointer.c | 585 ++++ types/seat/wlr_seat_touch.c | 495 +++ types/tablet_v2/wlr_tablet_v2.c | 300 ++ types/tablet_v2/wlr_tablet_v2_pad.c | 703 +++++ types/tablet_v2/wlr_tablet_v2_tablet.c | 131 + types/tablet_v2/wlr_tablet_v2_tool.c | 837 ++++++ types/wlr_compositor.c | 1501 ++++++++++ types/wlr_content_type_v1.c | 198 ++ types/wlr_cursor.c | 1213 ++++++++ types/wlr_cursor_shape_v1.c | 262 ++ types/wlr_damage_ring.c | 202 ++ types/wlr_data_control_v1.c | 696 +++++ types/wlr_drm.c | 266 ++ types/wlr_drm_lease_v1.c | 724 +++++ types/wlr_export_dmabuf_v1.c | 221 ++ types/wlr_ext_foreign_toplevel_list_v1.c | 265 ++ types/wlr_foreign_toplevel_management_v1.c | 708 +++++ types/wlr_fractional_scale_v1.c | 205 ++ types/wlr_fullscreen_shell_v1.c | 138 + types/wlr_gamma_control_v1.c | 279 ++ types/wlr_idle_inhibit_v1.c | 158 + types/wlr_idle_notify_v1.c | 255 ++ types/wlr_input_device.c | 26 + types/wlr_input_method_v2.c | 625 ++++ types/wlr_keyboard.c | 307 ++ types/wlr_keyboard_group.c | 318 ++ types/wlr_keyboard_shortcuts_inhibit_v1.c | 227 ++ types/wlr_layer_shell_v1.c | 623 ++++ types/wlr_linux_dmabuf_v1.c | 1173 ++++++++ types/wlr_matrix.c | 169 ++ types/wlr_output_layer.c | 25 + types/wlr_output_layout.c | 488 +++ types/wlr_output_management_v1.c | 1029 +++++++ types/wlr_output_power_management_v1.c | 225 ++ types/wlr_pointer.c | 42 + types/wlr_pointer_constraints_v1.c | 399 +++ types/wlr_pointer_gestures_v1.c | 427 +++ types/wlr_presentation_time.c | 329 ++ types/wlr_primary_selection.c | 102 + types/wlr_primary_selection_v1.c | 492 +++ types/wlr_region.c | 75 + types/wlr_relative_pointer_v1.c | 203 ++ types/wlr_screencopy_v1.c | 720 +++++ types/wlr_security_context_v1.c | 442 +++ types/wlr_server_decoration.c | 195 ++ types/wlr_session_lock_v1.c | 478 +++ types/wlr_shm.c | 570 ++++ types/wlr_single_pixel_buffer_v1.c | 185 ++ types/wlr_subcompositor.c | 437 +++ types/wlr_switch.c | 28 + types/wlr_tablet_pad.c | 46 + types/wlr_tablet_tool.c | 38 + types/wlr_tearing_control_v1.c | 223 ++ types/wlr_text_input_v3.c | 337 +++ types/wlr_touch.c | 34 + types/wlr_transient_seat_v1.c | 160 + types/wlr_viewporter.c | 256 ++ types/wlr_virtual_keyboard_v1.c | 242 ++ types/wlr_virtual_pointer_v1.c | 343 +++ types/wlr_xcursor_manager.c | 65 + types/wlr_xdg_activation_v1.c | 427 +++ types/wlr_xdg_decoration_v1.c | 285 ++ types/wlr_xdg_foreign_registry.c | 72 + types/wlr_xdg_foreign_v1.c | 420 +++ types/wlr_xdg_foreign_v2.c | 423 +++ types/wlr_xdg_output_v1.c | 291 ++ types/xdg_shell/wlr_xdg_popup.c | 532 ++++ types/xdg_shell/wlr_xdg_positioner.c | 503 ++++ types/xdg_shell/wlr_xdg_shell.c | 169 ++ types/xdg_shell/wlr_xdg_surface.c | 625 ++++ types/xdg_shell/wlr_xdg_toplevel.c | 613 ++++ util/addon.c | 55 + util/array.c | 40 + util/box.c | 212 ++ util/env.c | 38 + util/global.c | 57 + util/log.c | 107 + util/meson.build | 16 + util/rect_union.c | 91 + util/region.c | 256 ++ util/set.c | 25 + util/shm.c | 97 + util/time.c | 35 + util/token.c | 39 + util/transform.c | 33 + util/utf8.c | 66 + wlroots.syms | 7 + xcursor/meson.build | 10 + xcursor/wlr_xcursor.c | 360 +++ xcursor/xcursor.c | 787 +++++ xwayland/meson.build | 98 + xwayland/selection/dnd.c | 338 +++ xwayland/selection/incoming.c | 539 ++++ xwayland/selection/outgoing.c | 471 +++ xwayland/selection/selection.c | 345 +++ xwayland/server.c | 502 ++++ xwayland/shell.c | 237 ++ xwayland/sockets.c | 222 ++ xwayland/sockets.h | 10 + xwayland/xwayland.c | 220 ++ xwayland/xwm.c | 2387 +++++++++++++++ 421 files changed, 93083 insertions(+) create mode 100644 .builds/alpine.yml create mode 100644 .builds/archlinux.yml create mode 100644 .builds/freebsd.yml create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .gitlab/issue_templates/Default.md create mode 100644 .mailmap create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 backend/backend.c create mode 100644 backend/drm/atomic.c create mode 100644 backend/drm/backend.c create mode 100644 backend/drm/drm.c create mode 100644 backend/drm/fb.c create mode 100755 backend/drm/gen_pnpids.sh create mode 100644 backend/drm/legacy.c create mode 100644 backend/drm/libliftoff.c create mode 100644 backend/drm/meson.build create mode 100644 backend/drm/monitor.c create mode 100644 backend/drm/properties.c create mode 100644 backend/drm/renderer.c create mode 100644 backend/drm/util.c create mode 100644 backend/headless/backend.c create mode 100644 backend/headless/meson.build create mode 100644 backend/headless/output.c create mode 100644 backend/libinput/backend.c create mode 100644 backend/libinput/events.c create mode 100644 backend/libinput/keyboard.c create mode 100644 backend/libinput/meson.build create mode 100644 backend/libinput/pointer.c create mode 100644 backend/libinput/switch.c create mode 100644 backend/libinput/tablet_pad.c create mode 100644 backend/libinput/tablet_tool.c create mode 100644 backend/libinput/touch.c create mode 100644 backend/meson.build create mode 100644 backend/multi/backend.c create mode 100644 backend/multi/meson.build create mode 100644 backend/session/meson.build create mode 100644 backend/session/session.c create mode 100644 backend/wayland/backend.c create mode 100644 backend/wayland/meson.build create mode 100644 backend/wayland/output.c create mode 100644 backend/wayland/pointer.c create mode 100644 backend/wayland/seat.c create mode 100644 backend/wayland/tablet_v2.c create mode 100644 backend/x11/backend.c create mode 100644 backend/x11/input_device.c create mode 100644 backend/x11/meson.build create mode 100644 backend/x11/output.c create mode 100644 docs/architecture.md create mode 100644 docs/env_vars.md create mode 100644 examples/.gitignore create mode 100644 examples/cairo-buffer.c create mode 100644 examples/cat.c create mode 100644 examples/cat.h create mode 100644 examples/embedded.c create mode 100644 examples/fullscreen-shell.c create mode 100644 examples/meson.build create mode 100644 examples/output-layers.c create mode 100644 examples/output-layout.c create mode 100644 examples/pointer.c create mode 100644 examples/rotation.c create mode 100644 examples/scene-graph.c create mode 100644 examples/simple.c create mode 100644 examples/tablet.c create mode 100644 examples/touch.c create mode 100644 include/backend/backend.h create mode 100644 include/backend/drm/drm.h create mode 100644 include/backend/drm/fb.h create mode 100644 include/backend/drm/iface.h create mode 100644 include/backend/drm/monitor.h create mode 100644 include/backend/drm/properties.h create mode 100644 include/backend/drm/renderer.h create mode 100644 include/backend/drm/util.h create mode 100644 include/backend/headless.h create mode 100644 include/backend/libinput.h create mode 100644 include/backend/multi.h create mode 100644 include/backend/session/session.h create mode 100644 include/backend/wayland.h create mode 100644 include/backend/x11.h create mode 100644 include/interfaces/wlr_input_device.h create mode 100644 include/meson.build create mode 100644 include/render/allocator/allocator.h create mode 100644 include/render/allocator/drm_dumb.h create mode 100644 include/render/allocator/gbm.h create mode 100644 include/render/allocator/shm.h create mode 100644 include/render/dmabuf.h create mode 100644 include/render/drm_format_set.h create mode 100644 include/render/egl.h create mode 100644 include/render/gles2.h create mode 100644 include/render/pixel_format.h create mode 100644 include/render/pixman.h create mode 100644 include/render/vulkan.h create mode 100644 include/render/wlr_renderer.h create mode 100644 include/types/wlr_buffer.h create mode 100644 include/types/wlr_data_device.h create mode 100644 include/types/wlr_keyboard.h create mode 100644 include/types/wlr_matrix.h create mode 100644 include/types/wlr_output.h create mode 100644 include/types/wlr_region.h create mode 100644 include/types/wlr_scene.h create mode 100644 include/types/wlr_seat.h create mode 100644 include/types/wlr_subcompositor.h create mode 100644 include/types/wlr_tablet_v2.h create mode 100644 include/types/wlr_xdg_shell.h create mode 100644 include/util/array.h create mode 100644 include/util/env.h create mode 100644 include/util/global.h create mode 100644 include/util/rect_union.h create mode 100644 include/util/set.h create mode 100644 include/util/shm.h create mode 100644 include/util/time.h create mode 100644 include/util/token.h create mode 100644 include/util/utf8.h create mode 100644 include/wlr/backend.h create mode 100644 include/wlr/backend/drm.h create mode 100644 include/wlr/backend/headless.h create mode 100644 include/wlr/backend/interface.h create mode 100644 include/wlr/backend/libinput.h create mode 100644 include/wlr/backend/multi.h create mode 100644 include/wlr/backend/session.h create mode 100644 include/wlr/backend/wayland.h create mode 100644 include/wlr/backend/x11.h create mode 100644 include/wlr/config.h.in create mode 100644 include/wlr/interfaces/wlr_buffer.h create mode 100644 include/wlr/interfaces/wlr_keyboard.h create mode 100644 include/wlr/interfaces/wlr_output.h create mode 100644 include/wlr/interfaces/wlr_pointer.h create mode 100644 include/wlr/interfaces/wlr_switch.h create mode 100644 include/wlr/interfaces/wlr_tablet_pad.h create mode 100644 include/wlr/interfaces/wlr_tablet_tool.h create mode 100644 include/wlr/interfaces/wlr_touch.h create mode 100644 include/wlr/meson.build create mode 100644 include/wlr/render/allocator.h create mode 100644 include/wlr/render/dmabuf.h create mode 100644 include/wlr/render/drm_format_set.h create mode 100644 include/wlr/render/egl.h create mode 100644 include/wlr/render/gles2.h create mode 100644 include/wlr/render/interface.h create mode 100644 include/wlr/render/pass.h create mode 100644 include/wlr/render/pixman.h create mode 100644 include/wlr/render/swapchain.h create mode 100644 include/wlr/render/vulkan.h create mode 100644 include/wlr/render/wlr_renderer.h create mode 100644 include/wlr/render/wlr_texture.h create mode 100644 include/wlr/types/wlr_buffer.h create mode 100644 include/wlr/types/wlr_compositor.h create mode 100644 include/wlr/types/wlr_content_type_v1.h create mode 100644 include/wlr/types/wlr_cursor.h create mode 100644 include/wlr/types/wlr_cursor_shape_v1.h create mode 100644 include/wlr/types/wlr_damage_ring.h create mode 100644 include/wlr/types/wlr_data_control_v1.h create mode 100644 include/wlr/types/wlr_data_device.h create mode 100644 include/wlr/types/wlr_drm.h create mode 100644 include/wlr/types/wlr_drm_lease_v1.h create mode 100644 include/wlr/types/wlr_export_dmabuf_v1.h create mode 100644 include/wlr/types/wlr_ext_foreign_toplevel_list_v1.h create mode 100644 include/wlr/types/wlr_foreign_toplevel_management_v1.h create mode 100644 include/wlr/types/wlr_fractional_scale_v1.h create mode 100644 include/wlr/types/wlr_fullscreen_shell_v1.h create mode 100644 include/wlr/types/wlr_gamma_control_v1.h create mode 100644 include/wlr/types/wlr_idle_inhibit_v1.h create mode 100644 include/wlr/types/wlr_idle_notify_v1.h create mode 100644 include/wlr/types/wlr_input_device.h create mode 100644 include/wlr/types/wlr_input_method_v2.h create mode 100644 include/wlr/types/wlr_keyboard.h create mode 100644 include/wlr/types/wlr_keyboard_group.h create mode 100644 include/wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h create mode 100644 include/wlr/types/wlr_layer_shell_v1.h create mode 100644 include/wlr/types/wlr_linux_dmabuf_v1.h create mode 100644 include/wlr/types/wlr_matrix.h create mode 100644 include/wlr/types/wlr_output.h create mode 100644 include/wlr/types/wlr_output_layer.h create mode 100644 include/wlr/types/wlr_output_layout.h create mode 100644 include/wlr/types/wlr_output_management_v1.h create mode 100644 include/wlr/types/wlr_output_power_management_v1.h create mode 100644 include/wlr/types/wlr_pointer.h create mode 100644 include/wlr/types/wlr_pointer_constraints_v1.h create mode 100644 include/wlr/types/wlr_pointer_gestures_v1.h create mode 100644 include/wlr/types/wlr_presentation_time.h create mode 100644 include/wlr/types/wlr_primary_selection.h create mode 100644 include/wlr/types/wlr_primary_selection_v1.h create mode 100644 include/wlr/types/wlr_region.h create mode 100644 include/wlr/types/wlr_relative_pointer_v1.h create mode 100644 include/wlr/types/wlr_scene.h create mode 100644 include/wlr/types/wlr_screencopy_v1.h create mode 100644 include/wlr/types/wlr_seat.h create mode 100644 include/wlr/types/wlr_security_context_v1.h create mode 100644 include/wlr/types/wlr_server_decoration.h create mode 100644 include/wlr/types/wlr_session_lock_v1.h create mode 100644 include/wlr/types/wlr_shm.h create mode 100644 include/wlr/types/wlr_single_pixel_buffer_v1.h create mode 100644 include/wlr/types/wlr_subcompositor.h create mode 100644 include/wlr/types/wlr_switch.h create mode 100644 include/wlr/types/wlr_tablet_pad.h create mode 100644 include/wlr/types/wlr_tablet_tool.h create mode 100644 include/wlr/types/wlr_tablet_v2.h create mode 100644 include/wlr/types/wlr_tearing_control_v1.h create mode 100644 include/wlr/types/wlr_text_input_v3.h create mode 100644 include/wlr/types/wlr_touch.h create mode 100644 include/wlr/types/wlr_transient_seat_v1.h create mode 100644 include/wlr/types/wlr_viewporter.h create mode 100644 include/wlr/types/wlr_virtual_keyboard_v1.h create mode 100644 include/wlr/types/wlr_virtual_pointer_v1.h create mode 100644 include/wlr/types/wlr_xcursor_manager.h create mode 100644 include/wlr/types/wlr_xdg_activation_v1.h create mode 100644 include/wlr/types/wlr_xdg_decoration_v1.h create mode 100644 include/wlr/types/wlr_xdg_foreign_registry.h create mode 100644 include/wlr/types/wlr_xdg_foreign_v1.h create mode 100644 include/wlr/types/wlr_xdg_foreign_v2.h create mode 100644 include/wlr/types/wlr_xdg_output_v1.h create mode 100644 include/wlr/types/wlr_xdg_shell.h create mode 100644 include/wlr/util/addon.h create mode 100644 include/wlr/util/box.h create mode 100644 include/wlr/util/edges.h create mode 100644 include/wlr/util/log.h create mode 100644 include/wlr/util/region.h create mode 100644 include/wlr/util/transform.h create mode 100644 include/wlr/version.h.in create mode 100644 include/wlr/xcursor.h create mode 100644 include/wlr/xwayland.h create mode 100644 include/wlr/xwayland/server.h create mode 100644 include/wlr/xwayland/shell.h create mode 100644 include/wlr/xwayland/xwayland.h create mode 100644 include/xcursor/cursor_data.h create mode 100644 include/xcursor/xcursor.h create mode 100644 include/xwayland/selection.h create mode 100644 include/xwayland/xwm.h create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 protocol/drm.xml create mode 100644 protocol/input-method-unstable-v2.xml create mode 100644 protocol/meson.build create mode 100644 protocol/server-decoration.xml create mode 100644 protocol/virtual-keyboard-unstable-v1.xml create mode 100644 protocol/wlr-data-control-unstable-v1.xml create mode 100644 protocol/wlr-export-dmabuf-unstable-v1.xml create mode 100644 protocol/wlr-foreign-toplevel-management-unstable-v1.xml create mode 100644 protocol/wlr-gamma-control-unstable-v1.xml create mode 100644 protocol/wlr-layer-shell-unstable-v1.xml create mode 100644 protocol/wlr-output-management-unstable-v1.xml create mode 100644 protocol/wlr-output-power-management-unstable-v1.xml create mode 100644 protocol/wlr-screencopy-unstable-v1.xml create mode 100644 protocol/wlr-virtual-pointer-unstable-v1.xml create mode 100644 render/allocator/allocator.c create mode 100644 render/allocator/drm_dumb.c create mode 100644 render/allocator/gbm.c create mode 100644 render/allocator/meson.build create mode 100644 render/allocator/shm.c create mode 100644 render/dmabuf.c create mode 100644 render/dmabuf_fallback.c create mode 100644 render/dmabuf_linux.c create mode 100644 render/drm_format_set.c create mode 100644 render/egl.c create mode 100644 render/gles2/meson.build create mode 100644 render/gles2/pass.c create mode 100644 render/gles2/pixel_format.c create mode 100644 render/gles2/renderer.c create mode 100644 render/gles2/shaders/common.vert create mode 100755 render/gles2/shaders/embed.sh create mode 100644 render/gles2/shaders/meson.build create mode 100644 render/gles2/shaders/quad.frag create mode 100644 render/gles2/shaders/tex_external.frag create mode 100644 render/gles2/shaders/tex_rgba.frag create mode 100644 render/gles2/shaders/tex_rgbx.frag create mode 100644 render/gles2/texture.c create mode 100644 render/meson.build create mode 100644 render/pass.c create mode 100644 render/pixel_format.c create mode 100644 render/pixman/meson.build create mode 100644 render/pixman/pass.c create mode 100644 render/pixman/pixel_format.c create mode 100644 render/pixman/renderer.c create mode 100644 render/swapchain.c create mode 100644 render/vulkan/meson.build create mode 100644 render/vulkan/pass.c create mode 100644 render/vulkan/pixel_format.c create mode 100644 render/vulkan/renderer.c create mode 100644 render/vulkan/shaders/common.vert create mode 100644 render/vulkan/shaders/meson.build create mode 100644 render/vulkan/shaders/output.frag create mode 100644 render/vulkan/shaders/quad.frag create mode 100644 render/vulkan/shaders/texture.frag create mode 100644 render/vulkan/texture.c create mode 100644 render/vulkan/util.c create mode 100644 render/vulkan/vulkan.c create mode 100644 render/wlr_renderer.c create mode 100644 render/wlr_texture.c create mode 100644 tinywl/.gitignore create mode 100644 tinywl/LICENSE create mode 100644 tinywl/Makefile create mode 100644 tinywl/README.md create mode 100644 tinywl/meson.build create mode 100644 tinywl/tinywl.c create mode 100644 types/buffer/buffer.c create mode 100644 types/buffer/client.c create mode 100644 types/buffer/dmabuf.c create mode 100644 types/buffer/readonly_data.c create mode 100644 types/buffer/resource.c create mode 100644 types/data_device/wlr_data_device.c create mode 100644 types/data_device/wlr_data_offer.c create mode 100644 types/data_device/wlr_data_source.c create mode 100644 types/data_device/wlr_drag.c create mode 100644 types/meson.build create mode 100644 types/output/cursor.c create mode 100644 types/output/output.c create mode 100644 types/output/render.c create mode 100644 types/output/state.c create mode 100644 types/output/swapchain.c create mode 100644 types/scene/drag_icon.c create mode 100644 types/scene/layer_shell_v1.c create mode 100644 types/scene/output_layout.c create mode 100644 types/scene/subsurface_tree.c create mode 100644 types/scene/surface.c create mode 100644 types/scene/wlr_scene.c create mode 100644 types/scene/xdg_shell.c create mode 100644 types/seat/wlr_seat.c create mode 100644 types/seat/wlr_seat_keyboard.c create mode 100644 types/seat/wlr_seat_pointer.c create mode 100644 types/seat/wlr_seat_touch.c create mode 100644 types/tablet_v2/wlr_tablet_v2.c create mode 100644 types/tablet_v2/wlr_tablet_v2_pad.c create mode 100644 types/tablet_v2/wlr_tablet_v2_tablet.c create mode 100644 types/tablet_v2/wlr_tablet_v2_tool.c create mode 100644 types/wlr_compositor.c create mode 100644 types/wlr_content_type_v1.c create mode 100644 types/wlr_cursor.c create mode 100644 types/wlr_cursor_shape_v1.c create mode 100644 types/wlr_damage_ring.c create mode 100644 types/wlr_data_control_v1.c create mode 100644 types/wlr_drm.c create mode 100644 types/wlr_drm_lease_v1.c create mode 100644 types/wlr_export_dmabuf_v1.c create mode 100644 types/wlr_ext_foreign_toplevel_list_v1.c create mode 100644 types/wlr_foreign_toplevel_management_v1.c create mode 100644 types/wlr_fractional_scale_v1.c create mode 100644 types/wlr_fullscreen_shell_v1.c create mode 100644 types/wlr_gamma_control_v1.c create mode 100644 types/wlr_idle_inhibit_v1.c create mode 100644 types/wlr_idle_notify_v1.c create mode 100644 types/wlr_input_device.c create mode 100644 types/wlr_input_method_v2.c create mode 100644 types/wlr_keyboard.c create mode 100644 types/wlr_keyboard_group.c create mode 100644 types/wlr_keyboard_shortcuts_inhibit_v1.c create mode 100644 types/wlr_layer_shell_v1.c create mode 100644 types/wlr_linux_dmabuf_v1.c create mode 100644 types/wlr_matrix.c create mode 100644 types/wlr_output_layer.c create mode 100644 types/wlr_output_layout.c create mode 100644 types/wlr_output_management_v1.c create mode 100644 types/wlr_output_power_management_v1.c create mode 100644 types/wlr_pointer.c create mode 100644 types/wlr_pointer_constraints_v1.c create mode 100644 types/wlr_pointer_gestures_v1.c create mode 100644 types/wlr_presentation_time.c create mode 100644 types/wlr_primary_selection.c create mode 100644 types/wlr_primary_selection_v1.c create mode 100644 types/wlr_region.c create mode 100644 types/wlr_relative_pointer_v1.c create mode 100644 types/wlr_screencopy_v1.c create mode 100644 types/wlr_security_context_v1.c create mode 100644 types/wlr_server_decoration.c create mode 100644 types/wlr_session_lock_v1.c create mode 100644 types/wlr_shm.c create mode 100644 types/wlr_single_pixel_buffer_v1.c create mode 100644 types/wlr_subcompositor.c create mode 100644 types/wlr_switch.c create mode 100644 types/wlr_tablet_pad.c create mode 100644 types/wlr_tablet_tool.c create mode 100644 types/wlr_tearing_control_v1.c create mode 100644 types/wlr_text_input_v3.c create mode 100644 types/wlr_touch.c create mode 100644 types/wlr_transient_seat_v1.c create mode 100644 types/wlr_viewporter.c create mode 100644 types/wlr_virtual_keyboard_v1.c create mode 100644 types/wlr_virtual_pointer_v1.c create mode 100644 types/wlr_xcursor_manager.c create mode 100644 types/wlr_xdg_activation_v1.c create mode 100644 types/wlr_xdg_decoration_v1.c create mode 100644 types/wlr_xdg_foreign_registry.c create mode 100644 types/wlr_xdg_foreign_v1.c create mode 100644 types/wlr_xdg_foreign_v2.c create mode 100644 types/wlr_xdg_output_v1.c create mode 100644 types/xdg_shell/wlr_xdg_popup.c create mode 100644 types/xdg_shell/wlr_xdg_positioner.c create mode 100644 types/xdg_shell/wlr_xdg_shell.c create mode 100644 types/xdg_shell/wlr_xdg_surface.c create mode 100644 types/xdg_shell/wlr_xdg_toplevel.c create mode 100644 util/addon.c create mode 100644 util/array.c create mode 100644 util/box.c create mode 100644 util/env.c create mode 100644 util/global.c create mode 100644 util/log.c create mode 100644 util/meson.build create mode 100644 util/rect_union.c create mode 100644 util/region.c create mode 100644 util/set.c create mode 100644 util/shm.c create mode 100644 util/time.c create mode 100644 util/token.c create mode 100644 util/transform.c create mode 100644 util/utf8.c create mode 100644 wlroots.syms create mode 100644 xcursor/meson.build create mode 100644 xcursor/wlr_xcursor.c create mode 100644 xcursor/xcursor.c create mode 100644 xwayland/meson.build create mode 100644 xwayland/selection/dnd.c create mode 100644 xwayland/selection/incoming.c create mode 100644 xwayland/selection/outgoing.c create mode 100644 xwayland/selection/selection.c create mode 100644 xwayland/server.c create mode 100644 xwayland/shell.c create mode 100644 xwayland/sockets.c create mode 100644 xwayland/sockets.h create mode 100644 xwayland/xwayland.c create mode 100644 xwayland/xwm.c diff --git a/.builds/alpine.yml b/.builds/alpine.yml new file mode 100644 index 0000000..72b9448 --- /dev/null +++ b/.builds/alpine.yml @@ -0,0 +1,38 @@ +image: alpine/edge +packages: + - eudev-dev + - glslang + - libdisplay-info-dev + - libinput-dev + - libliftoff-dev + - libxkbcommon-dev + - mesa-dev + - meson + - pixman-dev + - vulkan-headers + - vulkan-loader-dev + - wayland-dev + - wayland-protocols + - xcb-util-image-dev + - xcb-util-renderutil-dev + - xcb-util-wm-dev + - xwayland-dev + - libseat-dev + - hwdata-dev +sources: + - https://gitlab.freedesktop.org/wlroots/wlroots.git +tasks: + - setup: | + cd wlroots + meson setup build --fatal-meson-warnings --default-library=both -Dauto_features=enabled -Dxcb-errors=disabled + - build: | + cd wlroots + ninja -C build + sudo ninja -C build install + - build-features-disabled: | + cd wlroots + meson build --reconfigure -Dauto_features=disabled + ninja -C build + - tinywl: | + cd wlroots/tinywl + make diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml new file mode 100644 index 0000000..3e15522 --- /dev/null +++ b/.builds/archlinux.yml @@ -0,0 +1,48 @@ +image: archlinux +packages: + - clang + - libinput + - libdisplay-info + - libliftoff + - libxkbcommon + - mesa + - meson + - pixman + - wayland + - wayland-protocols + - xcb-util-errors + - xcb-util-image + - xcb-util-renderutil + - xcb-util-wm + - xorg-xwayland + - seatd + - vulkan-icd-loader + - vulkan-headers + - glslang + - hwdata +sources: + - https://gitlab.freedesktop.org/wlroots/wlroots.git +tasks: + - setup: | + cd wlroots + CC=gcc meson setup build-gcc --fatal-meson-warnings --default-library=both -Dauto_features=enabled --prefix /usr -Db_sanitize=address,undefined + CC=clang meson setup build-clang --fatal-meson-warnings -Dauto_features=enabled + - gcc: | + cd wlroots/build-gcc + ninja + sudo ninja install + cd ../tinywl + CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" make + - clang: | + cd wlroots/build-clang + ninja + - smoke-test: | + cd wlroots/tinywl + sudo modprobe vkms + udevadm settle + export WLR_BACKENDS=drm + export WLR_RENDERER=pixman + export WLR_DRM_DEVICES=/dev/dri/by-path/platform-vkms-card + export UBSAN_OPTIONS=halt_on_error=1 + sudo chmod ugo+rw /dev/dri/by-path/platform-vkms-card + sudo -E seatd-launch -- ./tinywl -s 'kill $PPID' || [ $? = 143 ] diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml new file mode 100644 index 0000000..bb24105 --- /dev/null +++ b/.builds/freebsd.yml @@ -0,0 +1,37 @@ +image: freebsd/latest +packages: + - devel/evdev-proto + - devel/libudev-devd + - devel/meson # implies ninja + - devel/pkgconf + - graphics/glslang + - graphics/libdrm + - graphics/libliftoff + - graphics/mesa-libs + - graphics/vulkan-headers + - graphics/vulkan-loader + - graphics/wayland + - graphics/wayland-protocols + - x11/libinput + - x11/libxcb + - x11/libxkbcommon + - x11/pixman + - x11/xcb-util-errors + - x11/xcb-util-renderutil + - x11/xcb-util-wm + - x11-servers/xwayland-devel + - sysutils/libdisplay-info + - sysutils/seatd + - gmake + - hwdata +sources: + - https://gitlab.freedesktop.org/wlroots/wlroots.git +tasks: + - wlroots: | + cd wlroots + meson setup build --fatal-meson-warnings -Dauto_features=enabled + ninja -C build + sudo ninja -C build install + - tinywl: | + cd wlroots/tinywl + gmake diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..694a7ea --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = tab +indent_size = 4 +max_line_length = 100 + +[*.xml] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d73983b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/subprojects/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..b0942ab --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,7 @@ +include: https://git.sr.ht/~emersion/dalligi/blob/master/templates/multi.yml +alpine: + extends: .dalligi +archlinux: + extends: .dalligi +freebsd: + extends: .dalligi diff --git a/.gitlab/issue_templates/Default.md b/.gitlab/issue_templates/Default.md new file mode 100644 index 0000000..68044e1 --- /dev/null +++ b/.gitlab/issue_templates/Default.md @@ -0,0 +1,15 @@ + diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..0e050b3 --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Isaac Freund diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3d13f5f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,449 @@ +# Contributing to wlroots + +Contributing just involves sending a merge request. You will probably be more +successful with your contribution if you visit [#wlroots on Libera Chat] +upfront and discuss your plans. + +Note: rules are made to be broken. Adjust or ignore any/all of these as you see +fit, but be prepared to justify it to your peers. + +## Forking + +New GitLab accounts may not have the permission to fork repositories. You will +need to [file a user verification request] to get this permission. See the +[freedesktop wiki] for more details. + +The fork must be marked as public to allow CI to run. + +## Merge Requests + +If you already have your own merge request habits, feel free to use them. If you +don't, however, allow me to make a suggestion: feature branches pulled from +upstream. Try this: + +1. Fork wlroots +2. `git clone git@gitlab.freedesktop.org:/wlroots.git && cd wlroots` +3. `git remote add upstream https://gitlab.freedesktop.org/wlroots/wlroots.git` + +You only need to do this once. You're never going to use your fork's master +branch. Instead, when you start working on a feature, do this: + +1. `git fetch upstream` +2. `git checkout -b add-so-and-so-feature upstream/master` +3. Add and commit your changes +4. `git push -u origin add-so-and-so-feature` +5. Make a merge request from your feature branch + +When you submit your merge request, your commit log should do most of the talking +when it comes to describing your changes and their motivation. In addition to +this, your merge request's comments will ideally include a test plan that the +reviewers can use to (1) demonstrate the problem on master, if applicable and +(2) verify that the problem no longer exists with your changes applied (or that +your new features work correctly). Document all of the edge cases you're aware +of so we can adequately test them - then verify the test plan yourself before +submitting. + +## Commit Log + +Unlike many projects using GitHub and GitLab, wlroots has a [linear, "recipe" +style] history. This means that every commit should be small, digestible, +stand-alone, and functional. Rather than a purely chronological commit history +like this: + +``` +doc: final docs for view transforms +fix tests when disabled, redo broken doc formatting +better transformed-view iteration (thanks Hannah!) +try to catch more cases in tests +tests: add new spline test +fix compilation on splines +doc: notes on reticulating splines +compositor: add spline reticulation for view transforms +``` + +We aim to have a clean history which only reflects the final state, broken up +into functional groupings: + +``` +compositor: add spline reticulation for view transforms +compositor: new iterator for view transforms +tests: add view-transform correctness tests +doc: fix formatting for view transforms +``` + +This ensures that the final patch series only contains the final state, +without the changes and missteps taken along the development process. A linear +history eases reviewing, cherry-picking and reverting changes. + +If you aren't comfortable with manipulating the Git history, have a look at +[git-rebase.io]. + +## Commit Messages + +Please strive to write good commit messages. Here's some guidelines to follow: + +The first line should be limited to 50 characters and should be a sentence that +completes the thought [When applied, this commit will...] *"Implement +cmd_move"* or *"Improve performance of arrange_windows on ARM"* or similar. + +The subsequent lines should be separated from the subject line by a single +blank line, and include optional details. In this you can give justification +for the change, [reference issues], or explain some of the subtler +details of your patch. This is important because when someone finds a line of +code they don't understand later, they can use the `git blame` command to find +out what the author was thinking when they wrote it. It's also easier to review +your merge requests if they're separated into logical commits that have good +commit messages and justify themselves in the extended commit description. + +As a good rule of thumb, anything you might put into the merge request +description on GitLab is probably fair game for going into the extended commit +message as well. + +See [How to Write a Git Commit Message] for more details. + +## Code Review + +When your changes are submitted for review, one or more core committers will +look over them. Smaller changes might be merged with little fanfare, but larger +changes will typically see review from several people. Be prepared to receive +some feedback - you may be asked to make changes to your work. Our code review +process is: + +1. **Triage** the merge request. Do the commit messages make sense? Is a test + plan necessary and/or present? Add anyone as reviewers that you think should + be there (using the relevant GitLab feature, if you have the permissions, or + with an @mention if necessary). +2. **Review** the code. Look for code style violations, naming convention + violations, buffer overflows, memory leaks, logic errors, non-portable code + (including GNU-isms), etc. For significant changes to the public API, loop in + a couple more people for discussion. +3. **Execute** the test plan, if present. +4. **Merge** the merge request when all reviewers approve. +5. **File** follow-up tickets if appropriate. + +## Code of Conduct + +Note that as a project hosted on freedesktop.org, wlroots follows its +[Code of Conduct], based on the Contributor Covenant. Please conduct yourself +in a respectful and civilized manner when communicating with community members +on IRC and bug tracker. + +## Style Reference + +wlroots is written in C with a style similar to the [kernel style], but with a +few notable differences. + +Try to keep your code conforming to C11 and POSIX as much as possible, and do +not use GNU extensions. + +### Brackets + +Brackets always go on the same line, including in functions. +Always include brackets for if/while/for, even if it's a single statement. +```c +void function(void) { + if (condition1) { + do_thing1(); + } + + if (condition2) { + do_thing2(); + } else { + do_thing3(); + } +} +``` + +### Indentation + +Indentations are a single tab. + +For long lines that need to be broken, the continuation line should be indented +with an additional tab. +If the line being broken is opening a new block (functions, if, while, etc.), +the continuation line should be indented with two tabs, so they can't be +misread as being part of the block. +```c +really_long_function(argument1, argument2, ..., + argument3, argument4); + +if (condition1 && condition2 && ... + condition3 && condition4) { + do_thing(); +} +``` + +Try to break the line in the place which you think is the most appropriate. + +### Line Length + +Try to keep your lines under 100 columns, but you can break this rule if it +improves readability. Don't break lines indiscriminately, try to find nice +breaking points so your code is easy to read. + +### Names + +Global function and type names should be prefixed with `wlr_submodule_` (e.g. +`struct wlr_output`, `wlr_output_set_cursor`). For static functions and +types local to a file, the names chosen aren't as important. Local function +names shouldn't have a `wlr_` prefix. + +For include guards, use the header's filename relative to include. Uppercase +all of the characters, and replace any invalid characters with an underscore. + +### Construction/Destruction Functions + +Functions that are responsible for constructing objects should take one of the +two following forms: + +* `init`: for functions which accept a pointer to a pre-allocated object (e.g. +a member of a struct) and initialize it. Such functions must zero out the +memory before initializing it to avoid leaving unset fields. +* `create`: for functions which allocate the memory for an object, initialize +it, and return a pointer. Such functions should allocate the memory with +`calloc()` to avoid leaving unset fields. + +Likewise, functions that are responsible for destructing objects should take +one of the two following forms: + +* `finish`: for functions which accept a pointer to an object and deinitialize +it. If a finished object isn't destroyed but kept for future use, it must be +reinitialized to be used again. +* `destroy`: for functions which accept a pointer to an object, deinitialize +it, and free the memory. Such functions should always be able to accept a NULL +pointer. + +### Error Codes + +For functions not returning a value, they should return a (stdbool.h) bool to +indicate whether they succeeded or not. + +### Macros + +Try to keep the use of macros to a minimum, especially if a function can do the +job. If you do need to use them, try to keep them close to where they're being +used and `#undef` them after. + +### Documentation + +* Documentation comments for declarations start with `/**` and end with `*/`. +* Cross-reference other declarations by ending function names with `()`, and + writing `struct`, `union`, `enum` or `typedef` before types. +* Document fields which can be NULL with a `// may be NULL` comment, optionally + with more details describing when this can happen. +* Document the bits of a bitfield with a `// enum bar` comment. +* Document the `data` argument of a `struct wl_signal` with a `// struct foo` + comment. +* Document the contents and container of a `struct wl_list` with a + `// content.link` and `// container.list` comment. + +### Safety + +* Avoid string manipulation functions which don't take the size of the + destination buffer as input: for instance, prefer `snprintf` over `sprintf`. +* Avoid repeating type names in `sizeof()` where possible. For instance, prefer + `ptr = calloc(1, sizeof(*ptr))` over `ptr = calloc(1, sizeof(struct foo))`. +* Prefer `*ptr = (struct foo){0}` over `memset(ptr, 0, sizeof(*ptr))`. +* Prefer `*foo = *bar` over `memcpy(foo, bar, sizeof(*foo))`. + +### Example + +```c +struct wlr_backend *wlr_backend_autocreate(struct wl_display *display) { + struct wlr_backend *backend; + if (getenv("WAYLAND_DISPLAY") || getenv("_WAYLAND_DISPLAY")) { + backend = attempt_wl_backend(display); + if (backend) { + return backend; + } + } + + const char *x11_display = getenv("DISPLAY"); + if (x11_display) { + return wlr_x11_backend_create(display, x11_display); + } + + // Attempt DRM+libinput + + struct wlr_session *session = wlr_session_create(display); + if (!session) { + wlr_log(WLR_ERROR, "Failed to start a DRM session"); + return NULL; + } + + int gpu = wlr_session_find_gpu(session); + if (gpu == -1) { + wlr_log(WLR_ERROR, "Failed to open DRM device"); + goto error_session; + } + + backend = wlr_multi_backend_create(session); + if (!backend) { + goto error_gpu; + } + + struct wlr_backend *libinput = wlr_libinput_backend_create(display, session); + if (!libinput) { + goto error_multi; + } + + struct wlr_backend *drm = wlr_drm_backend_create(display, session, gpu); + if (!drm) { + goto error_libinput; + } + + wlr_multi_backend_add(backend, libinput); + wlr_multi_backend_add(backend, drm); + return backend; + +error_libinput: + wlr_backend_destroy(libinput); +error_multi: + wlr_backend_destroy(backend); +error_gpu: + wlr_session_close_file(session, gpu); +error_session: + wlr_session_destroy(session); + return NULL; +} +``` + +## Wayland protocol implementation + +Each protocol generally lives in a file with the same name, usually containing +at least one struct for each interface in the protocol. For instance, +`xdg_shell` lives in `types/wlr_xdg_shell.h` and has a `wlr_xdg_surface` struct. + +### Globals + +Global interfaces generally have public constructors and destructors. Their +struct has a field holding the `wl_global` itself, a destroy signal and a +`wl_display` destroy listener. Example: + +```c +struct wlr_compositor { + struct wl_global *global; + … + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_surface; + struct wl_signal destroy; + } events; +}; +``` + +When the destructor is called, it should emit the destroy signal, remove the +display destroy listener, destroy the `wl_global` and then destroy the struct. +The destructor can assume all clients and resources have been already +destroyed. + +### Resources + +Resources are the representation of Wayland objects on the compositor side. They +generally have an associated struct, called the _object struct_, stored in their +`user_data` field. + +Object structs can be retrieved from resources via `wl_resource_get_data`. To +prevent bad casts, a safe helper function checking the type of the resource is +used: + +```c +static const struct wl_surface_interface surface_impl; + +struct wlr_surface *wlr_surface_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_surface_interface, + &surface_impl)); + return wl_resource_get_user_data(resource); +} +``` + +If a pointer to a `wl_resource` is stored, a resource destroy handler needs to +be registered to clean it up. libwayland will automatically destroy resources +in an arbitrary order when a client is disconnected, the compositor must handle +this correctly. + +### Destroying resources + +Object structs should only be destroyed when their resource is destroyed, ie. +in the resource destroy handler (set with `wl_resource_set_implementation`). + +- If the object has a destructor request: the request handler should just call + `wl_resource_destroy` and do nothing else. The compositor must not destroy + resources on its own outside the destructor request handler. +- If the protocol specifies that an object is destroyed when an event is sent: + it's the only case where the compositor is allowed to send the event and then + call `wl_resource_destroy`. An example of this is `wl_callback`. + +### Inert resources + +Some resources can become inert in situations described in the protocol or when +the compositor decides to get rid of them. All requests made to inert resources +should be ignored, except the destructor. This is achieved by: + +1. When the resource becomes inert: destroy the object struct and call + `wl_resource_set_user_data(resource, NULL)`. Do not destroy the resource. +2. For each request made to a resource that can be inert: add a NULL check to + ignore the request if the resource is inert. +3. When the client calls the destructor request on the resource: call + `wl_resource_destroy(resource)` as usual. +4. When the resource is destroyed, if the resource isn't inert, destroy the + object struct. + +Example: + +```c +// Handles the destroy request +static void subsurface_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +// Handles a regular request +static void subsurface_set_position(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y) { + struct wlr_subsurface *subsurface = subsurface_from_resource(resource); + if (subsurface == NULL) { + return; + } + + … +} + +// Destroys the wlr_subsurface struct +static void subsurface_destroy(struct wlr_subsurface *subsurface) { + if (subsurface == NULL) { + return; + } + + … + + wl_resource_set_user_data(subsurface->resource, NULL); + free(subsurface); +} + +// Resource destroy listener +static void subsurface_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_subsurface *subsurface = subsurface_from_resource(resource); + subsurface_destroy(subsurface); +} + +// Makes the resource inert +static void subsurface_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_subsurface *subsurface = + wl_container_of(listener, subsurface, surface_destroy); + subsurface_destroy(subsurface); +} +``` + +[#wlroots on Libera Chat]: https://web.libera.chat/gamja/?channels=#wlroots +[file a user verification request]: https://gitlab.freedesktop.org/freedesktop/freedesktop/-/issues/new?issuable_template=User%20verification +[freedesktop wiki]: https://gitlab.freedesktop.org/freedesktop/freedesktop/-/wikis/home +[linear, "recipe" style]: https://www.bitsnbites.eu/git-history-work-log-vs-recipe/ +[git-rebase.io]: https://git-rebase.io/ +[reference issues]: https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically +[Code of Conduct]: https://www.freedesktop.org/wiki/CodeOfConduct/ +[How to Write a Git Commit Message]: https://chris.beams.io/posts/git-commit/ +[kernel style]: https://www.kernel.org/doc/Documentation/process/coding-style.rst diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fe85581 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2017, 2018 Drew DeVault +Copyright (c) 2014 Jari Vetoniemi +Copyright (c) 2023 The wlroots contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..603ee20 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# wlroots + +Pluggable, composable, unopinionated modules for building a [Wayland] +compositor; or about 60,000 lines of code you were going to write anyway. + +- wlroots provides backends that abstract the underlying display and input + hardware, including KMS/DRM, libinput, Wayland, X11, and headless backends, + plus any custom backends you choose to write, which can all be created or + destroyed at runtime and used in concert with each other. +- wlroots provides unopinionated, mostly standalone implementations of many + Wayland interfaces, both from wayland.xml and various protocol extensions. + We also promote the standardization of portable extensions across + many compositors. +- wlroots provides several powerful, standalone, and optional tools that + implement components common to many compositors, such as the arrangement of + outputs in physical space. +- wlroots provides an Xwayland abstraction that allows you to have excellent + Xwayland support without worrying about writing your own X11 window manager + on top of writing your compositor. +- wlroots provides a renderer abstraction that simple compositors can use to + avoid writing GL code directly, but which steps out of the way when your + needs demand custom rendering code. + +wlroots implements a huge variety of Wayland compositor features and implements +them *right*, so you can focus on the features that make your compositor +unique. By using wlroots, you get high performance, excellent hardware +compatibility, broad support for many wayland interfaces, and comfortable +development tools - or any subset of these features you like, because all of +them work independently of one another and freely compose with anything you want +to implement yourself. + +Check out our [wiki] to get started with wlroots. Join our IRC channel: +[#wlroots on Libera Chat]. + +A variety of [wrapper libraries] are available for using it with your favorite +programming language. + +## Building + +Install dependencies: + +* meson +* wayland +* wayland-protocols +* EGL and GLESv2 (optional, for the GLES2 renderer) +* Vulkan loader, headers and glslang (optional, for the Vulkan renderer) +* libdrm +* GBM (optional, for the GBM allocator) +* libinput (optional, for the libinput backend) +* xkbcommon +* udev (optional, for the session) +* pixman +* [libseat] (optional, for the session) +* [hwdata] (optional, for the DRM backend) +* [libdisplay-info] (optional, for the DRM backend) +* [libliftoff] (optional, for the DRM backend) + +If you choose to enable X11 support: + +* xwayland (build-time only, optional at runtime) +* libxcb +* libxcb-render-util +* libxcb-wm +* libxcb-errors (optional, for improved error reporting) + +Run these commands: + + meson setup build/ + ninja -C build/ + +Install like so: + + sudo ninja -C build/ install + +## Contributing + +See [CONTRIBUTING.md]. + +[Wayland]: https://wayland.freedesktop.org/ +[wiki]: https://gitlab.freedesktop.org/wlroots/wlroots/-/wikis/Getting-started +[#wlroots on Libera Chat]: https://web.libera.chat/gamja/?channels=#wlroots +[wrapper libraries]: https://gitlab.freedesktop.org/wlroots/wlroots/-/wikis/Projects-which-use-wlroots#wrapper-libraries +[libseat]: https://git.sr.ht/~kennylevinsen/seatd +[hwdata]: https://github.com/vcrhonek/hwdata +[libdisplay-info]: https://gitlab.freedesktop.org/emersion/libdisplay-info +[libliftoff]: https://gitlab.freedesktop.org/emersion/libliftoff +[CONTRIBUTING.md]: https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/master/CONTRIBUTING.md diff --git a/backend/backend.c b/backend/backend.c new file mode 100644 index 0000000..de2acfe --- /dev/null +++ b/backend/backend.c @@ -0,0 +1,447 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "backend/backend.h" +#include "backend/multi.h" +#include "render/allocator/allocator.h" +#include "util/env.h" +#include "util/time.h" + +#if WLR_HAS_SESSION +#include +#endif + +#if WLR_HAS_DRM_BACKEND +#include +#include "backend/drm/monitor.h" +#endif + +#if WLR_HAS_LIBINPUT_BACKEND +#include +#endif + +#if WLR_HAS_X11_BACKEND +#include +#endif + +#define WAIT_SESSION_TIMEOUT 10000 // ms + +void wlr_backend_init(struct wlr_backend *backend, + const struct wlr_backend_impl *impl) { + *backend = (struct wlr_backend){ + .impl = impl, + }; + wl_signal_init(&backend->events.destroy); + wl_signal_init(&backend->events.new_input); + wl_signal_init(&backend->events.new_output); +} + +void wlr_backend_finish(struct wlr_backend *backend) { + wl_signal_emit_mutable(&backend->events.destroy, backend); +} + +bool wlr_backend_start(struct wlr_backend *backend) { + if (backend->impl->start) { + return backend->impl->start(backend); + } + return true; +} + +void wlr_backend_destroy(struct wlr_backend *backend) { + if (!backend) { + return; + } + + if (backend->impl && backend->impl->destroy) { + backend->impl->destroy(backend); + } else { + free(backend); + } +} + +static struct wlr_session *session_create_and_wait(struct wl_event_loop *loop) { +#if WLR_HAS_SESSION + struct wlr_session *session = wlr_session_create(loop); + + if (!session) { + wlr_log(WLR_ERROR, "Failed to start a session"); + return NULL; + } + + if (!session->active) { + wlr_log(WLR_INFO, "Waiting for a session to become active"); + + int64_t started_at = get_current_time_msec(); + int64_t timeout = WAIT_SESSION_TIMEOUT; + + while (!session->active) { + int ret = wl_event_loop_dispatch(loop, (int)timeout); + if (ret < 0) { + wlr_log_errno(WLR_ERROR, "Failed to wait for session active: " + "wl_event_loop_dispatch failed"); + return NULL; + } + + int64_t now = get_current_time_msec(); + if (now >= started_at + WAIT_SESSION_TIMEOUT) { + break; + } + timeout = started_at + WAIT_SESSION_TIMEOUT - now; + } + + if (!session->active) { + wlr_log(WLR_ERROR, "Timeout waiting session to become active"); + return NULL; + } + } + + return session; +#else + wlr_log(WLR_ERROR, "Cannot create session: disabled at compile-time"); + return NULL; +#endif +} + +int wlr_backend_get_drm_fd(struct wlr_backend *backend) { + if (!backend->impl->get_drm_fd) { + return -1; + } + return backend->impl->get_drm_fd(backend); +} + +uint32_t backend_get_buffer_caps(struct wlr_backend *backend) { + if (!backend->impl->get_buffer_caps) { + return 0; + } + + return backend->impl->get_buffer_caps(backend); +} + +static size_t parse_outputs_env(const char *name) { + const char *outputs_str = getenv(name); + if (outputs_str == NULL) { + return 1; + } + + char *end; + int outputs = (int)strtol(outputs_str, &end, 10); + if (*end || outputs < 0) { + wlr_log(WLR_ERROR, "%s specified with invalid integer, ignoring", name); + return 1; + } + + return outputs; +} + +/** + * Helper to destroy the multi backend when one of its nested backends is + * destroyed. + */ +struct wlr_auto_backend_monitor { + struct wlr_backend *multi; + struct wlr_backend *primary; + + struct wl_listener multi_destroy; + struct wl_listener primary_destroy; +}; + +static void auto_backend_monitor_destroy(struct wlr_auto_backend_monitor *monitor) { + wl_list_remove(&monitor->multi_destroy.link); + wl_list_remove(&monitor->primary_destroy.link); + free(monitor); +} + +static void monitor_handle_multi_destroy(struct wl_listener *listener, void *data) { + struct wlr_auto_backend_monitor *monitor = wl_container_of(listener, monitor, multi_destroy); + auto_backend_monitor_destroy(monitor); +} + +static void monitor_handle_primary_destroy(struct wl_listener *listener, void *data) { + struct wlr_auto_backend_monitor *monitor = wl_container_of(listener, monitor, primary_destroy); + wlr_backend_destroy(monitor->multi); +} + +static struct wlr_auto_backend_monitor *auto_backend_monitor_create( + struct wlr_backend *multi, struct wlr_backend *primary) { + struct wlr_auto_backend_monitor *monitor = calloc(1, sizeof(*monitor)); + if (monitor == NULL) { + return NULL; + } + + monitor->multi = multi; + monitor->primary = primary; + + monitor->multi_destroy.notify = monitor_handle_multi_destroy; + wl_signal_add(&multi->events.destroy, &monitor->multi_destroy); + + monitor->primary_destroy.notify = monitor_handle_primary_destroy; + wl_signal_add(&primary->events.destroy, &monitor->primary_destroy); + + return monitor; +} + +static struct wlr_backend *attempt_wl_backend(struct wl_event_loop *loop) { + struct wlr_backend *backend = wlr_wl_backend_create(loop, NULL); + if (backend == NULL) { + return NULL; + } + + size_t outputs = parse_outputs_env("WLR_WL_OUTPUTS"); + for (size_t i = 0; i < outputs; ++i) { + wlr_wl_output_create(backend); + } + + return backend; +} + +static struct wlr_backend *attempt_x11_backend(struct wl_event_loop *loop, + const char *x11_display) { +#if WLR_HAS_X11_BACKEND + struct wlr_backend *backend = wlr_x11_backend_create(loop, x11_display); + if (backend == NULL) { + return NULL; + } + + size_t outputs = parse_outputs_env("WLR_X11_OUTPUTS"); + for (size_t i = 0; i < outputs; ++i) { + wlr_x11_output_create(backend); + } + + return backend; +#else + wlr_log(WLR_ERROR, "Cannot create X11 backend: disabled at compile-time"); + return NULL; +#endif +} + +static struct wlr_backend *attempt_headless_backend(struct wl_event_loop *loop) { + struct wlr_backend *backend = wlr_headless_backend_create(loop); + if (backend == NULL) { + return NULL; + } + + size_t outputs = parse_outputs_env("WLR_HEADLESS_OUTPUTS"); + for (size_t i = 0; i < outputs; ++i) { + wlr_headless_add_output(backend, 1280, 720); + } + + return backend; +} + +static struct wlr_backend *attempt_drm_backend(struct wlr_backend *backend, struct wlr_session *session) { +#if WLR_HAS_DRM_BACKEND + struct wlr_device *gpus[8]; + ssize_t num_gpus = wlr_session_find_gpus(session, 8, gpus); + if (num_gpus < 0) { + wlr_log(WLR_ERROR, "Failed to find GPUs"); + return NULL; + } + + if (num_gpus == 0) { + wlr_log(WLR_ERROR, "Found 0 GPUs, cannot create backend"); + return NULL; + } else { + wlr_log(WLR_INFO, "Found %zu GPUs", num_gpus); + } + + struct wlr_backend *primary_drm = NULL; + for (size_t i = 0; i < (size_t)num_gpus; ++i) { + struct wlr_backend *drm = wlr_drm_backend_create(session, gpus[i], primary_drm); + if (!drm) { + wlr_log(WLR_ERROR, "Failed to create DRM backend"); + continue; + } + + if (!primary_drm) { + primary_drm = drm; + } + + wlr_multi_backend_add(backend, drm); + } + if (!primary_drm) { + wlr_log(WLR_ERROR, "Could not successfully create backend on any GPU"); + return NULL; + } + + if (getenv("WLR_DRM_DEVICES") == NULL) { + drm_backend_monitor_create(backend, primary_drm, session); + } + + return primary_drm; +#else + wlr_log(WLR_ERROR, "Cannot create DRM backend: disabled at compile-time"); + return NULL; +#endif +} + +static struct wlr_backend *attempt_libinput_backend(struct wlr_session *session) { +#if WLR_HAS_LIBINPUT_BACKEND + return wlr_libinput_backend_create(session); +#else + wlr_log(WLR_ERROR, "Cannot create libinput backend: disabled at compile-time"); + return NULL; +#endif +} + +static bool attempt_backend_by_name(struct wl_event_loop *loop, + struct wlr_backend *multi, char *name, + struct wlr_session **session_ptr) { + struct wlr_backend *backend = NULL; + if (strcmp(name, "wayland") == 0) { + backend = attempt_wl_backend(loop); + } else if (strcmp(name, "x11") == 0) { + backend = attempt_x11_backend(loop, NULL); + } else if (strcmp(name, "headless") == 0) { + backend = attempt_headless_backend(loop); + } else if (strcmp(name, "drm") == 0 || strcmp(name, "libinput") == 0) { + // DRM and libinput need a session + if (*session_ptr == NULL) { + *session_ptr = session_create_and_wait(loop); + if (*session_ptr == NULL) { + wlr_log(WLR_ERROR, "failed to start a session"); + return false; + } + } + + if (strcmp(name, "libinput") == 0) { + backend = attempt_libinput_backend(*session_ptr); + } else { + // attempt_drm_backend() adds the multi drm backends itself + return attempt_drm_backend(multi, *session_ptr) != NULL; + } + } else { + wlr_log(WLR_ERROR, "unrecognized backend '%s'", name); + return false; + } + if (backend == NULL) { + return false; + } + + return wlr_multi_backend_add(multi, backend); +} + +struct wlr_backend *wlr_backend_autocreate(struct wl_event_loop *loop, + struct wlr_session **session_ptr) { + if (session_ptr != NULL) { + *session_ptr = NULL; + } + + struct wlr_session *session = NULL; + struct wlr_backend *multi = wlr_multi_backend_create(loop); + if (!multi) { + wlr_log(WLR_ERROR, "could not allocate multibackend"); + return NULL; + } + + char *names = getenv("WLR_BACKENDS"); + if (names) { + wlr_log(WLR_INFO, "Loading user-specified backends due to WLR_BACKENDS: %s", + names); + + names = strdup(names); + if (names == NULL) { + wlr_log(WLR_ERROR, "allocation failed"); + goto error; + } + + char *saveptr; + char *name = strtok_r(names, ",", &saveptr); + while (name != NULL) { + if (!attempt_backend_by_name(loop, multi, name, &session)) { + wlr_log(WLR_ERROR, "failed to add backend '%s'", name); + free(names); + goto error; + } + + name = strtok_r(NULL, ",", &saveptr); + } + + free(names); + goto success; + } + + if (getenv("WAYLAND_DISPLAY") || getenv("WAYLAND_SOCKET")) { + struct wlr_backend *wl_backend = attempt_wl_backend(loop); + if (!wl_backend) { + goto error; + } + wlr_multi_backend_add(multi, wl_backend); + + if (!auto_backend_monitor_create(multi, wl_backend)) { + goto error; + } + + goto success; + } + + const char *x11_display = getenv("DISPLAY"); + if (x11_display) { + struct wlr_backend *x11_backend = attempt_x11_backend(loop, x11_display); + if (!x11_backend) { + goto error; + } + wlr_multi_backend_add(multi, x11_backend); + + if (!auto_backend_monitor_create(multi, x11_backend)) { + goto error; + } + + goto success; + } + + // Attempt DRM+libinput + session = session_create_and_wait(loop); + if (!session) { + wlr_log(WLR_ERROR, "Failed to start a DRM session"); + goto error; + } + + struct wlr_backend *libinput = attempt_libinput_backend(session); + if (libinput) { + wlr_multi_backend_add(multi, libinput); + if (!auto_backend_monitor_create(multi, libinput)) { + goto error; + } + } else if (env_parse_bool("WLR_LIBINPUT_NO_DEVICES")) { + wlr_log(WLR_INFO, "WLR_LIBINPUT_NO_DEVICES is set, " + "starting without libinput backend"); + } else { + wlr_log(WLR_ERROR, "Failed to start libinput backend"); + wlr_log(WLR_ERROR, "Set WLR_LIBINPUT_NO_DEVICES=1 to skip libinput"); + goto error; + } + + struct wlr_backend *primary_drm = attempt_drm_backend(multi, session); + if (primary_drm == NULL) { + wlr_log(WLR_ERROR, "Failed to open any DRM device"); + goto error; + } + + if (!auto_backend_monitor_create(multi, primary_drm)) { + goto error; + } + +success: + if (session_ptr != NULL) { + *session_ptr = session; + } + return multi; + +error: + wlr_backend_destroy(multi); +#if WLR_HAS_SESSION + wlr_session_destroy(session); +#endif + return NULL; +} diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c new file mode 100644 index 0000000..03d6466 --- /dev/null +++ b/backend/drm/atomic.c @@ -0,0 +1,434 @@ +#include +#include +#include +#include +#include +#include +#include "backend/drm/drm.h" +#include "backend/drm/fb.h" +#include "backend/drm/iface.h" +#include "backend/drm/util.h" + +static char *atomic_commit_flags_str(uint32_t flags) { + const char *const l[] = { + (flags & DRM_MODE_PAGE_FLIP_EVENT) ? "PAGE_FLIP_EVENT" : NULL, + (flags & DRM_MODE_PAGE_FLIP_ASYNC) ? "PAGE_FLIP_ASYNC" : NULL, + (flags & DRM_MODE_ATOMIC_TEST_ONLY) ? "ATOMIC_TEST_ONLY" : NULL, + (flags & DRM_MODE_ATOMIC_NONBLOCK) ? "ATOMIC_NONBLOCK" : NULL, + (flags & DRM_MODE_ATOMIC_ALLOW_MODESET) ? "ATOMIC_ALLOW_MODESET" : NULL, + }; + + char *buf = NULL; + size_t size = 0; + FILE *f = open_memstream(&buf, &size); + if (f == NULL) { + return NULL; + } + + for (size_t i = 0; i < sizeof(l) / sizeof(l[0]); i++) { + if (l[i] == NULL) { + continue; + } + if (ftell(f) > 0) { + fprintf(f, " | "); + } + fprintf(f, "%s", l[i]); + } + + if (ftell(f) == 0) { + fprintf(f, "none"); + } + + fclose(f); + + return buf; +} + +struct atomic { + drmModeAtomicReq *req; + bool failed; +}; + +static void atomic_begin(struct atomic *atom) { + *atom = (struct atomic){0}; + + atom->req = drmModeAtomicAlloc(); + if (!atom->req) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + atom->failed = true; + return; + } +} + +static bool atomic_commit(struct atomic *atom, struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, struct wlr_drm_page_flip *page_flip, + uint32_t flags) { + if (atom->failed) { + return false; + } + + int ret = drmModeAtomicCommit(drm->fd, atom->req, flags, page_flip); + if (ret != 0) { + enum wlr_log_importance log_level = WLR_ERROR; + if (flags & DRM_MODE_ATOMIC_TEST_ONLY) { + log_level = WLR_DEBUG; + } + + if (conn != NULL) { + wlr_drm_conn_log_errno(conn, log_level, "Atomic commit failed"); + } else { + wlr_log_errno(log_level, "Atomic commit failed"); + } + + char *flags_str = atomic_commit_flags_str(flags); + wlr_log(WLR_DEBUG, "(Atomic commit flags: %s)", + flags_str ? flags_str : ""); + free(flags_str); + return false; + } + + return true; +} + +static void atomic_finish(struct atomic *atom) { + drmModeAtomicFree(atom->req); +} + +static void atomic_add(struct atomic *atom, uint32_t id, uint32_t prop, uint64_t val) { + if (!atom->failed && drmModeAtomicAddProperty(atom->req, id, prop, val) < 0) { + wlr_log_errno(WLR_ERROR, "Failed to add atomic DRM property"); + atom->failed = true; + } +} + +bool create_mode_blob(struct wlr_drm_connector *conn, + const struct wlr_drm_connector_state *state, uint32_t *blob_id) { + if (!state->active) { + *blob_id = 0; + return true; + } + + if (drmModeCreatePropertyBlob(conn->backend->fd, &state->mode, + sizeof(drmModeModeInfo), blob_id)) { + wlr_log_errno(WLR_ERROR, "Unable to create mode property blob"); + return false; + } + + return true; +} + +bool create_gamma_lut_blob(struct wlr_drm_backend *drm, + size_t size, const uint16_t *lut, uint32_t *blob_id) { + if (size == 0) { + *blob_id = 0; + return true; + } + + struct drm_color_lut *gamma = malloc(size * sizeof(*gamma)); + if (gamma == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate gamma table"); + return false; + } + + const uint16_t *r = lut; + const uint16_t *g = lut + size; + const uint16_t *b = lut + 2 * size; + for (size_t i = 0; i < size; i++) { + gamma[i].red = r[i]; + gamma[i].green = g[i]; + gamma[i].blue = b[i]; + } + + if (drmModeCreatePropertyBlob(drm->fd, gamma, + size * sizeof(*gamma), blob_id) != 0) { + wlr_log_errno(WLR_ERROR, "Unable to create gamma LUT property blob"); + free(gamma); + return false; + } + free(gamma); + + return true; +} + +bool create_fb_damage_clips_blob(struct wlr_drm_backend *drm, + int width, int height, const pixman_region32_t *damage, uint32_t *blob_id) { + if (!pixman_region32_not_empty(damage)) { + *blob_id = 0; + return true; + } + + pixman_region32_t clipped; + pixman_region32_init(&clipped); + pixman_region32_intersect_rect(&clipped, damage, 0, 0, width, height); + + int rects_len; + const pixman_box32_t *rects = pixman_region32_rectangles(&clipped, &rects_len); + int ret = drmModeCreatePropertyBlob(drm->fd, rects, sizeof(*rects) * rects_len, blob_id); + pixman_region32_fini(&clipped); + if (ret != 0) { + wlr_log_errno(WLR_ERROR, "Failed to create FB_DAMAGE_CLIPS property blob"); + return false; + } + + return true; +} + +static uint64_t max_bpc_for_format(uint32_t format) { + switch (format) { + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_ABGR2101010: + return 10; + case DRM_FORMAT_XBGR16161616F: + case DRM_FORMAT_ABGR16161616F: + case DRM_FORMAT_XBGR16161616: + case DRM_FORMAT_ABGR16161616: + return 16; + default: + return 8; + } +} + +static uint64_t pick_max_bpc(struct wlr_drm_connector *conn, struct wlr_drm_fb *fb) { + uint32_t format = DRM_FORMAT_INVALID; + struct wlr_dmabuf_attributes attribs = {0}; + if (wlr_buffer_get_dmabuf(fb->wlr_buf, &attribs)) { + format = attribs.format; + } + + uint64_t target_bpc = max_bpc_for_format(format); + if (target_bpc < conn->max_bpc_bounds[0]) { + target_bpc = conn->max_bpc_bounds[0]; + } + if (target_bpc > conn->max_bpc_bounds[1]) { + target_bpc = conn->max_bpc_bounds[1]; + } + return target_bpc; +} + +static void destroy_blob(struct wlr_drm_backend *drm, uint32_t id) { + if (id == 0) { + return; + } + if (drmModeDestroyPropertyBlob(drm->fd, id) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to destroy blob"); + } +} + +static void commit_blob(struct wlr_drm_backend *drm, + uint32_t *current, uint32_t next) { + if (*current == next) { + return; + } + destroy_blob(drm, *current); + *current = next; +} + +static void rollback_blob(struct wlr_drm_backend *drm, + uint32_t *current, uint32_t next) { + if (*current == next) { + return; + } + destroy_blob(drm, next); +} + +static void plane_disable(struct atomic *atom, struct wlr_drm_plane *plane) { + uint32_t id = plane->id; + const union wlr_drm_plane_props *props = &plane->props; + atomic_add(atom, id, props->fb_id, 0); + atomic_add(atom, id, props->crtc_id, 0); +} + +static void set_plane_props(struct atomic *atom, struct wlr_drm_backend *drm, + struct wlr_drm_plane *plane, struct wlr_drm_fb *fb, uint32_t crtc_id, + int32_t x, int32_t y) { + uint32_t id = plane->id; + const union wlr_drm_plane_props *props = &plane->props; + + if (fb == NULL) { + wlr_log(WLR_ERROR, "Failed to acquire FB for plane %"PRIu32, plane->id); + atom->failed = true; + return; + } + + uint32_t width = fb->wlr_buf->width; + uint32_t height = fb->wlr_buf->height; + + // The src_* properties are in 16.16 fixed point + atomic_add(atom, id, props->src_x, 0); + atomic_add(atom, id, props->src_y, 0); + atomic_add(atom, id, props->src_w, (uint64_t)width << 16); + atomic_add(atom, id, props->src_h, (uint64_t)height << 16); + atomic_add(atom, id, props->crtc_w, width); + atomic_add(atom, id, props->crtc_h, height); + atomic_add(atom, id, props->fb_id, fb->id); + atomic_add(atom, id, props->crtc_id, crtc_id); + atomic_add(atom, id, props->crtc_x, (uint64_t)x); + atomic_add(atom, id, props->crtc_y, (uint64_t)y); +} + +static bool atomic_crtc_commit(struct wlr_drm_connector *conn, + const struct wlr_drm_connector_state *state, + struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only) { + struct wlr_drm_backend *drm = conn->backend; + struct wlr_output *output = &conn->output; + struct wlr_drm_crtc *crtc = conn->crtc; + + bool modeset = state->modeset; + bool active = state->active; + + uint32_t mode_id = crtc->mode_id; + if (modeset) { + if (!create_mode_blob(conn, state, &mode_id)) { + return false; + } + } + + uint32_t gamma_lut = crtc->gamma_lut; + if (state->base->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { + // Fallback to legacy gamma interface when gamma properties are not + // available (can happen on older Intel GPUs that support gamma but not + // degamma). + if (crtc->props.gamma_lut == 0) { + if (!drm_legacy_crtc_set_gamma(drm, crtc, + state->base->gamma_lut_size, + state->base->gamma_lut)) { + return false; + } + } else { + if (!create_gamma_lut_blob(drm, state->base->gamma_lut_size, + state->base->gamma_lut, &gamma_lut)) { + return false; + } + } + } + + uint32_t fb_damage_clips = 0; + if ((state->base->committed & WLR_OUTPUT_STATE_DAMAGE) && + crtc->primary->props.fb_damage_clips != 0) { + create_fb_damage_clips_blob(drm, state->primary_fb->wlr_buf->width, + state->primary_fb->wlr_buf->height, &state->base->damage, &fb_damage_clips); + } + + bool prev_vrr_enabled = + output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED; + bool vrr_enabled = prev_vrr_enabled; + if ((state->base->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED)) { + if (!drm_connector_supports_vrr(conn)) { + return false; + } + vrr_enabled = state->base->adaptive_sync_enabled; + } + + if (test_only) { + flags |= DRM_MODE_ATOMIC_TEST_ONLY; + } + if (modeset) { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + if (!test_only && state->nonblock) { + flags |= DRM_MODE_ATOMIC_NONBLOCK; + } + + struct atomic atom; + atomic_begin(&atom); + atomic_add(&atom, conn->id, conn->props.crtc_id, active ? crtc->id : 0); + if (modeset && active && conn->props.link_status != 0) { + atomic_add(&atom, conn->id, conn->props.link_status, + DRM_MODE_LINK_STATUS_GOOD); + } + if (active && conn->props.content_type != 0) { + atomic_add(&atom, conn->id, conn->props.content_type, + DRM_MODE_CONTENT_TYPE_GRAPHICS); + } + if (modeset && active && conn->props.max_bpc != 0 && conn->max_bpc_bounds[1] != 0) { + atomic_add(&atom, conn->id, conn->props.max_bpc, pick_max_bpc(conn, state->primary_fb)); + } + atomic_add(&atom, crtc->id, crtc->props.mode_id, mode_id); + atomic_add(&atom, crtc->id, crtc->props.active, active); + if (active) { + if (crtc->props.gamma_lut != 0) { + atomic_add(&atom, crtc->id, crtc->props.gamma_lut, gamma_lut); + } + if (crtc->props.vrr_enabled != 0) { + atomic_add(&atom, crtc->id, crtc->props.vrr_enabled, vrr_enabled); + } + set_plane_props(&atom, drm, crtc->primary, state->primary_fb, crtc->id, + 0, 0); + if (crtc->primary->props.fb_damage_clips != 0) { + atomic_add(&atom, crtc->primary->id, + crtc->primary->props.fb_damage_clips, fb_damage_clips); + } + if (crtc->cursor) { + if (drm_connector_is_cursor_visible(conn)) { + set_plane_props(&atom, drm, crtc->cursor, state->cursor_fb, + crtc->id, conn->cursor_x, conn->cursor_y); + } else { + plane_disable(&atom, crtc->cursor); + } + } + } else { + plane_disable(&atom, crtc->primary); + if (crtc->cursor) { + plane_disable(&atom, crtc->cursor); + } + } + + bool ok = atomic_commit(&atom, drm, conn, page_flip, flags); + atomic_finish(&atom); + + if (ok && !test_only) { + if (!crtc->own_mode_id) { + crtc->mode_id = 0; // don't try to delete previous master's blobs + } + crtc->own_mode_id = true; + commit_blob(drm, &crtc->mode_id, mode_id); + commit_blob(drm, &crtc->gamma_lut, gamma_lut); + + if (vrr_enabled != prev_vrr_enabled) { + output->adaptive_sync_status = vrr_enabled ? + WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED : + WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED; + wlr_drm_conn_log(conn, WLR_DEBUG, "VRR %s", + vrr_enabled ? "enabled" : "disabled"); + } + } else { + rollback_blob(drm, &crtc->mode_id, mode_id); + rollback_blob(drm, &crtc->gamma_lut, gamma_lut); + } + destroy_blob(drm, fb_damage_clips); + + return ok; +} + +bool drm_atomic_reset(struct wlr_drm_backend *drm) { + struct atomic atom; + atomic_begin(&atom); + + for (size_t i = 0; i < drm->num_crtcs; i++) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + atomic_add(&atom, crtc->id, crtc->props.mode_id, 0); + atomic_add(&atom, crtc->id, crtc->props.active, 0); + } + + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->connectors, link) { + atomic_add(&atom, conn->id, conn->props.crtc_id, 0); + } + + for (size_t i = 0; i < drm->num_planes; i++) { + plane_disable(&atom, &drm->planes[i]); + } + + uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET; + bool ok = atomic_commit(&atom, drm, NULL, NULL, flags); + atomic_finish(&atom); + + return ok; +} + +const struct wlr_drm_interface atomic_iface = { + .crtc_commit = atomic_crtc_commit, + .reset = drm_atomic_reset, +}; diff --git a/backend/drm/backend.c b/backend/drm/backend.c new file mode 100644 index 0000000..27e5585 --- /dev/null +++ b/backend/drm/backend.c @@ -0,0 +1,262 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/drm/drm.h" +#include "backend/drm/fb.h" + +struct wlr_drm_backend *get_drm_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_drm(wlr_backend)); + struct wlr_drm_backend *backend = wl_container_of(wlr_backend, backend, backend); + return backend; +} + +static bool backend_start(struct wlr_backend *backend) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + scan_drm_connectors(drm, NULL); + return true; +} + +static void backend_destroy(struct wlr_backend *backend) { + if (!backend) { + return; + } + + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + + struct wlr_drm_connector *conn, *next; + wl_list_for_each_safe(conn, next, &drm->connectors, link) { + conn->crtc = NULL; // leave CRTCs on when shutting down + destroy_drm_connector(conn); + } + + struct wlr_drm_page_flip *page_flip, *page_flip_tmp; + wl_list_for_each_safe(page_flip, page_flip_tmp, &drm->page_flips, link) { + drm_page_flip_destroy(page_flip); + } + + wlr_backend_finish(backend); + + wl_list_remove(&drm->session_destroy.link); + wl_list_remove(&drm->session_active.link); + wl_list_remove(&drm->parent_destroy.link); + wl_list_remove(&drm->dev_change.link); + wl_list_remove(&drm->dev_remove.link); + + if (drm->parent) { + finish_drm_renderer(&drm->mgpu_renderer); + } + + finish_drm_resources(drm); + + struct wlr_drm_fb *fb, *fb_tmp; + wl_list_for_each_safe(fb, fb_tmp, &drm->fbs, link) { + drm_fb_destroy(fb); + } + + free(drm->name); + wlr_session_close_file(drm->session, drm->dev); + wl_event_source_remove(drm->drm_event); + free(drm); +} + +static int backend_get_drm_fd(struct wlr_backend *backend) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + return drm->fd; +} + +static uint32_t drm_backend_get_buffer_caps(struct wlr_backend *backend) { + return WLR_BUFFER_CAP_DMABUF; +} + +static const struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_drm_fd = backend_get_drm_fd, + .get_buffer_caps = drm_backend_get_buffer_caps, +}; + +bool wlr_backend_is_drm(struct wlr_backend *b) { + return b->impl == &backend_impl; +} + +struct wlr_backend *wlr_drm_backend_get_parent(struct wlr_backend *backend) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + return drm->parent ? &drm->parent->backend : NULL; +} + +static void handle_session_active(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = + wl_container_of(listener, drm, session_active); + struct wlr_session *session = drm->session; + + wlr_log(WLR_INFO, "DRM FD %s", session->active ? "resumed" : "paused"); + + if (!session->active) { + return; + } + + scan_drm_connectors(drm, NULL); + restore_drm_device(drm); +} + +static void handle_dev_change(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = wl_container_of(listener, drm, dev_change); + struct wlr_device_change_event *change = data; + + if (!drm->session->active) { + return; + } + + switch (change->type) { + case WLR_DEVICE_HOTPLUG: + wlr_log(WLR_DEBUG, "Received hotplug event for %s", drm->name); + scan_drm_connectors(drm, &change->hotplug); + break; + case WLR_DEVICE_LEASE: + wlr_log(WLR_DEBUG, "Received lease event for %s", drm->name); + scan_drm_leases(drm); + break; + default: + wlr_log(WLR_DEBUG, "Received unknown change event for %s", drm->name); + } +} + +static void handle_dev_remove(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = wl_container_of(listener, drm, dev_remove); + + wlr_log(WLR_INFO, "Destroying DRM backend for %s", drm->name); + backend_destroy(&drm->backend); +} + +static void handle_session_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = + wl_container_of(listener, drm, session_destroy); + backend_destroy(&drm->backend); +} + +static void handle_parent_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = + wl_container_of(listener, drm, parent_destroy); + backend_destroy(&drm->backend); +} + +struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session, + struct wlr_device *dev, struct wlr_backend *parent) { + assert(session && dev); + assert(!parent || wlr_backend_is_drm(parent)); + + char *name = drmGetDeviceNameFromFd2(dev->fd); + drmVersion *version = drmGetVersion(dev->fd); + wlr_log(WLR_INFO, "Initializing DRM backend for %s (%s)", name, version->name); + drmFreeVersion(version); + + struct wlr_drm_backend *drm = calloc(1, sizeof(*drm)); + if (!drm) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + wlr_backend_init(&drm->backend, &backend_impl); + + drm->session = session; + wl_list_init(&drm->fbs); + wl_list_init(&drm->connectors); + wl_list_init(&drm->page_flips); + + drm->dev = dev; + drm->fd = dev->fd; + drm->name = name; + + if (parent != NULL) { + drm->parent = get_drm_backend_from_backend(parent); + + drm->parent_destroy.notify = handle_parent_destroy; + wl_signal_add(&parent->events.destroy, &drm->parent_destroy); + } else { + wl_list_init(&drm->parent_destroy.link); + } + + drm->dev_change.notify = handle_dev_change; + wl_signal_add(&dev->events.change, &drm->dev_change); + + drm->dev_remove.notify = handle_dev_remove; + wl_signal_add(&dev->events.remove, &drm->dev_remove); + + drm->drm_event = wl_event_loop_add_fd(session->event_loop, drm->fd, + WL_EVENT_READABLE, handle_drm_event, drm); + if (!drm->drm_event) { + wlr_log(WLR_ERROR, "Failed to create DRM event source"); + goto error_fd; + } + + drm->session_active.notify = handle_session_active; + wl_signal_add(&session->events.active, &drm->session_active); + + if (!check_drm_features(drm)) { + goto error_event; + } + + if (!init_drm_resources(drm)) { + goto error_event; + } + + if (drm->parent) { + if (!init_drm_renderer(drm, &drm->mgpu_renderer)) { + wlr_log(WLR_ERROR, "Failed to initialize renderer"); + goto error_resources; + } + + // We'll perform a multi-GPU copy for all submitted buffers, we need + // to be able to texture from them + struct wlr_renderer *renderer = drm->mgpu_renderer.wlr_rend; + const struct wlr_drm_format_set *texture_formats = + wlr_renderer_get_dmabuf_texture_formats(renderer); + if (texture_formats == NULL) { + wlr_log(WLR_ERROR, "Failed to query renderer texture formats"); + goto error_mgpu_renderer; + } + + // Forbid implicit modifiers, because their meaning changes from one + // GPU to another. + for (size_t i = 0; i < texture_formats->len; i++) { + const struct wlr_drm_format *fmt = &texture_formats->formats[i]; + for (size_t j = 0; j < fmt->len; j++) { + uint64_t mod = fmt->modifiers[j]; + if (mod == DRM_FORMAT_MOD_INVALID) { + continue; + } + wlr_drm_format_set_add(&drm->mgpu_formats, fmt->format, mod); + } + } + } + + drm->session_destroy.notify = handle_session_destroy; + wl_signal_add(&session->events.destroy, &drm->session_destroy); + + return &drm->backend; + +error_mgpu_renderer: + finish_drm_renderer(&drm->mgpu_renderer); +error_resources: + finish_drm_resources(drm); +error_event: + wl_list_remove(&drm->session_active.link); + wl_event_source_remove(drm->drm_event); +error_fd: + wl_list_remove(&drm->dev_remove.link); + wl_list_remove(&drm->dev_change.link); + wl_list_remove(&drm->parent_destroy.link); + wlr_session_close_file(drm->session, dev); + free(drm); + return NULL; +} diff --git a/backend/drm/drm.c b/backend/drm/drm.c new file mode 100644 index 0000000..3a71f39 --- /dev/null +++ b/backend/drm/drm.c @@ -0,0 +1,2069 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/drm/drm.h" +#include "backend/drm/fb.h" +#include "backend/drm/iface.h" +#include "backend/drm/util.h" +#include "render/pixel_format.h" +#include "render/drm_format_set.h" +#include "render/wlr_renderer.h" +#include "types/wlr_output.h" +#include "util/env.h" +#include "config.h" + +#if HAVE_LIBLIFTOFF +#include +#endif + +// Output state which needs a KMS commit to be applied +static const uint32_t COMMIT_OUTPUT_STATE = + WLR_OUTPUT_STATE_BUFFER | + WLR_OUTPUT_STATE_MODE | + WLR_OUTPUT_STATE_ENABLED | + WLR_OUTPUT_STATE_GAMMA_LUT | + WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED | + WLR_OUTPUT_STATE_LAYERS; + +static const uint32_t SUPPORTED_OUTPUT_STATE = + WLR_OUTPUT_STATE_BACKEND_OPTIONAL | COMMIT_OUTPUT_STATE; + +bool check_drm_features(struct wlr_drm_backend *drm) { + if (drmGetCap(drm->fd, DRM_CAP_CURSOR_WIDTH, &drm->cursor_width)) { + drm->cursor_width = 64; + } + if (drmGetCap(drm->fd, DRM_CAP_CURSOR_HEIGHT, &drm->cursor_height)) { + drm->cursor_height = 64; + } + + uint64_t cap; + if (drmGetCap(drm->fd, DRM_CAP_PRIME, &cap) || + !(cap & DRM_PRIME_CAP_IMPORT)) { + wlr_log(WLR_ERROR, "PRIME import not supported"); + return false; + } + + if (drm->parent) { + if (drmGetCap(drm->parent->fd, DRM_CAP_PRIME, &cap) || + !(cap & DRM_PRIME_CAP_EXPORT)) { + wlr_log(WLR_ERROR, + "PRIME export not supported on primary GPU"); + return false; + } + } + + if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { + wlr_log(WLR_ERROR, "DRM universal planes unsupported"); + return false; + } + + if (drmGetCap(drm->fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap) || !cap) { + wlr_log(WLR_ERROR, "DRM_CRTC_IN_VBLANK_EVENT unsupported"); + return false; + } + + if (drmGetCap(drm->fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap) || !cap) { + wlr_log(WLR_ERROR, "DRM_CAP_TIMESTAMP_MONOTONIC unsupported"); + return false; + } + + if (env_parse_bool("WLR_DRM_FORCE_LIBLIFTOFF")) { +#if HAVE_LIBLIFTOFF + wlr_log(WLR_INFO, + "WLR_DRM_FORCE_LIBLIFTOFF set, forcing libliftoff interface"); + if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_ATOMIC, 1) != 0) { + wlr_log_errno(WLR_ERROR, "drmSetClientCap(ATOMIC) failed"); + return false; + } + drm->iface = &liftoff_iface; +#else + wlr_log(WLR_ERROR, "libliftoff interface not available"); + return false; +#endif + } else if (env_parse_bool("WLR_DRM_NO_ATOMIC")) { + wlr_log(WLR_DEBUG, + "WLR_DRM_NO_ATOMIC set, forcing legacy DRM interface"); + drm->iface = &legacy_iface; + } else if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_ATOMIC, 1)) { + wlr_log(WLR_DEBUG, + "Atomic modesetting unsupported, using legacy DRM interface"); + drm->iface = &legacy_iface; + } else { + wlr_log(WLR_DEBUG, "Using atomic DRM interface"); + drm->iface = &atomic_iface; + } + + if (drm->iface == &legacy_iface) { + drm->supports_tearing_page_flips = drmGetCap(drm->fd, DRM_CAP_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1; + } else { + drm->supports_tearing_page_flips = drmGetCap(drm->fd, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1; + } + + if (env_parse_bool("WLR_DRM_NO_MODIFIERS")) { + wlr_log(WLR_DEBUG, "WLR_DRM_NO_MODIFIERS set, disabling modifiers"); + } else { + int ret = drmGetCap(drm->fd, DRM_CAP_ADDFB2_MODIFIERS, &cap); + drm->addfb2_modifiers = ret == 0 && cap == 1; + wlr_log(WLR_DEBUG, "ADDFB2 modifiers %s", + drm->addfb2_modifiers ? "supported" : "unsupported"); + } + + return true; +} + +static bool init_plane(struct wlr_drm_backend *drm, + struct wlr_drm_plane *p, const drmModePlane *drm_plane) { + uint32_t id = drm_plane->plane_id; + + union wlr_drm_plane_props props = {0}; + if (!get_drm_plane_props(drm->fd, id, &props)) { + return false; + } + + uint64_t type; + if (!get_drm_prop(drm->fd, id, props.type, &type)) { + return false; + } + + p->type = type; + p->id = drm_plane->plane_id; + p->props = props; + p->initial_crtc_id = drm_plane->crtc_id; + + for (size_t i = 0; i < drm_plane->count_formats; ++i) { + // Force a LINEAR layout for the cursor if the driver doesn't support + // modifiers + wlr_drm_format_set_add(&p->formats, drm_plane->formats[i], + DRM_FORMAT_MOD_LINEAR); + if (type != DRM_PLANE_TYPE_CURSOR) { + wlr_drm_format_set_add(&p->formats, drm_plane->formats[i], + DRM_FORMAT_MOD_INVALID); + } + } + + if (p->props.in_formats && drm->addfb2_modifiers) { + uint64_t blob_id; + if (!get_drm_prop(drm->fd, p->id, p->props.in_formats, &blob_id)) { + wlr_log(WLR_ERROR, "Failed to read IN_FORMATS property"); + return false; + } + + drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(drm->fd, blob_id); + if (!blob) { + wlr_log(WLR_ERROR, "Failed to read IN_FORMATS blob"); + return false; + } + + drmModeFormatModifierIterator iter = {0}; + while (drmModeFormatModifierBlobIterNext(blob, &iter)) { + wlr_drm_format_set_add(&p->formats, iter.fmt, iter.mod); + } + + drmModeFreePropertyBlob(blob); + } + + assert(drm->num_crtcs <= 32); + for (size_t j = 0; j < drm->num_crtcs; j++) { + uint32_t crtc_bit = 1 << j; + if ((drm_plane->possible_crtcs & crtc_bit) == 0) { + continue; + } + + struct wlr_drm_crtc *crtc = &drm->crtcs[j]; + if (type == DRM_PLANE_TYPE_PRIMARY && !crtc->primary) { + crtc->primary = p; + break; + } + if (type == DRM_PLANE_TYPE_CURSOR && !crtc->cursor) { + crtc->cursor = p; + break; + } + } + + return true; +} + +static bool init_planes(struct wlr_drm_backend *drm) { + drmModePlaneRes *plane_res = drmModeGetPlaneResources(drm->fd); + if (!plane_res) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM plane resources"); + return false; + } + + wlr_log(WLR_INFO, "Found %"PRIu32" DRM planes", plane_res->count_planes); + + drm->num_planes = plane_res->count_planes; + drm->planes = calloc(drm->num_planes, sizeof(*drm->planes)); + if (drm->planes == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error; + } + + for (uint32_t i = 0; i < plane_res->count_planes; ++i) { + uint32_t id = plane_res->planes[i]; + + drmModePlane *drm_plane = drmModeGetPlane(drm->fd, id); + if (!drm_plane) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM plane"); + goto error; + } + + struct wlr_drm_plane *plane = &drm->planes[i]; + if (!init_plane(drm, plane, drm_plane)) { + goto error; + } + + drmModeFreePlane(drm_plane); + } + + drmModeFreePlaneResources(plane_res); + return true; + +error: + free(drm->planes); + drmModeFreePlaneResources(plane_res); + return false; +} + +bool init_drm_resources(struct wlr_drm_backend *drm) { + drmModeRes *res = drmModeGetResources(drm->fd); + if (!res) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM resources"); + return false; + } + + wlr_log(WLR_INFO, "Found %d DRM CRTCs", res->count_crtcs); + + drm->num_crtcs = res->count_crtcs; + if (drm->num_crtcs == 0) { + drmModeFreeResources(res); + return true; + } + + drm->crtcs = calloc(drm->num_crtcs, sizeof(drm->crtcs[0])); + if (!drm->crtcs) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error_res; + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + crtc->id = res->crtcs[i]; + + drmModeCrtc *drm_crtc = drmModeGetCrtc(drm->fd, crtc->id); + if (drm_crtc == NULL) { + wlr_log_errno(WLR_ERROR, "drmModeGetCrtc failed"); + goto error_res; + } + crtc->legacy_gamma_size = drm_crtc->gamma_size; + drmModeFreeCrtc(drm_crtc); + + if (!get_drm_crtc_props(drm->fd, crtc->id, &crtc->props)) { + goto error_crtcs; + } + + wl_list_init(&crtc->layers); + } + + if (!init_planes(drm)) { + goto error_crtcs; + } + + if (drm->iface->init != NULL && !drm->iface->init(drm)) { + goto error_crtcs; + } + + drmModeFreeResources(res); + + return true; + +error_crtcs: + free(drm->crtcs); +error_res: + drmModeFreeResources(res); + return false; +} + +static void drm_plane_finish_surface(struct wlr_drm_plane *plane) { + if (!plane) { + return; + } + + drm_fb_clear(&plane->queued_fb); + drm_fb_clear(&plane->current_fb); + + finish_drm_surface(&plane->mgpu_surf); +} + +void finish_drm_resources(struct wlr_drm_backend *drm) { + if (!drm) { + return; + } + + if (drm->iface->finish != NULL) { + drm->iface->finish(drm); + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + + if (crtc->mode_id && crtc->own_mode_id) { + drmModeDestroyPropertyBlob(drm->fd, crtc->mode_id); + } + if (crtc->gamma_lut) { + drmModeDestroyPropertyBlob(drm->fd, crtc->gamma_lut); + } + } + + free(drm->crtcs); + + for (size_t i = 0; i < drm->num_planes; ++i) { + struct wlr_drm_plane *plane = &drm->planes[i]; + drm_plane_finish_surface(plane); + wlr_drm_format_set_finish(&plane->formats); + } + + free(drm->planes); +} + +static struct wlr_drm_connector *get_drm_connector_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_drm(wlr_output)); + struct wlr_drm_connector *conn = wl_container_of(wlr_output, conn, output); + return conn; +} + +static void layer_handle_addon_destroy(struct wlr_addon *addon) { + struct wlr_drm_layer *layer = wl_container_of(addon, layer, addon); + wlr_addon_finish(&layer->addon); + wl_list_remove(&layer->link); +#if HAVE_LIBLIFTOFF + liftoff_layer_destroy(layer->liftoff); +#endif + drm_fb_clear(&layer->pending_fb); + drm_fb_clear(&layer->queued_fb); + drm_fb_clear(&layer->current_fb); + free(layer->candidate_planes); + free(layer); +} + +const struct wlr_addon_interface layer_impl = { + .name = "wlr_drm_layer", + .destroy = layer_handle_addon_destroy, +}; + +struct wlr_drm_layer *get_drm_layer(struct wlr_drm_backend *drm, + struct wlr_output_layer *wlr_layer) { + struct wlr_addon *addon = + wlr_addon_find(&wlr_layer->addons, drm, &layer_impl); + assert(addon != NULL); + struct wlr_drm_layer *layer = wl_container_of(addon, layer, addon); + return layer; +} + +static struct wlr_drm_layer *get_or_create_layer(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, struct wlr_output_layer *wlr_layer) { + struct wlr_drm_layer *layer; + struct wlr_addon *addon = + wlr_addon_find(&wlr_layer->addons, drm, &layer_impl); + if (addon != NULL) { + layer = wl_container_of(addon, layer, addon); + return layer; + } + + layer = calloc(1, sizeof(*layer)); + if (layer == NULL) { + return NULL; + } + + layer->wlr = wlr_layer; + +#if HAVE_LIBLIFTOFF + layer->liftoff = liftoff_layer_create(crtc->liftoff); + if (layer->liftoff == NULL) { + free(layer); + return NULL; + } +#else + abort(); // unreachable +#endif + + layer->candidate_planes = calloc(sizeof(bool), drm->num_planes); + if (layer->candidate_planes == NULL) { +#if HAVE_LIBLIFTOFF + liftoff_layer_destroy(layer->liftoff); +#endif + free(layer); + return NULL; + } + + wlr_addon_init(&layer->addon, &wlr_layer->addons, drm, &layer_impl); + wl_list_insert(&crtc->layers, &layer->link); + + return layer; +} + +static void drm_connector_set_pending_page_flip(struct wlr_drm_connector *conn, + struct wlr_drm_page_flip *page_flip) { + if (conn->pending_page_flip != NULL) { + conn->pending_page_flip->conn = NULL; + } + conn->pending_page_flip = page_flip; +} + +void drm_page_flip_destroy(struct wlr_drm_page_flip *page_flip) { + if (!page_flip) { + return; + } + + wl_list_remove(&page_flip->link); + free(page_flip); +} + +static struct wlr_drm_page_flip *drm_page_flip_create(struct wlr_drm_connector *conn) { + struct wlr_drm_page_flip *page_flip = calloc(1, sizeof(*page_flip)); + if (page_flip == NULL) { + return NULL; + } + page_flip->conn = conn; + wl_list_insert(&conn->backend->page_flips, &page_flip->link); + return page_flip; +} + +static bool drm_crtc_commit(struct wlr_drm_connector *conn, + const struct wlr_drm_connector_state *state, + uint32_t flags, bool test_only) { + // Disallow atomic-only flags + assert((flags & ~DRM_MODE_PAGE_FLIP_FLAGS) == 0); + + struct wlr_drm_page_flip *page_flip = NULL; + if (flags & DRM_MODE_PAGE_FLIP_EVENT) { + page_flip = drm_page_flip_create(conn); + if (page_flip == NULL) { + return false; + } + } + + struct wlr_drm_backend *drm = conn->backend; + struct wlr_drm_crtc *crtc = conn->crtc; + bool ok = drm->iface->crtc_commit(conn, state, page_flip, flags, test_only); + if (ok && !test_only) { + drm_fb_copy(&crtc->primary->queued_fb, state->primary_fb); + if (crtc->cursor != NULL) { + drm_fb_copy(&crtc->cursor->queued_fb, state->cursor_fb); + } + drm_fb_clear(&conn->cursor_pending_fb); + + struct wlr_drm_layer *layer; + wl_list_for_each(layer, &crtc->layers, link) { + drm_fb_move(&layer->queued_fb, &layer->pending_fb); + } + + drm_connector_set_pending_page_flip(conn, page_flip); + + if (state->base->committed & WLR_OUTPUT_STATE_MODE) { + conn->refresh = calculate_refresh_rate(&state->mode); + } + } else { + // The set_cursor() hook is a bit special: it's not really synchronized + // to commit() or test(). Once set_cursor() returns true, the new + // cursor is effectively committed. So don't roll it back here, or we + // risk ending up in a state where we don't have a cursor FB but + // wlr_drm_connector.cursor_enabled is true. + // TODO: fix our output interface to avoid this issue. + + struct wlr_drm_layer *layer; + wl_list_for_each(layer, &crtc->layers, link) { + drm_fb_clear(&layer->pending_fb); + } + + drm_page_flip_destroy(page_flip); + } + return ok; +} + +static void drm_connector_state_init(struct wlr_drm_connector_state *state, + struct wlr_drm_connector *conn, + const struct wlr_output_state *base) { + *state = (struct wlr_drm_connector_state){ + .base = base, + .modeset = base->allow_reconfiguration, + .active = output_pending_enabled(&conn->output, base), + // The wlr_output API requires non-modeset commits with a new buffer to + // wait for the frame event. However compositors often perform + // non-modesets commits without a new buffer without waiting for the + // frame event. In that case we need to make the KMS commit blocking, + // otherwise the kernel will error out with EBUSY. + .nonblock = !base->allow_reconfiguration && + (base->committed & WLR_OUTPUT_STATE_BUFFER), + }; + + struct wlr_output_mode *mode = conn->output.current_mode; + int32_t width = conn->output.width; + int32_t height = conn->output.height; + int32_t refresh = conn->output.refresh; + + if (base->committed & WLR_OUTPUT_STATE_MODE) { + switch (base->mode_type) { + case WLR_OUTPUT_STATE_MODE_FIXED:; + mode = base->mode; + break; + case WLR_OUTPUT_STATE_MODE_CUSTOM: + mode = NULL; + width = base->custom_mode.width; + height = base->custom_mode.height; + refresh = base->custom_mode.refresh; + break; + } + } + + if (mode) { + struct wlr_drm_mode *drm_mode = wl_container_of(mode, drm_mode, wlr_mode); + state->mode = drm_mode->drm_mode; + } else { + generate_cvt_mode(&state->mode, width, height, (float)refresh / 1000); + state->mode.type = DRM_MODE_TYPE_USERDEF; + } + + if (output_pending_enabled(&conn->output, base)) { + // The CRTC must be set up before this function is called + assert(conn->crtc != NULL); + + struct wlr_drm_plane *primary = conn->crtc->primary; + if (primary->queued_fb != NULL) { + state->primary_fb = drm_fb_lock(primary->queued_fb); + } else if (primary->current_fb != NULL) { + state->primary_fb = drm_fb_lock(primary->current_fb); + } + + if (conn->cursor_enabled) { + struct wlr_drm_plane *cursor = conn->crtc->cursor; + assert(cursor != NULL); + if (conn->cursor_pending_fb != NULL) { + state->cursor_fb = drm_fb_lock(conn->cursor_pending_fb); + } else if (cursor->queued_fb != NULL) { + state->cursor_fb = drm_fb_lock(cursor->queued_fb); + } else if (cursor->current_fb != NULL) { + state->cursor_fb = drm_fb_lock(cursor->current_fb); + } + } + } +} + +static void drm_connector_state_finish(struct wlr_drm_connector_state *state) { + drm_fb_clear(&state->primary_fb); + drm_fb_clear(&state->cursor_fb); +} + +static bool drm_connector_state_update_primary_fb(struct wlr_drm_connector *conn, + struct wlr_drm_connector_state *state) { + struct wlr_drm_backend *drm = conn->backend; + + assert(state->base->committed & WLR_OUTPUT_STATE_BUFFER); + + struct wlr_drm_crtc *crtc = conn->crtc; + assert(crtc != NULL); + + struct wlr_drm_plane *plane = crtc->primary; + struct wlr_buffer *source_buf = state->base->buffer; + + struct wlr_buffer *local_buf; + if (drm->parent) { + struct wlr_drm_format format = {0}; + if (!drm_plane_pick_render_format(plane, &format, &drm->mgpu_renderer)) { + wlr_log(WLR_ERROR, "Failed to pick primary plane format"); + return false; + } + + // TODO: fallback to modifier-less buffer allocation + bool ok = init_drm_surface(&plane->mgpu_surf, &drm->mgpu_renderer, + source_buf->width, source_buf->height, &format); + wlr_drm_format_finish(&format); + if (!ok) { + return false; + } + + local_buf = drm_surface_blit(&plane->mgpu_surf, source_buf); + if (local_buf == NULL) { + return false; + } + } else { + local_buf = wlr_buffer_lock(source_buf); + } + + bool ok = drm_fb_import(&state->primary_fb, drm, local_buf, + &plane->formats); + wlr_buffer_unlock(local_buf); + if (!ok) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "Failed to import buffer for scan-out"); + return false; + } + + return true; +} + +static bool drm_connector_set_pending_layer_fbs(struct wlr_drm_connector *conn, + const struct wlr_output_state *state) { + struct wlr_drm_backend *drm = conn->backend; + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc || drm->parent) { + return false; + } + + if (!crtc->liftoff) { + return true; // libliftoff is disabled + } + + assert(state->committed & WLR_OUTPUT_STATE_LAYERS); + + for (size_t i = 0; i < state->layers_len; i++) { + struct wlr_output_layer_state *layer_state = &state->layers[i]; + struct wlr_drm_layer *layer = + get_or_create_layer(drm, crtc, layer_state->layer); + if (!layer) { + return false; + } + + if (layer_state->buffer != NULL) { + drm_fb_import(&layer->pending_fb, drm, layer_state->buffer, NULL); + } else { + drm_fb_clear(&layer->pending_fb); + } + } + + return true; +} + +static bool drm_connector_alloc_crtc(struct wlr_drm_connector *conn); + +static bool drm_connector_test(struct wlr_output *output, + const struct wlr_output_state *state) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + + if (!conn->backend->session->active) { + return false; + } + + uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE; + if (unsupported != 0) { + wlr_log(WLR_DEBUG, "Unsupported output state fields: 0x%"PRIx32, + unsupported); + return false; + } + + if ((state->committed & COMMIT_OUTPUT_STATE) == 0) { + // This commit doesn't change the KMS state + return true; + } + + if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && state->enabled) { + if (output->current_mode == NULL && + !(state->committed & WLR_OUTPUT_STATE_MODE)) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "Can't enable an output without a mode"); + return false; + } + } + + if (output_pending_enabled(output, state) && !drm_connector_alloc_crtc(conn)) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "No CRTC available for this connector"); + return false; + } + + bool ok = false; + struct wlr_drm_connector_state pending = {0}; + drm_connector_state_init(&pending, conn, state); + + if ((state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) && + state->adaptive_sync_enabled && + !drm_connector_supports_vrr(conn)) { + goto out; + } + + if (conn->backend->parent) { + // If we're running as a secondary GPU, we can't perform an atomic + // commit without blitting a buffer. + ok = true; + goto out; + } + + if (!conn->crtc) { + // If the output is disabled, we don't have a crtc even after + // reallocation + ok = true; + goto out; + } + + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + if (!drm_connector_state_update_primary_fb(conn, &pending)) { + goto out; + } + + if (pending.base->tearing_page_flip && !conn->backend->supports_tearing_page_flips) { + wlr_log(WLR_ERROR, "Attempted to submit a tearing page flip to an unsupported backend!"); + goto out; + } + } + if (state->committed & WLR_OUTPUT_STATE_LAYERS) { + if (!drm_connector_set_pending_layer_fbs(conn, pending.base)) { + return false; + } + } + + if (pending.active && !pending.primary_fb) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "No primary frame buffer available for this connector"); + goto out; + } + + ok = drm_crtc_commit(conn, &pending, 0, true); + +out: + drm_connector_state_finish(&pending); + return ok; +} + +bool drm_connector_supports_vrr(struct wlr_drm_connector *conn) { + struct wlr_drm_backend *drm = conn->backend; + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + + uint64_t vrr_capable; + if (conn->props.vrr_capable == 0 || + !get_drm_prop(drm->fd, conn->id, conn->props.vrr_capable, + &vrr_capable) || !vrr_capable) { + wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to enable adaptive sync: " + "connector doesn't support VRR"); + return false; + } + + if (crtc->props.vrr_enabled == 0) { + wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to enable adaptive sync: " + "CRTC %"PRIu32" doesn't support VRR", crtc->id); + return false; + } + + return true; +} + +static bool drm_connector_commit_state(struct wlr_drm_connector *conn, + const struct wlr_output_state *base) { + struct wlr_drm_backend *drm = conn->backend; + + if (!drm->session->active) { + return false; + } + + bool ok = false; + struct wlr_drm_connector_state pending = {0}; + drm_connector_state_init(&pending, conn, base); + + if (!pending.active && conn->crtc == NULL) { + // Disabling an already-disabled connector + ok = true; + goto out; + } + + if (pending.active) { + if (!drm_connector_alloc_crtc(conn)) { + wlr_drm_conn_log(conn, WLR_ERROR, + "No CRTC available for this connector"); + goto out; + } + } + + if (pending.base->committed & WLR_OUTPUT_STATE_BUFFER) { + if (!drm_connector_state_update_primary_fb(conn, &pending)) { + goto out; + } + } + if (pending.base->committed & WLR_OUTPUT_STATE_LAYERS) { + if (!drm_connector_set_pending_layer_fbs(conn, pending.base)) { + return false; + } + } + + if (pending.modeset) { + if (pending.active) { + wlr_drm_conn_log(conn, WLR_INFO, "Modesetting with %dx%d @ %.3f Hz", + pending.mode.hdisplay, pending.mode.vdisplay, + (float)calculate_refresh_rate(&pending.mode) / 1000); + } else { + wlr_drm_conn_log(conn, WLR_INFO, "Turning off"); + } + } + + // wlr_drm_interface.crtc_commit will perform either a non-blocking + // page-flip, either a blocking modeset. When performing a blocking modeset + // we'll wait for all queued page-flips to complete, so we don't need this + // safeguard. + if (pending.nonblock && conn->pending_page_flip != NULL) { + wlr_drm_conn_log(conn, WLR_ERROR, "Failed to page-flip output: " + "a page-flip is already pending"); + goto out; + } + + uint32_t flags = 0; + if (pending.active) { + flags |= DRM_MODE_PAGE_FLIP_EVENT; + } + if (pending.base->tearing_page_flip) { + flags |= DRM_MODE_PAGE_FLIP_ASYNC; + } + + ok = drm_crtc_commit(conn, &pending, flags, false); + if (!ok) { + goto out; + } + + if (!pending.active) { + drm_plane_finish_surface(conn->crtc->primary); + drm_plane_finish_surface(conn->crtc->cursor); + drm_fb_clear(&conn->cursor_pending_fb); + + conn->cursor_enabled = false; + conn->crtc = NULL; + } + +out: + drm_connector_state_finish(&pending); + return ok; +} + +static bool drm_connector_commit(struct wlr_output *output, + const struct wlr_output_state *state) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + + if (!drm_connector_test(output, state)) { + return false; + } + + return drm_connector_commit_state(conn, state); +} + +size_t drm_crtc_get_gamma_lut_size(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc) { + if (crtc->props.gamma_lut_size == 0 || drm->iface == &legacy_iface) { + return (size_t)crtc->legacy_gamma_size; + } + + uint64_t gamma_lut_size; + if (!get_drm_prop(drm->fd, crtc->id, crtc->props.gamma_lut_size, + &gamma_lut_size)) { + wlr_log(WLR_ERROR, "Unable to get gamma lut size"); + return 0; + } + + return gamma_lut_size; +} + +static size_t drm_connector_get_gamma_size(struct wlr_output *output) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = conn->backend; + struct wlr_drm_crtc *crtc = conn->crtc; + + if (crtc == NULL) { + return 0; + } + + return drm_crtc_get_gamma_lut_size(drm, crtc); +} + +static void realloc_crtcs(struct wlr_drm_backend *drm, + struct wlr_drm_connector *want_conn); + +static bool drm_connector_alloc_crtc(struct wlr_drm_connector *conn) { + if (conn->crtc == NULL) { + realloc_crtcs(conn->backend, conn); + } + bool ok = conn->crtc != NULL; + if (!ok) { + wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to find free CRTC"); + } + return ok; +} + +static struct wlr_drm_mode *drm_mode_create(const drmModeModeInfo *modeinfo) { + struct wlr_drm_mode *mode = calloc(1, sizeof(*mode)); + if (!mode) { + return NULL; + } + + mode->drm_mode = *modeinfo; + mode->wlr_mode.width = mode->drm_mode.hdisplay; + mode->wlr_mode.height = mode->drm_mode.vdisplay; + mode->wlr_mode.refresh = calculate_refresh_rate(modeinfo); + mode->wlr_mode.picture_aspect_ratio = get_picture_aspect_ratio(modeinfo); + if (modeinfo->type & DRM_MODE_TYPE_PREFERRED) { + mode->wlr_mode.preferred = true; + } + + return mode; +} + +struct wlr_output_mode *wlr_drm_connector_add_mode(struct wlr_output *output, + const drmModeModeInfo *modeinfo) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + + if (modeinfo->type != DRM_MODE_TYPE_USERDEF) { + return NULL; + } + + struct wlr_output_mode *wlr_mode; + wl_list_for_each(wlr_mode, &conn->output.modes, link) { + struct wlr_drm_mode *mode = wl_container_of(wlr_mode, mode, wlr_mode); + if (memcmp(&mode->drm_mode, modeinfo, sizeof(*modeinfo)) == 0) { + return wlr_mode; + } + } + + struct wlr_drm_mode *mode = drm_mode_create(modeinfo); + if (!mode) { + return NULL; + } + + wl_list_insert(&conn->output.modes, &mode->wlr_mode.link); + + wlr_drm_conn_log(conn, WLR_INFO, "Registered custom mode " + "%"PRId32"x%"PRId32"@%"PRId32, + mode->wlr_mode.width, mode->wlr_mode.height, + mode->wlr_mode.refresh); + + return &mode->wlr_mode; +} + +const drmModeModeInfo *wlr_drm_mode_get_info(struct wlr_output_mode *wlr_mode) { + const struct wlr_drm_mode *mode = wl_container_of(wlr_mode, mode, wlr_mode); + return &mode->drm_mode; +} + +static bool drm_connector_set_cursor(struct wlr_output *output, + struct wlr_buffer *buffer, int hotspot_x, int hotspot_y) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = conn->backend; + struct wlr_drm_crtc *crtc = conn->crtc; + + if (!crtc) { + return false; + } + + struct wlr_drm_plane *plane = crtc->cursor; + if (plane == NULL) { + return false; + } + + if (conn->cursor_hotspot_x != hotspot_x || + conn->cursor_hotspot_y != hotspot_y) { + // Update cursor hotspot + conn->cursor_x -= hotspot_x - conn->cursor_hotspot_x; + conn->cursor_y -= hotspot_y - conn->cursor_hotspot_y; + conn->cursor_hotspot_x = hotspot_x; + conn->cursor_hotspot_y = hotspot_y; + } + + conn->cursor_enabled = false; + drm_fb_clear(&conn->cursor_pending_fb); + if (buffer != NULL) { + if ((uint64_t)buffer->width != drm->cursor_width || + (uint64_t)buffer->height != drm->cursor_height) { + wlr_drm_conn_log(conn, WLR_DEBUG, "Cursor buffer size mismatch"); + return false; + } + + struct wlr_buffer *local_buf; + if (drm->parent) { + struct wlr_drm_format format = {0}; + if (!drm_plane_pick_render_format(plane, &format, &drm->mgpu_renderer)) { + wlr_log(WLR_ERROR, "Failed to pick cursor plane format"); + return false; + } + + bool ok = init_drm_surface(&plane->mgpu_surf, &drm->mgpu_renderer, + buffer->width, buffer->height, &format); + wlr_drm_format_finish(&format); + if (!ok) { + return false; + } + + local_buf = drm_surface_blit(&plane->mgpu_surf, buffer); + if (local_buf == NULL) { + return false; + } + } else { + local_buf = wlr_buffer_lock(buffer); + } + + bool ok = drm_fb_import(&conn->cursor_pending_fb, drm, local_buf, + &plane->formats); + wlr_buffer_unlock(local_buf); + if (!ok) { + return false; + } + + conn->cursor_enabled = true; + conn->cursor_width = buffer->width; + conn->cursor_height = buffer->height; + } + + wlr_output_update_needs_frame(output); + return true; +} + +static bool drm_connector_move_cursor(struct wlr_output *output, + int x, int y) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + if (!conn->crtc) { + return false; + } + struct wlr_drm_plane *plane = conn->crtc->cursor; + if (!plane) { + return false; + } + + struct wlr_box box = { .x = x, .y = y }; + + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + enum wl_output_transform transform = + wlr_output_transform_invert(output->transform); + wlr_box_transform(&box, &box, transform, width, height); + + box.x -= conn->cursor_hotspot_x; + box.y -= conn->cursor_hotspot_y; + + conn->cursor_x = box.x; + conn->cursor_y = box.y; + + wlr_output_update_needs_frame(output); + return true; +} + +bool drm_connector_is_cursor_visible(struct wlr_drm_connector *conn) { + return conn->cursor_enabled && + conn->cursor_x < conn->output.width && + conn->cursor_y < conn->output.height && + conn->cursor_x + conn->cursor_width >= 0 && + conn->cursor_y + conn->cursor_height >= 0; +} + +static void dealloc_crtc(struct wlr_drm_connector *conn); + +/** + * Destroy the compositor-facing part of a connector. + * + * The connector isn't destroyed when disconnected. Only the compositor-facing + * wlr_output interface is cleaned up. + */ +static void drm_connector_destroy_output(struct wlr_output *output) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + + dealloc_crtc(conn); + + conn->status = DRM_MODE_DISCONNECTED; + drm_connector_set_pending_page_flip(conn, NULL); + + struct wlr_drm_mode *mode, *mode_tmp; + wl_list_for_each_safe(mode, mode_tmp, &conn->output.modes, wlr_mode.link) { + wl_list_remove(&mode->wlr_mode.link); + free(mode); + } + + conn->output = (struct wlr_output){0}; +} + +static const struct wlr_drm_format_set *drm_connector_get_cursor_formats( + struct wlr_output *output, uint32_t buffer_caps) { + if (!(buffer_caps & WLR_BUFFER_CAP_DMABUF)) { + return NULL; + } + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + if (!drm_connector_alloc_crtc(conn)) { + return NULL; + } + struct wlr_drm_plane *plane = conn->crtc->cursor; + if (!plane) { + return NULL; + } + if (conn->backend->parent) { + return &conn->backend->mgpu_formats; + } + return &plane->formats; +} + +static void drm_connector_get_cursor_size(struct wlr_output *output, + int *width, int *height) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + *width = (int)drm->cursor_width; + *height = (int)drm->cursor_height; +} + +static const struct wlr_drm_format_set *drm_connector_get_primary_formats( + struct wlr_output *output, uint32_t buffer_caps) { + if (!(buffer_caps & WLR_BUFFER_CAP_DMABUF)) { + return NULL; + } + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + if (!drm_connector_alloc_crtc(conn)) { + return NULL; + } + if (conn->backend->parent) { + return &conn->backend->mgpu_formats; + } + return &conn->crtc->primary->formats; +} + +static const struct wlr_output_impl output_impl = { + .set_cursor = drm_connector_set_cursor, + .move_cursor = drm_connector_move_cursor, + .destroy = drm_connector_destroy_output, + .test = drm_connector_test, + .commit = drm_connector_commit, + .get_gamma_size = drm_connector_get_gamma_size, + .get_cursor_formats = drm_connector_get_cursor_formats, + .get_cursor_size = drm_connector_get_cursor_size, + .get_primary_formats = drm_connector_get_primary_formats, +}; + +bool wlr_output_is_drm(struct wlr_output *output) { + return output->impl == &output_impl; +} + +uint32_t wlr_drm_connector_get_id(struct wlr_output *output) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + return conn->id; +} + +enum wl_output_transform wlr_drm_connector_get_panel_orientation( + struct wlr_output *output) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + if (!conn->props.panel_orientation) { + return WL_OUTPUT_TRANSFORM_NORMAL; + } + + char *orientation = get_drm_prop_enum(conn->backend->fd, conn->id, + conn->props.panel_orientation); + if (orientation == NULL) { + return WL_OUTPUT_TRANSFORM_NORMAL; + } + + enum wl_output_transform tr; + if (strcmp(orientation, "Normal") == 0) { + tr = WL_OUTPUT_TRANSFORM_NORMAL; + } else if (strcmp(orientation, "Left Side Up") == 0) { + tr = WL_OUTPUT_TRANSFORM_90; + } else if (strcmp(orientation, "Upside Down") == 0) { + tr = WL_OUTPUT_TRANSFORM_180; + } else if (strcmp(orientation, "Right Side Up") == 0) { + tr = WL_OUTPUT_TRANSFORM_270; + } else { + wlr_drm_conn_log(conn, WLR_ERROR, "Unknown panel orientation: %s", orientation); + tr = WL_OUTPUT_TRANSFORM_NORMAL; + } + + free(orientation); + return tr; +} + +static const int32_t subpixel_map[] = { + [DRM_MODE_SUBPIXEL_UNKNOWN] = WL_OUTPUT_SUBPIXEL_UNKNOWN, + [DRM_MODE_SUBPIXEL_HORIZONTAL_RGB] = WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB, + [DRM_MODE_SUBPIXEL_HORIZONTAL_BGR] = WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR, + [DRM_MODE_SUBPIXEL_VERTICAL_RGB] = WL_OUTPUT_SUBPIXEL_VERTICAL_RGB, + [DRM_MODE_SUBPIXEL_VERTICAL_BGR] = WL_OUTPUT_SUBPIXEL_VERTICAL_BGR, + [DRM_MODE_SUBPIXEL_NONE] = WL_OUTPUT_SUBPIXEL_NONE, +}; + +static void dealloc_crtc(struct wlr_drm_connector *conn) { + if (conn->crtc == NULL) { + return; + } + + wlr_drm_conn_log(conn, WLR_DEBUG, "De-allocating CRTC %" PRIu32, + conn->crtc->id); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, false); + if (!drm_connector_commit_state(conn, &state)) { + // On GPU unplug, disabling the CRTC can fail with EPERM + wlr_drm_conn_log(conn, WLR_ERROR, "Failed to disable CRTC %"PRIu32, + conn->crtc->id); + } + wlr_output_state_finish(&state); +} + +static void format_nullable_crtc(char *str, size_t size, struct wlr_drm_crtc *crtc) { + if (crtc != NULL) { + snprintf(str, size, "CRTC %"PRIu32, crtc->id); + } else { + snprintf(str, size, "no CRTC"); + } +} + +static void realloc_crtcs(struct wlr_drm_backend *drm, + struct wlr_drm_connector *want_conn) { + assert(drm->num_crtcs > 0); + + size_t num_connectors = wl_list_length(&drm->connectors); + if (num_connectors == 0) { + return; + } + + wlr_log(WLR_DEBUG, "Reallocating CRTCs"); + + struct wlr_drm_connector *connectors[num_connectors]; + uint32_t connector_constraints[num_connectors]; + uint32_t previous_match[drm->num_crtcs]; + uint32_t new_match[drm->num_crtcs]; + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + previous_match[i] = UNMATCHED; + } + + size_t i = 0; + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->connectors, link) { + connectors[i] = conn; + + if (conn->crtc) { + previous_match[conn->crtc - drm->crtcs] = i; + } + + // Only request a CRTC if the connected is currently enabled or it's the + // connector the user wants to enable + bool want_crtc = conn == want_conn || conn->output.enabled; + + if (conn->status == DRM_MODE_CONNECTED && want_crtc) { + connector_constraints[i] = conn->possible_crtcs; + } else { + // Will always fail to match anything + connector_constraints[i] = 0; + } + + ++i; + } + + match_obj(num_connectors, connector_constraints, + drm->num_crtcs, previous_match, new_match); + + // Converts our crtc=>connector result into a connector=>crtc one. + struct wlr_drm_crtc *connector_match[num_connectors]; + for (size_t i = 0 ; i < num_connectors; ++i) { + connector_match[i] = NULL; + } + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (new_match[i] != UNMATCHED) { + connector_match[new_match[i]] = &drm->crtcs[i]; + } + } + + for (size_t i = 0; i < num_connectors; ++i) { + struct wlr_drm_connector *conn = connectors[i]; + struct wlr_drm_crtc *new_crtc = connector_match[i]; + + char old_crtc_str[16], new_crtc_str[16]; + format_nullable_crtc(old_crtc_str, sizeof(old_crtc_str), conn->crtc); + format_nullable_crtc(new_crtc_str, sizeof(new_crtc_str), new_crtc); + + char crtc_str[64]; + if (conn->crtc != new_crtc) { + snprintf(crtc_str, sizeof(crtc_str), "%s → %s", old_crtc_str, new_crtc_str); + } else { + snprintf(crtc_str, sizeof(crtc_str), "%s (no change)", new_crtc_str); + } + + wlr_log(WLR_DEBUG, " Connector %s (%s%s): %s", + conn->name, drm_connector_status_str(conn->status), + connector_constraints[i] != 0 ? ", needs CRTC" : "", + crtc_str); + } + + // Refuse to remove a CRTC from an enabled connector, and refuse to + // change the CRTC of an enabled connector. + for (size_t i = 0; i < num_connectors; ++i) { + struct wlr_drm_connector *conn = connectors[i]; + if (conn->status != DRM_MODE_CONNECTED || !conn->output.enabled) { + continue; + } + if (connector_match[i] == NULL) { + wlr_log(WLR_DEBUG, "Could not match a CRTC for previously connected output; " + "keeping old configuration"); + return; + } + assert(conn->crtc != NULL); + if (connector_match[i] != conn->crtc) { + wlr_log(WLR_DEBUG, "Cannot switch CRTC for enabled output; " + "keeping old configuration"); + return; + } + } + + // Apply new configuration + for (size_t i = 0; i < num_connectors; ++i) { + struct wlr_drm_connector *conn = connectors[i]; + + if (conn->crtc != NULL && connector_match[i]) { + // We don't need to change anything + continue; + } + + dealloc_crtc(conn); + if (connector_match[i] != NULL) { + conn->crtc = connector_match[i]; + } + } +} + +static struct wlr_drm_crtc *connector_get_current_crtc( + struct wlr_drm_connector *wlr_conn, const drmModeConnector *drm_conn) { + struct wlr_drm_backend *drm = wlr_conn->backend; + + uint32_t crtc_id = 0; + if (wlr_conn->props.crtc_id != 0) { + uint64_t value; + if (!get_drm_prop(drm->fd, wlr_conn->id, + wlr_conn->props.crtc_id, &value)) { + wlr_drm_conn_log(wlr_conn, WLR_ERROR, + "Failed to get CRTC_ID connector property"); + return NULL; + } + crtc_id = (uint32_t)value; + } else if (drm_conn->encoder_id != 0) { + // Fallback to the legacy API + drmModeEncoder *enc = drmModeGetEncoder(drm->fd, drm_conn->encoder_id); + if (enc == NULL) { + wlr_drm_conn_log(wlr_conn, WLR_ERROR, + "drmModeGetEncoder() failed"); + return NULL; + } + crtc_id = enc->crtc_id; + drmModeFreeEncoder(enc); + } + if (crtc_id == 0) { + return NULL; + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (drm->crtcs[i].id == crtc_id) { + return &drm->crtcs[i]; + } + } + + wlr_drm_conn_log(wlr_conn, WLR_ERROR, + "Failed to find current CRTC ID %" PRIu32, crtc_id); + return NULL; +} + +static struct wlr_drm_connector *create_drm_connector(struct wlr_drm_backend *drm, + const drmModeConnector *drm_conn) { + struct wlr_drm_connector *wlr_conn = calloc(1, sizeof(*wlr_conn)); + if (!wlr_conn) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + wlr_conn->backend = drm; + wlr_conn->status = DRM_MODE_DISCONNECTED; + wlr_conn->id = drm_conn->connector_id; + + if (!get_drm_connector_props(drm->fd, wlr_conn->id, &wlr_conn->props)) { + free(wlr_conn); + return false; + } + + const char *conn_name = + drmModeGetConnectorTypeName(drm_conn->connector_type); + if (conn_name == NULL) { + conn_name = "Unknown"; + } + + snprintf(wlr_conn->name, sizeof(wlr_conn->name), + "%s-%"PRIu32, conn_name, drm_conn->connector_type_id); + + wlr_conn->possible_crtcs = + drmModeConnectorGetPossibleCrtcs(drm->fd, drm_conn); + if (wlr_conn->possible_crtcs == 0) { + wlr_drm_conn_log(wlr_conn, WLR_ERROR, "No CRTC possible"); + } + + wlr_conn->crtc = connector_get_current_crtc(wlr_conn, drm_conn); + + wl_list_insert(drm->connectors.prev, &wlr_conn->link); + return wlr_conn; +} + +static drmModeModeInfo *connector_get_current_mode(struct wlr_drm_connector *wlr_conn) { + struct wlr_drm_backend *drm = wlr_conn->backend; + + if (wlr_conn->crtc == NULL) { + return NULL; + } + + if (wlr_conn->crtc->props.mode_id != 0) { + size_t size = 0; + drmModeModeInfo *mode = get_drm_prop_blob(drm->fd, wlr_conn->crtc->id, + wlr_conn->crtc->props.mode_id, &size); + assert(mode == NULL || size == sizeof(*mode)); + return mode; + } else { + // Fallback to the legacy API + drmModeCrtc *drm_crtc = drmModeGetCrtc(drm->fd, wlr_conn->crtc->id); + if (drm_crtc == NULL) { + wlr_log_errno(WLR_ERROR, "drmModeGetCrtc failed"); + return NULL; + } + if (!drm_crtc->mode_valid) { + drmModeFreeCrtc(drm_crtc); + return NULL; + } + drmModeModeInfo *mode = malloc(sizeof(*mode)); + if (mode == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + drmModeFreeCrtc(drm_crtc); + return NULL; + } + *mode = drm_crtc->mode; + drmModeFreeCrtc(drm_crtc); + return mode; + } +} + +static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn, + const drmModeConnector *drm_conn) { + struct wlr_drm_backend *drm = wlr_conn->backend; + struct wlr_output *output = &wlr_conn->output; + + wlr_log(WLR_DEBUG, "Current CRTC: %d", + wlr_conn->crtc ? (int)wlr_conn->crtc->id : -1); + + // keep track of all the modes ourselves first. We must only fill out + // the modes list after wlr_output_init() + struct wl_list modes; + wl_list_init(&modes); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, wlr_conn->crtc != NULL); + + drmModeModeInfo *current_modeinfo = connector_get_current_mode(wlr_conn); + + wlr_log(WLR_INFO, "Detected modes:"); + + for (int i = 0; i < drm_conn->count_modes; ++i) { + if (drm_conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) { + continue; + } + + struct wlr_drm_mode *mode = drm_mode_create(&drm_conn->modes[i]); + if (!mode) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + free(current_modeinfo); + wlr_output_state_finish(&state); + return false; + } + + // If this is the current mode set on the conn's crtc, + // then set it as the conn's output current mode. + if (current_modeinfo != NULL && memcmp(&mode->drm_mode, + current_modeinfo, sizeof(*current_modeinfo)) == 0) { + wlr_output_state_set_mode(&state, &mode->wlr_mode); + + uint64_t mode_id = 0; + get_drm_prop(drm->fd, wlr_conn->crtc->id, + wlr_conn->crtc->props.mode_id, &mode_id); + + wlr_conn->crtc->own_mode_id = false; + wlr_conn->crtc->mode_id = mode_id; + wlr_conn->refresh = calculate_refresh_rate(current_modeinfo); + } + + wlr_log(WLR_INFO, " %"PRId32"x%"PRId32" @ %.3f Hz %s", + mode->wlr_mode.width, mode->wlr_mode.height, + (float)mode->wlr_mode.refresh / 1000, + mode->wlr_mode.preferred ? "(preferred)" : ""); + + wl_list_insert(modes.prev, &mode->wlr_mode.link); + } + + free(current_modeinfo); + + wlr_output_init(output, &drm->backend, &output_impl, drm->session->event_loop, &state); + wlr_output_state_finish(&state); + + // fill out the modes + wl_list_insert_list(&output->modes, &modes); + + wlr_output_set_name(output, wlr_conn->name); + + output->phys_width = drm_conn->mmWidth; + output->phys_height = drm_conn->mmHeight; + wlr_log(WLR_INFO, "Physical size: %"PRId32"x%"PRId32, + output->phys_width, output->phys_height); + if (drm_conn->subpixel < sizeof(subpixel_map) / sizeof(subpixel_map[0])) { + output->subpixel = subpixel_map[drm_conn->subpixel]; + } else { + wlr_log(WLR_ERROR, "Unknown subpixel value: %d", (int)drm_conn->subpixel); + } + + uint64_t non_desktop; + if (get_drm_prop(drm->fd, wlr_conn->id, + wlr_conn->props.non_desktop, &non_desktop)) { + if (non_desktop == 1) { + wlr_log(WLR_INFO, "Non-desktop connector"); + } + output->non_desktop = non_desktop; + } + + memset(wlr_conn->max_bpc_bounds, 0, sizeof(wlr_conn->max_bpc_bounds)); + if (wlr_conn->props.max_bpc != 0) { + if (!introspect_drm_prop_range(drm->fd, wlr_conn->props.max_bpc, + &wlr_conn->max_bpc_bounds[0], &wlr_conn->max_bpc_bounds[1])) { + wlr_log(WLR_ERROR, "Failed to introspect 'max bpc' property"); + } + } + + size_t edid_len = 0; + uint8_t *edid = get_drm_prop_blob(drm->fd, + wlr_conn->id, wlr_conn->props.edid, &edid_len); + parse_edid(wlr_conn, edid_len, edid); + free(edid); + + char *subconnector = NULL; + if (wlr_conn->props.subconnector) { + subconnector = get_drm_prop_enum(drm->fd, + wlr_conn->id, wlr_conn->props.subconnector); + } + if (subconnector && strcmp(subconnector, "Native") == 0) { + free(subconnector); + subconnector = NULL; + } + + char description[128]; + snprintf(description, sizeof(description), "%s %s%s%s (%s%s%s)", + output->make, output->model, + output->serial ? " " : "", + output->serial ? output->serial : "", + output->name, + subconnector ? " via " : "", + subconnector ? subconnector : ""); + wlr_output_set_description(output, description); + + free(subconnector); + wlr_conn->status = DRM_MODE_CONNECTED; + return true; +} + +static void disconnect_drm_connector(struct wlr_drm_connector *conn); + +void scan_drm_connectors(struct wlr_drm_backend *drm, + struct wlr_device_hotplug_event *event) { + if (event != NULL && event->connector_id != 0) { + wlr_log(WLR_INFO, "Scanning DRM connector %"PRIu32" on %s", + event->connector_id, drm->name); + } else { + wlr_log(WLR_INFO, "Scanning DRM connectors on %s", drm->name); + } + + drmModeRes *res = drmModeGetResources(drm->fd); + if (!res) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM resources"); + return; + } + + size_t seen_len = wl_list_length(&drm->connectors); + // +1 so length can never be 0, which is undefined behaviour. + // Last element isn't used. + bool seen[seen_len + 1]; + memset(seen, false, sizeof(seen)); + size_t new_outputs_len = 0; + struct wlr_drm_connector *new_outputs[res->count_connectors + 1]; + + for (int i = 0; i < res->count_connectors; ++i) { + uint32_t conn_id = res->connectors[i]; + + ssize_t index = -1; + struct wlr_drm_connector *c, *wlr_conn = NULL; + wl_list_for_each(c, &drm->connectors, link) { + index++; + if (c->id == conn_id) { + wlr_conn = c; + break; + } + } + + // If the hotplug event contains a connector ID, ignore any other + // connector. + if (event != NULL && event->connector_id != 0 && + event->connector_id != conn_id) { + if (wlr_conn != NULL) { + seen[index] = true; + } + continue; + } + + drmModeConnector *drm_conn = drmModeGetConnector(drm->fd, conn_id); + if (!drm_conn) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM connector"); + continue; + } + + if (!wlr_conn) { + wlr_conn = create_drm_connector(drm, drm_conn); + if (wlr_conn == NULL) { + continue; + } + wlr_log(WLR_INFO, "Found connector '%s'", wlr_conn->name); + } else { + seen[index] = true; + } + + // This can only happen *after* hotplug, since we haven't read the + // connector properties yet + if (wlr_conn->props.link_status != 0) { + uint64_t link_status; + if (!get_drm_prop(drm->fd, wlr_conn->id, + wlr_conn->props.link_status, &link_status)) { + wlr_drm_conn_log(wlr_conn, WLR_ERROR, + "Failed to get link status prop"); + continue; + } + + if (link_status == DRM_MODE_LINK_STATUS_BAD) { + // We need to reload our list of modes and force a modeset + wlr_drm_conn_log(wlr_conn, WLR_INFO, "Bad link detected"); + disconnect_drm_connector(wlr_conn); + } + } + + if (wlr_conn->status == DRM_MODE_DISCONNECTED && + drm_conn->connection == DRM_MODE_CONNECTED) { + wlr_log(WLR_INFO, "'%s' connected", wlr_conn->name); + if (!connect_drm_connector(wlr_conn, drm_conn)) { + wlr_drm_conn_log(wlr_conn, WLR_ERROR, "Failed to connect DRM connector"); + continue; + } + new_outputs[new_outputs_len++] = wlr_conn; + } else if (wlr_conn->status == DRM_MODE_CONNECTED && + drm_conn->connection != DRM_MODE_CONNECTED) { + wlr_log(WLR_INFO, "'%s' disconnected", wlr_conn->name); + disconnect_drm_connector(wlr_conn); + } + + drmModeFreeConnector(drm_conn); + } + + drmModeFreeResources(res); + + // Iterate in reverse order because we'll remove items from the list and + // still want indices to remain correct. + struct wlr_drm_connector *conn, *tmp_conn; + size_t index = wl_list_length(&drm->connectors); + wl_list_for_each_reverse_safe(conn, tmp_conn, &drm->connectors, link) { + index--; + if (index >= seen_len || seen[index]) { + continue; + } + + wlr_log(WLR_INFO, "'%s' disappeared", conn->name); + destroy_drm_connector(conn); + } + + realloc_crtcs(drm, NULL); + + for (size_t i = 0; i < new_outputs_len; ++i) { + struct wlr_drm_connector *conn = new_outputs[i]; + + wlr_drm_conn_log(conn, WLR_INFO, "Requesting modeset"); + wl_signal_emit_mutable(&drm->backend.events.new_output, + &conn->output); + } +} + +void scan_drm_leases(struct wlr_drm_backend *drm) { + drmModeLesseeListRes *list = drmModeListLessees(drm->fd); + if (list == NULL) { + wlr_log_errno(WLR_ERROR, "drmModeListLessees failed"); + return; + } + + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->connectors, link) { + if (conn->lease == NULL) { + continue; + } + + bool found = false; + for (size_t i = 0; i < list->count; i++) { + if (list->lessees[i] == conn->lease->lessee_id) { + found = true; + break; + } + } + if (!found) { + wlr_log(WLR_DEBUG, "DRM lease %"PRIu32" has been terminated", + conn->lease->lessee_id); + drm_lease_destroy(conn->lease); + } + } + + drmFree(list); +} + +static void build_current_connector_state(struct wlr_output_state *state, + struct wlr_drm_connector *conn) { + bool enabled = conn->status != DRM_MODE_DISCONNECTED && conn->output.enabled; + + wlr_output_state_init(state); + wlr_output_state_set_enabled(state, enabled); + if (!enabled) { + return; + } + + if (conn->output.current_mode != NULL) { + wlr_output_state_set_mode(state, conn->output.current_mode); + } else { + wlr_output_state_set_custom_mode(state, + conn->output.width, conn->output.height, conn->output.refresh); + } +} + +/** + * Check whether we need to perform a full reset after a VT switch. + * + * If any connector or plane has a different CRTC, we need to perform a full + * reset to restore our mapping. We couldn't avoid a full reset even if we + * used a single KMS atomic commit to apply our state: the kernel rejects + * commits which migrate a plane from one CRTC to another without going through + * an intermediate state where the plane is disabled. + */ +static bool skip_reset_for_restore(struct wlr_drm_backend *drm) { + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->connectors, link) { + drmModeConnector *drm_conn = drmModeGetConnectorCurrent(drm->fd, conn->id); + if (drm_conn == NULL) { + return false; + } + struct wlr_drm_crtc *crtc = connector_get_current_crtc(conn, drm_conn); + drmModeFreeConnector(drm_conn); + + if (crtc != NULL && conn->crtc != crtc) { + return false; + } + } + + for (size_t i = 0; i < drm->num_planes; i++) { + struct wlr_drm_plane *plane = &drm->planes[i]; + + drmModePlane *drm_plane = drmModeGetPlane(drm->fd, plane->id); + if (drm_plane == NULL) { + return false; + } + uint32_t crtc_id = drm_plane->crtc_id; + drmModeFreePlane(drm_plane); + + struct wlr_drm_crtc *crtc = NULL; + for (size_t i = 0; i < drm->num_crtcs; i++) { + if (drm->crtcs[i].id == crtc_id) { + crtc = &drm->crtcs[i]; + break; + } + } + if (crtc == NULL) { + continue; + } + + bool ok = false; + switch (plane->type) { + case DRM_PLANE_TYPE_PRIMARY: + ok = crtc->primary == plane; + break; + case DRM_PLANE_TYPE_CURSOR: + ok = crtc->cursor == plane; + break; + } + if (!ok) { + return false; + } + } + + return true; +} + +void restore_drm_device(struct wlr_drm_backend *drm) { + // The previous DRM master leaves KMS in an undefined state. We need + // to restore our own state, but be careful to avoid invalid + // configurations. The connector/CRTC mapping may have changed, so + // first disable all CRTCs, then light up the ones we were using + // before the VT switch. + // TODO: better use the atomic API to improve restoration after a VT switch + if (!skip_reset_for_restore(drm) && !drm->iface->reset(drm)) { + wlr_log(WLR_ERROR, "Failed to reset state after VT switch"); + } + + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->connectors, link) { + struct wlr_output_state state; + build_current_connector_state(&state, conn); + if (!drm_connector_commit_state(conn, &state)) { + wlr_drm_conn_log(conn, WLR_ERROR, "Failed to restore state after VT switch"); + } + wlr_output_state_finish(&state); + } +} + +static int mhz_to_nsec(int mhz) { + return 1000000000000LL / mhz; +} + +static void handle_page_flip(int fd, unsigned seq, + unsigned tv_sec, unsigned tv_usec, unsigned crtc_id, void *data) { + struct wlr_drm_page_flip *page_flip = data; + + struct wlr_drm_connector *conn = page_flip->conn; + if (conn != NULL) { + conn->pending_page_flip = NULL; + } + drm_page_flip_destroy(page_flip); + + if (conn == NULL) { + return; + } + + struct wlr_drm_backend *drm = conn->backend; + + if (conn->status != DRM_MODE_CONNECTED || conn->crtc == NULL) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "Ignoring page-flip event for disabled connector"); + return; + } + + struct wlr_drm_plane *plane = conn->crtc->primary; + if (plane->queued_fb) { + drm_fb_move(&plane->current_fb, &plane->queued_fb); + } + if (conn->crtc->cursor && conn->crtc->cursor->queued_fb) { + drm_fb_move(&conn->crtc->cursor->current_fb, + &conn->crtc->cursor->queued_fb); + } + + struct wlr_drm_layer *layer; + wl_list_for_each(layer, &conn->crtc->layers, link) { + drm_fb_move(&layer->current_fb, &layer->queued_fb); + } + + uint32_t present_flags = WLR_OUTPUT_PRESENT_VSYNC | + WLR_OUTPUT_PRESENT_HW_CLOCK | WLR_OUTPUT_PRESENT_HW_COMPLETION; + /* Don't report ZERO_COPY in multi-gpu situations, because we had to copy + * data between the GPUs, even if we were using the direct scanout + * interface. + */ + if (!drm->parent) { + present_flags |= WLR_OUTPUT_PRESENT_ZERO_COPY; + } + + struct timespec present_time = { + .tv_sec = tv_sec, + .tv_nsec = tv_usec * 1000, + }; + struct wlr_output_event_present present_event = { + /* The DRM backend guarantees that the presentation event will be for + * the last submitted frame. */ + .commit_seq = conn->output.commit_seq, + .presented = drm->session->active, + .when = &present_time, + .seq = seq, + .refresh = mhz_to_nsec(conn->refresh), + .flags = present_flags, + }; + wlr_output_send_present(&conn->output, &present_event); + + if (drm->session->active) { + wlr_output_send_frame(&conn->output); + } +} + +int handle_drm_event(int fd, uint32_t mask, void *data) { + struct wlr_drm_backend *drm = data; + + drmEventContext event = { + .version = 3, + .page_flip_handler2 = handle_page_flip, + }; + + if (drmHandleEvent(fd, &event) != 0) { + wlr_log(WLR_ERROR, "drmHandleEvent failed"); + wlr_backend_destroy(&drm->backend); + } + return 1; +} + +static void disconnect_drm_connector(struct wlr_drm_connector *conn) { + if (conn->status == DRM_MODE_DISCONNECTED) { + return; + } + + // This will cleanup the compositor-facing wlr_output, but won't destroy + // our wlr_drm_connector. + wlr_output_destroy(&conn->output); + + assert(conn->status == DRM_MODE_DISCONNECTED); +} + +void destroy_drm_connector(struct wlr_drm_connector *conn) { + disconnect_drm_connector(conn); + + wl_list_remove(&conn->link); + free(conn); +} + +int wlr_drm_backend_get_non_master_fd(struct wlr_backend *backend) { + assert(backend); + + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + int fd = open(drm->name, O_RDWR | O_CLOEXEC); + + if (fd < 0) { + wlr_log_errno(WLR_ERROR, "Unable to clone DRM fd for client fd"); + return -1; + } + + if (drmIsMaster(fd) && drmDropMaster(fd) < 0) { + wlr_log_errno(WLR_ERROR, "Failed to drop master"); + return -1; + } + + return fd; +} + +struct wlr_drm_lease *wlr_drm_create_lease(struct wlr_output **outputs, + size_t n_outputs, int *lease_fd_ptr) { + assert(outputs); + + if (n_outputs == 0) { + wlr_log(WLR_ERROR, "Can't lease 0 outputs"); + return NULL; + } + + struct wlr_drm_backend *drm = + get_drm_backend_from_backend(outputs[0]->backend); + + int n_objects = 0; + uint32_t objects[4 * n_outputs + 1]; + for (size_t i = 0; i < n_outputs; ++i) { + struct wlr_drm_connector *conn = + get_drm_connector_from_output(outputs[i]); + assert(conn->lease == NULL); + + if (conn->backend != drm) { + wlr_log(WLR_ERROR, "Can't lease output from different backends"); + return NULL; + } + + objects[n_objects++] = conn->id; + wlr_log(WLR_DEBUG, "Connector %d", conn->id); + + if (!drm_connector_alloc_crtc(conn)) { + wlr_log(WLR_ERROR, "Failled to allocate connector CRTC"); + return NULL; + } + + objects[n_objects++] = conn->crtc->id; + wlr_log(WLR_DEBUG, "CRTC %d", conn->crtc->id); + + objects[n_objects++] = conn->crtc->primary->id; + wlr_log(WLR_DEBUG, "Primary plane %d", conn->crtc->primary->id); + + if (conn->crtc->cursor) { + wlr_log(WLR_DEBUG, "Cursor plane %d", conn->crtc->cursor->id); + objects[n_objects++] = conn->crtc->cursor->id; + } + } + + assert(n_objects != 0); + + struct wlr_drm_lease *lease = calloc(1, sizeof(*lease)); + if (lease == NULL) { + return NULL; + } + + lease->backend = drm; + wl_signal_init(&lease->events.destroy); + + wlr_log(WLR_DEBUG, "Issuing DRM lease with %d objects", n_objects); + int lease_fd = drmModeCreateLease(drm->fd, objects, n_objects, O_CLOEXEC, + &lease->lessee_id); + if (lease_fd < 0) { + free(lease); + return NULL; + } + *lease_fd_ptr = lease_fd; + + wlr_log(WLR_DEBUG, "Issued DRM lease %"PRIu32, lease->lessee_id); + for (size_t i = 0; i < n_outputs; ++i) { + struct wlr_drm_connector *conn = + get_drm_connector_from_output(outputs[i]); + conn->lease = lease; + conn->crtc->lease = lease; + } + + return lease; +} + +void wlr_drm_lease_terminate(struct wlr_drm_lease *lease) { + struct wlr_drm_backend *drm = lease->backend; + + wlr_log(WLR_DEBUG, "Terminating DRM lease %d", lease->lessee_id); + int ret = drmModeRevokeLease(drm->fd, lease->lessee_id); + if (ret < 0) { + wlr_log_errno(WLR_ERROR, "Failed to terminate lease"); + } + + drm_lease_destroy(lease); +} + +void drm_lease_destroy(struct wlr_drm_lease *lease) { + struct wlr_drm_backend *drm = lease->backend; + + wl_signal_emit_mutable(&lease->events.destroy, NULL); + + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->connectors, link) { + if (conn->lease == lease) { + conn->lease = NULL; + } + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (drm->crtcs[i].lease == lease) { + drm->crtcs[i].lease = NULL; + } + } + + free(lease); +} diff --git a/backend/drm/fb.c b/backend/drm/fb.c new file mode 100644 index 0000000..575f32d --- /dev/null +++ b/backend/drm/fb.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/drm/drm.h" +#include "backend/drm/fb.h" +#include "render/pixel_format.h" + +void drm_fb_clear(struct wlr_drm_fb **fb_ptr) { + if (*fb_ptr == NULL) { + return; + } + + struct wlr_drm_fb *fb = *fb_ptr; + wlr_buffer_unlock(fb->wlr_buf); // may destroy the buffer + + *fb_ptr = NULL; +} + +struct wlr_drm_fb *drm_fb_lock(struct wlr_drm_fb *fb) { + wlr_buffer_lock(fb->wlr_buf); + return fb; +} + +static void drm_fb_handle_destroy(struct wlr_addon *addon) { + struct wlr_drm_fb *fb = wl_container_of(addon, fb, addon); + drm_fb_destroy(fb); +} + +static const struct wlr_addon_interface fb_addon_impl = { + .name = "wlr_drm_fb", + .destroy = drm_fb_handle_destroy, +}; + +static uint32_t get_fb_for_bo(struct wlr_drm_backend *drm, + struct wlr_dmabuf_attributes *dmabuf, uint32_t handles[static 4]) { + uint64_t modifiers[4] = {0}; + for (int i = 0; i < dmabuf->n_planes; i++) { + // KMS requires all BO planes to have the same modifier + modifiers[i] = dmabuf->modifier; + } + + uint32_t id = 0; + if (drm->addfb2_modifiers && dmabuf->modifier != DRM_FORMAT_MOD_INVALID) { + if (drmModeAddFB2WithModifiers(drm->fd, dmabuf->width, dmabuf->height, + dmabuf->format, handles, dmabuf->stride, dmabuf->offset, + modifiers, &id, DRM_MODE_FB_MODIFIERS) != 0) { + wlr_log_errno(WLR_DEBUG, "drmModeAddFB2WithModifiers failed"); + } + } else { + if (dmabuf->modifier != DRM_FORMAT_MOD_INVALID && + dmabuf->modifier != DRM_FORMAT_MOD_LINEAR) { + wlr_log(WLR_ERROR, "Cannot import DRM framebuffer with explicit " + "modifier 0x%"PRIX64, dmabuf->modifier); + return 0; + } + + int ret = drmModeAddFB2(drm->fd, dmabuf->width, dmabuf->height, + dmabuf->format, handles, dmabuf->stride, dmabuf->offset, &id, 0); + if (ret != 0 && dmabuf->format == DRM_FORMAT_ARGB8888 && + dmabuf->n_planes == 1 && dmabuf->offset[0] == 0) { + // Some big-endian machines don't support drmModeAddFB2. Try a + // last-resort fallback for ARGB8888 buffers, like Xorg's + // modesetting driver does. + wlr_log(WLR_DEBUG, "drmModeAddFB2 failed (%s), falling back to " + "legacy drmModeAddFB", strerror(-ret)); + + uint32_t depth = 32; + uint32_t bpp = 32; + ret = drmModeAddFB(drm->fd, dmabuf->width, dmabuf->height, depth, + bpp, dmabuf->stride[0], handles[0], &id); + if (ret != 0) { + wlr_log_errno(WLR_DEBUG, "drmModeAddFB failed"); + } + } else if (ret != 0) { + wlr_log_errno(WLR_DEBUG, "drmModeAddFB2 failed"); + } + } + + return id; +} + +static void close_all_bo_handles(struct wlr_drm_backend *drm, + uint32_t handles[static 4]) { + for (int i = 0; i < 4; ++i) { + if (handles[i] == 0) { + continue; + } + + // If multiple planes share the same BO handle, avoid double-closing it + bool already_closed = false; + for (int j = 0; j < i; ++j) { + if (handles[i] == handles[j]) { + already_closed = true; + break; + } + } + if (already_closed) { + continue; + } + + if (drmCloseBufferHandle(drm->fd, handles[i]) != 0) { + wlr_log_errno(WLR_ERROR, "drmCloseBufferHandle failed"); + } + } +} + +static void drm_poisoned_fb_handle_destroy(struct wlr_addon *addon) { + wlr_addon_finish(addon); + free(addon); +} + +static const struct wlr_addon_interface poisoned_fb_addon_impl = { + .name = "wlr_drm_poisoned_fb", + .destroy = drm_poisoned_fb_handle_destroy, +}; + +static bool is_buffer_poisoned(struct wlr_drm_backend *drm, + struct wlr_buffer *buf) { + return wlr_addon_find(&buf->addons, drm, &poisoned_fb_addon_impl) != NULL; +} + +/** + * Mark the buffer as "poisoned", ie. it cannot be imported into KMS. This + * allows us to avoid repeatedly trying to import it when it's not + * scanout-capable. + */ +static void poison_buffer(struct wlr_drm_backend *drm, + struct wlr_buffer *buf) { + struct wlr_addon *addon = calloc(1, sizeof(*addon)); + if (addon == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return; + } + wlr_addon_init(addon, &buf->addons, drm, &poisoned_fb_addon_impl); + wlr_log(WLR_DEBUG, "Poisoning buffer"); +} + +static struct wlr_drm_fb *drm_fb_create(struct wlr_drm_backend *drm, + struct wlr_buffer *buf, const struct wlr_drm_format_set *formats) { + struct wlr_dmabuf_attributes attribs; + if (!wlr_buffer_get_dmabuf(buf, &attribs)) { + wlr_log(WLR_DEBUG, "Failed to get DMA-BUF from buffer"); + return NULL; + } + + if (is_buffer_poisoned(drm, buf)) { + wlr_log(WLR_DEBUG, "Buffer is poisoned"); + return NULL; + } + + struct wlr_drm_fb *fb = calloc(1, sizeof(*fb)); + if (!fb) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + if (formats && !wlr_drm_format_set_has(formats, attribs.format, + attribs.modifier)) { + // The format isn't supported by the plane. Try stripping the alpha + // channel, if any. + const struct wlr_pixel_format_info *info = + drm_get_pixel_format_info(attribs.format); + if (info != NULL && info->opaque_substitute != DRM_FORMAT_INVALID && + wlr_drm_format_set_has(formats, info->opaque_substitute, attribs.modifier)) { + attribs.format = info->opaque_substitute; + } else { + wlr_log(WLR_DEBUG, "Buffer format 0x%"PRIX32" with modifier " + "0x%"PRIX64" cannot be scanned out", + attribs.format, attribs.modifier); + goto error_fb; + } + } + + uint32_t handles[4] = {0}; + for (int i = 0; i < attribs.n_planes; ++i) { + int ret = drmPrimeFDToHandle(drm->fd, attribs.fd[i], &handles[i]); + if (ret != 0) { + wlr_log_errno(WLR_DEBUG, "drmPrimeFDToHandle failed"); + goto error_bo_handle; + } + } + + fb->id = get_fb_for_bo(drm, &attribs, handles); + if (!fb->id) { + wlr_log(WLR_DEBUG, "Failed to import BO in KMS"); + poison_buffer(drm, buf); + goto error_bo_handle; + } + + close_all_bo_handles(drm, handles); + + fb->backend = drm; + fb->wlr_buf = buf; + + wlr_addon_init(&fb->addon, &buf->addons, drm, &fb_addon_impl); + wl_list_insert(&drm->fbs, &fb->link); + + return fb; + +error_bo_handle: + close_all_bo_handles(drm, handles); +error_fb: + free(fb); + return NULL; +} + +void drm_fb_destroy(struct wlr_drm_fb *fb) { + struct wlr_drm_backend *drm = fb->backend; + + wl_list_remove(&fb->link); + wlr_addon_finish(&fb->addon); + + int ret = drmModeCloseFB(drm->fd, fb->id); + if (ret == -EINVAL) { + ret = drmModeRmFB(drm->fd, fb->id); + } + if (ret != 0) { + wlr_log(WLR_ERROR, "Failed to close FB: %s", strerror(-ret)); + } + + free(fb); +} + +bool drm_fb_import(struct wlr_drm_fb **fb_ptr, struct wlr_drm_backend *drm, + struct wlr_buffer *buf, const struct wlr_drm_format_set *formats) { + struct wlr_drm_fb *fb; + struct wlr_addon *addon = wlr_addon_find(&buf->addons, drm, &fb_addon_impl); + if (addon != NULL) { + fb = wl_container_of(addon, fb, addon); + } else { + fb = drm_fb_create(drm, buf, formats); + if (!fb) { + return false; + } + } + + wlr_buffer_lock(buf); + drm_fb_move(fb_ptr, &fb); + return true; +} + +void drm_fb_move(struct wlr_drm_fb **new, struct wlr_drm_fb **old) { + drm_fb_clear(new); + *new = *old; + *old = NULL; +} + +void drm_fb_copy(struct wlr_drm_fb **new, struct wlr_drm_fb *old) { + drm_fb_clear(new); + if (old != NULL) { + *new = drm_fb_lock(old); + } +} diff --git a/backend/drm/gen_pnpids.sh b/backend/drm/gen_pnpids.sh new file mode 100755 index 0000000..5a9547a --- /dev/null +++ b/backend/drm/gen_pnpids.sh @@ -0,0 +1,26 @@ +#!/bin/sh -eu +# +# usage: gen_pnpids.sh < pnp.ids > pnpids.c + +gen_pnps() +{ + while read -r id vendor; do + [ "${#id}" = 3 ] || exit 1 + + printf "\tcase PNP_ID('%c', '%c', '%c'): return \"%s\";\n" \ + "$id" "${id#?}" "${id#??}" "$vendor" + done +} + +cat << EOF +#include "backend/drm/util.h" + +#define PNP_ID(a, b, c) ((a & 0x1f) << 10) | ((b & 0x1f) << 5) | (c & 0x1f) +const char *get_pnp_manufacturer(const char code[static 3]) { + switch (PNP_ID(code[0], code[1], code[2])) { +$(gen_pnps) + } + return NULL; +} +#undef PNP_ID +EOF diff --git a/backend/drm/legacy.c b/backend/drm/legacy.c new file mode 100644 index 0000000..f8cabbd --- /dev/null +++ b/backend/drm/legacy.c @@ -0,0 +1,246 @@ +#include +#include +#include +#include +#include +#include "backend/drm/drm.h" +#include "backend/drm/fb.h" +#include "backend/drm/iface.h" +#include "backend/drm/util.h" + +static bool legacy_fb_props_match(struct wlr_drm_fb *fb1, + struct wlr_drm_fb *fb2) { + struct wlr_dmabuf_attributes dmabuf1 = {0}, dmabuf2 = {0}; + if (!wlr_buffer_get_dmabuf(fb1->wlr_buf, &dmabuf1) || + !wlr_buffer_get_dmabuf(fb2->wlr_buf, &dmabuf2)) { + return false; + } + + if (dmabuf1.width != dmabuf2.width || + dmabuf1.height != dmabuf2.height || + dmabuf1.format != dmabuf2.format || + dmabuf1.modifier != dmabuf2.modifier || + dmabuf1.n_planes != dmabuf2.n_planes) { + return false; + } + + for (int i = 0; i < dmabuf1.n_planes; i++) { + if (dmabuf1.stride[i] != dmabuf2.stride[i] || + dmabuf1.offset[i] != dmabuf2.offset[i]) { + return false; + } + } + + return true; +} + +static bool legacy_crtc_test(struct wlr_drm_connector *conn, + const struct wlr_drm_connector_state *state) { + struct wlr_drm_crtc *crtc = conn->crtc; + + if ((state->base->committed & WLR_OUTPUT_STATE_BUFFER) && !state->modeset) { + struct wlr_drm_fb *pending_fb = state->primary_fb; + + struct wlr_drm_fb *prev_fb = crtc->primary->queued_fb; + if (!prev_fb) { + prev_fb = crtc->primary->current_fb; + } + + /* Legacy is only guaranteed to be able to display a FB if it's been + * allocated the same way as the previous one. */ + if (prev_fb != NULL && !legacy_fb_props_match(prev_fb, pending_fb)) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "Cannot change scan-out buffer parameters with legacy KMS API"); + return false; + } + } + + return true; +} + +static bool legacy_crtc_commit(struct wlr_drm_connector *conn, + const struct wlr_drm_connector_state *state, + struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only) { + if (!legacy_crtc_test(conn, state)) { + return false; + } + if (test_only) { + return true; + } + + struct wlr_drm_backend *drm = conn->backend; + struct wlr_output *output = &conn->output; + struct wlr_drm_crtc *crtc = conn->crtc; + struct wlr_drm_plane *cursor = crtc->cursor; + + uint32_t fb_id = 0; + if (state->active) { + if (state->primary_fb == NULL) { + wlr_log(WLR_ERROR, "%s: failed to acquire primary FB", + conn->output.name); + return false; + } + fb_id = state->primary_fb->id; + } + + if (state->modeset) { + uint32_t *conns = NULL; + size_t conns_len = 0; + drmModeModeInfo *mode = NULL; + if (state->active) { + conns = &conn->id; + conns_len = 1; + mode = (drmModeModeInfo *)&state->mode; + } + + uint32_t dpms = state->active ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF; + if (drmModeConnectorSetProperty(drm->fd, conn->id, conn->props.dpms, + dpms) != 0) { + wlr_drm_conn_log_errno(conn, WLR_ERROR, + "Failed to set DPMS property"); + return false; + } + + if (drmModeSetCrtc(drm->fd, crtc->id, fb_id, 0, 0, + conns, conns_len, mode)) { + wlr_drm_conn_log_errno(conn, WLR_ERROR, "Failed to set CRTC"); + return false; + } + } + + if (state->base->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { + if (!drm_legacy_crtc_set_gamma(drm, crtc, + state->base->gamma_lut_size, state->base->gamma_lut)) { + return false; + } + } + + if (state->base->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) { + if (!drm_connector_supports_vrr(conn)) { + return false; + } + if (drmModeObjectSetProperty(drm->fd, crtc->id, DRM_MODE_OBJECT_CRTC, + crtc->props.vrr_enabled, + state->base->adaptive_sync_enabled) != 0) { + wlr_drm_conn_log_errno(conn, WLR_ERROR, + "drmModeObjectSetProperty(VRR_ENABLED) failed"); + return false; + } + output->adaptive_sync_status = state->base->adaptive_sync_enabled ? + WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED : + WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED; + wlr_drm_conn_log(conn, WLR_DEBUG, "VRR %s", + state->base->adaptive_sync_enabled ? "enabled" : "disabled"); + } + + if (cursor != NULL && drm_connector_is_cursor_visible(conn)) { + struct wlr_drm_fb *cursor_fb = state->cursor_fb; + if (cursor_fb == NULL) { + wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to acquire cursor FB"); + return false; + } + + drmModeFB *drm_fb = drmModeGetFB(drm->fd, cursor_fb->id); + if (drm_fb == NULL) { + wlr_drm_conn_log_errno(conn, WLR_DEBUG, "Failed to get cursor " + "BO handle: drmModeGetFB failed"); + return false; + } + uint32_t cursor_handle = drm_fb->handle; + uint32_t cursor_width = drm_fb->width; + uint32_t cursor_height = drm_fb->height; + drmModeFreeFB(drm_fb); + + int ret = drmModeSetCursor(drm->fd, crtc->id, cursor_handle, + cursor_width, cursor_height); + int set_cursor_errno = errno; + if (drmCloseBufferHandle(drm->fd, cursor_handle) != 0) { + wlr_log_errno(WLR_ERROR, "drmCloseBufferHandle failed"); + } + if (ret != 0) { + wlr_drm_conn_log(conn, WLR_DEBUG, "drmModeSetCursor failed: %s", + strerror(set_cursor_errno)); + return false; + } + + if (drmModeMoveCursor(drm->fd, + crtc->id, conn->cursor_x, conn->cursor_y) != 0) { + wlr_drm_conn_log_errno(conn, WLR_ERROR, "drmModeMoveCursor failed"); + return false; + } + } else { + if (drmModeSetCursor(drm->fd, crtc->id, 0, 0, 0)) { + wlr_drm_conn_log_errno(conn, WLR_DEBUG, "drmModeSetCursor failed"); + return false; + } + } + + if (flags & DRM_MODE_PAGE_FLIP_EVENT) { + if (drmModePageFlip(drm->fd, crtc->id, fb_id, flags, page_flip)) { + wlr_drm_conn_log_errno(conn, WLR_ERROR, "drmModePageFlip failed"); + return false; + } + } + + return true; +} + +static void fill_empty_gamma_table(size_t size, + uint16_t *r, uint16_t *g, uint16_t *b) { + assert(0xFFFF < UINT64_MAX / (size - 1)); + for (uint32_t i = 0; i < size; ++i) { + uint16_t val = (uint64_t)0xFFFF * i / (size - 1); + r[i] = g[i] = b[i] = val; + } +} + +bool drm_legacy_crtc_set_gamma(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, size_t size, uint16_t *lut) { + uint16_t *linear_lut = NULL; + if (size == 0) { + // The legacy interface doesn't offer a way to reset the gamma LUT + size = drm_crtc_get_gamma_lut_size(drm, crtc); + if (size == 0) { + return false; + } + + linear_lut = malloc(3 * size * sizeof(uint16_t)); + if (linear_lut == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return false; + } + fill_empty_gamma_table(size, linear_lut, linear_lut + size, + linear_lut + 2 * size); + + lut = linear_lut; + } + + uint16_t *r = lut, *g = lut + size, *b = lut + 2 * size; + if (drmModeCrtcSetGamma(drm->fd, crtc->id, size, r, g, b) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to set gamma LUT on CRTC %"PRIu32, + crtc->id); + free(linear_lut); + return false; + } + + free(linear_lut); + return true; +} + +static bool legacy_reset(struct wlr_drm_backend *drm) { + bool ok = true; + for (size_t i = 0; i < drm->num_crtcs; i++) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + if (drmModeSetCrtc(drm->fd, crtc->id, 0, 0, 0, NULL, 0, NULL) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to disable CRTC %"PRIu32, + crtc->id); + ok = false; + } + } + return ok; +} + +const struct wlr_drm_interface legacy_iface = { + .crtc_commit = legacy_crtc_commit, + .reset = legacy_reset, +}; diff --git a/backend/drm/libliftoff.c b/backend/drm/libliftoff.c new file mode 100644 index 0000000..1d38c8f --- /dev/null +++ b/backend/drm/libliftoff.c @@ -0,0 +1,513 @@ +#include +#include +#include +#include +#include + +#include "backend/drm/drm.h" +#include "backend/drm/fb.h" +#include "backend/drm/iface.h" + +static bool init(struct wlr_drm_backend *drm) { + // TODO: lower log level + liftoff_log_set_priority(LIFTOFF_DEBUG); + + int drm_fd = fcntl(drm->fd, F_DUPFD_CLOEXEC, 0); + if (drm_fd < 0) { + wlr_log_errno(WLR_ERROR, "fcntl(F_DUPFD_CLOEXEC) failed"); + return false; + } + + drm->liftoff = liftoff_device_create(drm_fd); + if (!drm->liftoff) { + wlr_log(WLR_ERROR, "Failed to create liftoff device"); + close(drm_fd); + return false; + } + + for (size_t i = 0; i < drm->num_planes; i++) { + struct wlr_drm_plane *plane = &drm->planes[i]; + if (plane->initial_crtc_id != 0) { + continue; + } + plane->liftoff = liftoff_plane_create(drm->liftoff, plane->id); + if (plane->liftoff == NULL) { + wlr_log(WLR_ERROR, "Failed to create liftoff plane"); + return false; + } + } + + for (size_t i = 0; i < drm->num_crtcs; i++) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + + crtc->liftoff = liftoff_output_create(drm->liftoff, crtc->id); + if (!crtc->liftoff) { + wlr_log(WLR_ERROR, "Failed to create liftoff output"); + return false; + } + + crtc->liftoff_composition_layer = liftoff_layer_create(crtc->liftoff); + if (!crtc->liftoff_composition_layer) { + wlr_log(WLR_ERROR, "Failed to create liftoff composition layer"); + return false; + } + liftoff_output_set_composition_layer(crtc->liftoff, + crtc->liftoff_composition_layer); + + if (crtc->primary) { + crtc->primary->liftoff_layer = liftoff_layer_create(crtc->liftoff); + if (!crtc->primary->liftoff_layer) { + wlr_log(WLR_ERROR, "Failed to create liftoff layer for primary plane"); + return false; + } + } + + if (crtc->cursor) { + crtc->cursor->liftoff_layer = liftoff_layer_create(crtc->liftoff); + if (!crtc->cursor->liftoff_layer) { + wlr_log(WLR_ERROR, "Failed to create liftoff layer for cursor plane"); + return false; + } + } + } + + return true; +} + +static bool register_planes_for_crtc(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc) { + // When performing the first modeset on a CRTC, we need to be a bit careful + // when it comes to planes: we don't want to allow libliftoff to make use + // of planes currently already in-use on another CRTC. We need to wait for + // a modeset to happen on the other CRTC before being able to use these. + for (size_t i = 0; i < drm->num_planes; i++) { + struct wlr_drm_plane *plane = &drm->planes[i]; + if (plane->liftoff != NULL || plane->initial_crtc_id != crtc->id) { + continue; + } + plane->liftoff = liftoff_plane_create(drm->liftoff, plane->id); + if (plane->liftoff == NULL) { + wlr_log(WLR_ERROR, "Failed to create liftoff plane"); + return false; + } + } + return true; +} + +static void finish(struct wlr_drm_backend *drm) { + for (size_t i = 0; i < drm->num_crtcs; i++) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + + if (crtc->primary) { + liftoff_layer_destroy(crtc->primary->liftoff_layer); + } + if (crtc->cursor) { + liftoff_layer_destroy(crtc->cursor->liftoff_layer); + } + + liftoff_layer_destroy(crtc->liftoff_composition_layer); + liftoff_output_destroy(crtc->liftoff); + } + + for (size_t i = 0; i < drm->num_planes; i++) { + struct wlr_drm_plane *plane = &drm->planes[i]; + liftoff_plane_destroy(plane->liftoff); + } + + liftoff_device_destroy(drm->liftoff); +} + +static bool add_prop(drmModeAtomicReq *req, uint32_t obj, + uint32_t prop, uint64_t val) { + if (drmModeAtomicAddProperty(req, obj, prop, val) < 0) { + wlr_log_errno(WLR_ERROR, "drmModeAtomicAddProperty failed"); + return false; + } + return true; +} + +static void commit_blob(struct wlr_drm_backend *drm, + uint32_t *current, uint32_t next) { + if (*current == next) { + return; + } + if (*current != 0) { + drmModeDestroyPropertyBlob(drm->fd, *current); + } + *current = next; +} + +static void rollback_blob(struct wlr_drm_backend *drm, + uint32_t *current, uint32_t next) { + if (*current == next) { + return; + } + if (next != 0) { + drmModeDestroyPropertyBlob(drm->fd, next); + } +} + +static bool set_plane_props(struct wlr_drm_plane *plane, + struct liftoff_layer *layer, struct wlr_drm_fb *fb, int32_t x, int32_t y, uint64_t zpos) { + if (fb == NULL) { + wlr_log(WLR_ERROR, "Failed to acquire FB for plane %"PRIu32, plane->id); + return false; + } + + uint32_t width = fb->wlr_buf->width; + uint32_t height = fb->wlr_buf->height; + + // The SRC_* properties are in 16.16 fixed point + return liftoff_layer_set_property(layer, "zpos", zpos) == 0 && + liftoff_layer_set_property(layer, "SRC_X", 0) == 0 && + liftoff_layer_set_property(layer, "SRC_Y", 0) == 0 && + liftoff_layer_set_property(layer, "SRC_W", (uint64_t)width << 16) == 0 && + liftoff_layer_set_property(layer, "SRC_H", (uint64_t)height << 16) == 0 && + liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x) == 0 && + liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y) == 0 && + liftoff_layer_set_property(layer, "CRTC_W", width) == 0 && + liftoff_layer_set_property(layer, "CRTC_H", height) == 0 && + liftoff_layer_set_property(layer, "FB_ID", fb->id) == 0; +} + +static bool disable_plane(struct wlr_drm_plane *plane) { + return liftoff_layer_set_property(plane->liftoff_layer, "FB_ID", 0) == 0; +} + +static uint64_t to_fp16(double v) { + return (uint64_t)round(v * (1 << 16)); +} + +static bool set_layer_props(struct wlr_drm_backend *drm, + const struct wlr_output_layer_state *state, uint64_t zpos, + struct wl_array *fb_damage_clips_arr) { + struct wlr_drm_layer *layer = get_drm_layer(drm, state->layer); + + uint32_t width = 0, height = 0; + if (state->buffer != NULL) { + width = state->buffer->width; + height = state->buffer->height; + } + + struct wlr_drm_fb *fb = layer->pending_fb; + int ret = 0; + if (state->buffer == NULL) { + ret = liftoff_layer_set_property(layer->liftoff, "FB_ID", 0); + } else if (fb == NULL) { + liftoff_layer_set_fb_composited(layer->liftoff); + } else { + ret = liftoff_layer_set_property(layer->liftoff, "FB_ID", fb->id); + } + if (ret != 0) { + return false; + } + + uint64_t crtc_x = (uint64_t)state->dst_box.x; + uint64_t crtc_y = (uint64_t)state->dst_box.y; + uint64_t crtc_w = (uint64_t)state->dst_box.width; + uint64_t crtc_h = (uint64_t)state->dst_box.height; + + struct wlr_fbox src_box = state->src_box; + if (wlr_fbox_empty(&src_box)) { + src_box = (struct wlr_fbox){ + .width = width, + .height = height, + }; + } + + uint64_t src_x = to_fp16(src_box.x); + uint64_t src_y = to_fp16(src_box.y); + uint64_t src_w = to_fp16(src_box.width); + uint64_t src_h = to_fp16(src_box.height); + + uint32_t fb_damage_clips = 0; + if (state->damage != NULL) { + uint32_t *ptr = wl_array_add(fb_damage_clips_arr, sizeof(fb_damage_clips)); + if (ptr == NULL) { + return false; + } + create_fb_damage_clips_blob(drm, width, height, + state->damage, &fb_damage_clips); + *ptr = fb_damage_clips; + } + + return + liftoff_layer_set_property(layer->liftoff, "zpos", zpos) == 0 && + liftoff_layer_set_property(layer->liftoff, "CRTC_X", crtc_x) == 0 && + liftoff_layer_set_property(layer->liftoff, "CRTC_Y", crtc_y) == 0 && + liftoff_layer_set_property(layer->liftoff, "CRTC_W", crtc_w) == 0 && + liftoff_layer_set_property(layer->liftoff, "CRTC_H", crtc_h) == 0 && + liftoff_layer_set_property(layer->liftoff, "SRC_X", src_x) == 0 && + liftoff_layer_set_property(layer->liftoff, "SRC_Y", src_y) == 0 && + liftoff_layer_set_property(layer->liftoff, "SRC_W", src_w) == 0 && + liftoff_layer_set_property(layer->liftoff, "SRC_H", src_h) == 0 && + liftoff_layer_set_property(layer->liftoff, "FB_DAMAGE_CLIPS", fb_damage_clips) == 0; +} + +static bool devid_from_fd(int fd, dev_t *devid) { + struct stat stat; + if (fstat(fd, &stat) != 0) { + wlr_log_errno(WLR_ERROR, "fstat failed"); + return false; + } + *devid = stat.st_rdev; + return true; +} + +static void update_layer_feedback(struct wlr_drm_backend *drm, + struct wlr_drm_layer *layer) { + bool changed = false; + for (size_t i = 0; i < drm->num_planes; i++) { + struct wlr_drm_plane *plane = &drm->planes[i]; + bool is_candidate = liftoff_layer_is_candidate_plane(layer->liftoff, + plane->liftoff); + if (layer->candidate_planes[i] != is_candidate) { + layer->candidate_planes[i] = is_candidate; + changed = true; + } + } + if (!changed) { + return; + } + + dev_t target_device; + if (!devid_from_fd(drm->fd, &target_device)) { + return; + } + + struct wlr_drm_format_set formats = {0}; + for (size_t i = 0; i < drm->num_planes; i++) { + struct wlr_drm_plane *plane = &drm->planes[i]; + if (!layer->candidate_planes[i]) { + continue; + } + + for (size_t j = 0; j < plane->formats.len; j++) { + const struct wlr_drm_format *format = &plane->formats.formats[j]; + for (size_t k = 0; k < format->len; k++) { + wlr_drm_format_set_add(&formats, format->format, + format->modifiers[k]); + } + } + } + + struct wlr_output_layer_feedback_event event = { + .target_device = target_device, + .formats = &formats, + }; + wl_signal_emit_mutable(&layer->wlr->events.feedback, &event); + + wlr_drm_format_set_finish(&formats); +} + +static bool crtc_commit(struct wlr_drm_connector *conn, + const struct wlr_drm_connector_state *state, + struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only) { + struct wlr_drm_backend *drm = conn->backend; + struct wlr_output *output = &conn->output; + struct wlr_drm_crtc *crtc = conn->crtc; + + bool modeset = state->modeset; + bool active = state->active; + + if (modeset && !register_planes_for_crtc(drm, crtc)) { + return false; + } + + uint32_t mode_id = crtc->mode_id; + if (modeset) { + if (!create_mode_blob(conn, state, &mode_id)) { + return false; + } + } + + uint32_t gamma_lut = crtc->gamma_lut; + if (state->base->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { + // Fallback to legacy gamma interface when gamma properties are not + // available (can happen on older Intel GPUs that support gamma but not + // degamma). + if (crtc->props.gamma_lut == 0) { + if (!drm_legacy_crtc_set_gamma(drm, crtc, + state->base->gamma_lut_size, + state->base->gamma_lut)) { + return false; + } + } else { + if (!create_gamma_lut_blob(drm, state->base->gamma_lut_size, + state->base->gamma_lut, &gamma_lut)) { + return false; + } + } + } + + struct wl_array fb_damage_clips_arr = {0}; + + uint32_t primary_fb_damage_clips = 0; + if ((state->base->committed & WLR_OUTPUT_STATE_DAMAGE) && + crtc->primary->props.fb_damage_clips != 0) { + uint32_t *ptr = wl_array_add(&fb_damage_clips_arr, sizeof(primary_fb_damage_clips)); + if (ptr == NULL) { + return false; + } + create_fb_damage_clips_blob(drm, state->primary_fb->wlr_buf->width, + state->primary_fb->wlr_buf->height, &state->base->damage, + &primary_fb_damage_clips); + *ptr = primary_fb_damage_clips; + } + + bool prev_vrr_enabled = + output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED; + bool vrr_enabled = prev_vrr_enabled; + if ((state->base->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) && + drm_connector_supports_vrr(conn)) { + vrr_enabled = state->base->adaptive_sync_enabled; + } + + if (test_only) { + flags |= DRM_MODE_ATOMIC_TEST_ONLY; + } + if (modeset) { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + if (!test_only && state->nonblock) { + flags |= DRM_MODE_ATOMIC_NONBLOCK; + } + + drmModeAtomicReq *req = drmModeAtomicAlloc(); + if (req == NULL) { + wlr_log(WLR_ERROR, "drmModeAtomicAlloc failed"); + return false; + } + + bool ok = add_prop(req, conn->id, conn->props.crtc_id, + active ? crtc->id : 0); + if (modeset && active && conn->props.link_status != 0) { + ok = ok && add_prop(req, conn->id, conn->props.link_status, + DRM_MODE_LINK_STATUS_GOOD); + } + if (active && conn->props.content_type != 0) { + ok = ok && add_prop(req, conn->id, conn->props.content_type, + DRM_MODE_CONTENT_TYPE_GRAPHICS); + } + // TODO: set "max bpc" + ok = ok && + add_prop(req, crtc->id, crtc->props.mode_id, mode_id) && + add_prop(req, crtc->id, crtc->props.active, active); + if (active) { + if (crtc->props.gamma_lut != 0) { + ok = ok && add_prop(req, crtc->id, crtc->props.gamma_lut, gamma_lut); + } + if (crtc->props.vrr_enabled != 0) { + ok = ok && add_prop(req, crtc->id, crtc->props.vrr_enabled, vrr_enabled); + } + ok = ok && + set_plane_props(crtc->primary, crtc->primary->liftoff_layer, state->primary_fb, 0, 0, 0) && + set_plane_props(crtc->primary, crtc->liftoff_composition_layer, state->primary_fb, 0, 0, 0); + liftoff_layer_set_property(crtc->primary->liftoff_layer, + "FB_DAMAGE_CLIPS", primary_fb_damage_clips); + liftoff_layer_set_property(crtc->liftoff_composition_layer, + "FB_DAMAGE_CLIPS", primary_fb_damage_clips); + + if (state->base->committed & WLR_OUTPUT_STATE_LAYERS) { + for (size_t i = 0; i < state->base->layers_len; i++) { + const struct wlr_output_layer_state *layer_state = &state->base->layers[i]; + ok = ok && set_layer_props(drm, layer_state, i + 1, + &fb_damage_clips_arr); + } + } + + if (crtc->cursor) { + if (drm_connector_is_cursor_visible(conn)) { + ok = ok && set_plane_props(crtc->cursor, crtc->cursor->liftoff_layer, + state->cursor_fb, conn->cursor_x, conn->cursor_y, + wl_list_length(&crtc->layers) + 1); + } else { + ok = ok && disable_plane(crtc->cursor); + } + } + } else { + ok = ok && disable_plane(crtc->primary); + if (crtc->cursor) { + ok = ok && disable_plane(crtc->cursor); + } + } + + if (!ok) { + goto out; + } + + int ret = liftoff_output_apply(crtc->liftoff, req, flags); + if (ret != 0) { + wlr_drm_conn_log(conn, test_only ? WLR_DEBUG : WLR_ERROR, + "liftoff_output_apply failed: %s", strerror(-ret)); + ok = false; + goto out; + } + + if (crtc->cursor && + liftoff_layer_needs_composition(crtc->cursor->liftoff_layer)) { + wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to scan-out cursor plane"); + ok = false; + goto out; + } + + ret = drmModeAtomicCommit(drm->fd, req, flags, page_flip); + if (ret != 0) { + wlr_drm_conn_log_errno(conn, test_only ? WLR_DEBUG : WLR_ERROR, + "Atomic commit failed"); + ok = false; + goto out; + } + + if (state->base->committed & WLR_OUTPUT_STATE_LAYERS) { + for (size_t i = 0; i < state->base->layers_len; i++) { + struct wlr_output_layer_state *layer_state = &state->base->layers[i]; + struct wlr_drm_layer *layer = get_drm_layer(drm, layer_state->layer); + layer_state->accepted = + !liftoff_layer_needs_composition(layer->liftoff); + if (!test_only && !layer_state->accepted) { + update_layer_feedback(drm, layer); + } + } + } + +out: + drmModeAtomicFree(req); + + if (ok && !test_only) { + if (!crtc->own_mode_id) { + crtc->mode_id = 0; // don't try to delete previous master's blobs + } + crtc->own_mode_id = true; + commit_blob(drm, &crtc->mode_id, mode_id); + commit_blob(drm, &crtc->gamma_lut, gamma_lut); + + if (vrr_enabled != prev_vrr_enabled) { + output->adaptive_sync_status = vrr_enabled ? + WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED : + WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED; + wlr_drm_conn_log(conn, WLR_DEBUG, "VRR %s", + vrr_enabled ? "enabled" : "disabled"); + } + } else { + rollback_blob(drm, &crtc->mode_id, mode_id); + rollback_blob(drm, &crtc->gamma_lut, gamma_lut); + } + + uint32_t *fb_damage_clips_ptr; + wl_array_for_each(fb_damage_clips_ptr, &fb_damage_clips_arr) { + if (drmModeDestroyPropertyBlob(drm->fd, *fb_damage_clips_ptr) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to destroy FB_DAMAGE_CLIPS property blob"); + } + } + wl_array_release(&fb_damage_clips_arr); + + return ok; +} + +const struct wlr_drm_interface liftoff_iface = { + .init = init, + .finish = finish, + .crtc_commit = crtc_commit, + .reset = drm_atomic_reset, +}; diff --git a/backend/drm/meson.build b/backend/drm/meson.build new file mode 100644 index 0000000..5d2f2b1 --- /dev/null +++ b/backend/drm/meson.build @@ -0,0 +1,56 @@ +hwdata = dependency( + 'hwdata', + required: 'drm' in backends, + native: true, + not_found_message: 'Required for the DRM backend.', +) + +libdisplay_info = dependency( + 'libdisplay-info', + required: 'drm' in backends, + fallback: 'libdisplay-info', + not_found_message: 'Required for the DRM backend.', +) + +libliftoff = dependency( + 'libliftoff', + version: '>=0.4.0', + fallback: 'libliftoff', + required: false, +) + +if not (hwdata.found() and libdisplay_info.found() and features['session']) + subdir_done() +endif + +hwdata_dir = hwdata.get_variable(pkgconfig: 'pkgdatadir') +pnpids_c = custom_target( + 'pnpids.c', + output: 'pnpids.c', + input: files(hwdata_dir / 'pnp.ids'), + feed: true, + capture: true, + command: files('gen_pnpids.sh'), +) +wlr_files += pnpids_c + +wlr_files += files( + 'atomic.c', + 'backend.c', + 'drm.c', + 'fb.c', + 'legacy.c', + 'monitor.c', + 'properties.c', + 'renderer.c', + 'util.c', +) + +if libliftoff.found() + wlr_files += files('libliftoff.c') +endif + +features += { 'drm-backend': true } +internal_features += { 'libliftoff': libliftoff.found() } +wlr_deps += libdisplay_info +wlr_deps += libliftoff diff --git a/backend/drm/monitor.c b/backend/drm/monitor.c new file mode 100644 index 0000000..efd8537 --- /dev/null +++ b/backend/drm/monitor.c @@ -0,0 +1,91 @@ +#include +#include +#include "backend/drm/monitor.h" +#include "backend/multi.h" +#include "backend/session/session.h" + +static void drm_backend_monitor_destroy(struct wlr_drm_backend_monitor* monitor) { + wl_list_remove(&monitor->session_add_drm_card.link); + wl_list_remove(&monitor->session_destroy.link); + wl_list_remove(&monitor->primary_drm_destroy.link); + wl_list_remove(&monitor->multi_destroy.link); + free(monitor); +} + +static void handle_add_drm_card(struct wl_listener *listener, void *data) { + struct wlr_session_add_event *event = data; + struct wlr_drm_backend_monitor *backend_monitor = + wl_container_of(listener, backend_monitor, session_add_drm_card); + + struct wlr_device *dev = + session_open_if_kms(backend_monitor->session, event->path); + if (!dev) { + wlr_log(WLR_ERROR, "Unable to open %s as DRM device", event->path); + return; + } + + wlr_log(WLR_DEBUG, "Creating DRM backend for %s after hotplug", event->path); + struct wlr_backend *child_drm = wlr_drm_backend_create(backend_monitor->session, + dev, backend_monitor->primary_drm); + if (!child_drm) { + wlr_log(WLR_ERROR, "Failed to create DRM backend after hotplug"); + return; + } + + if (!wlr_multi_backend_add(backend_monitor->multi, child_drm)) { + wlr_log(WLR_ERROR, "Failed to add new drm backend to multi backend"); + wlr_backend_destroy(child_drm); + return; + } + + if (!wlr_backend_start(child_drm)) { + wlr_log(WLR_ERROR, "Failed to start new child DRM backend"); + wlr_backend_destroy(child_drm); + } +} + +static void handle_session_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm_backend_monitor *backend_monitor = + wl_container_of(listener, backend_monitor, session_destroy); + drm_backend_monitor_destroy(backend_monitor); +} + +static void handle_primary_drm_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm_backend_monitor *backend_monitor = + wl_container_of(listener, backend_monitor, primary_drm_destroy); + drm_backend_monitor_destroy(backend_monitor); +} + +static void handle_multi_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm_backend_monitor *backend_monitor = + wl_container_of(listener, backend_monitor, multi_destroy); + drm_backend_monitor_destroy(backend_monitor); +} + +struct wlr_drm_backend_monitor *drm_backend_monitor_create( + struct wlr_backend *multi, struct wlr_backend *primary_drm, + struct wlr_session *session) { + struct wlr_drm_backend_monitor *monitor = calloc(1, sizeof(*monitor)); + if (!monitor) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + monitor->multi = multi; + monitor->primary_drm = primary_drm; + monitor->session = session; + + monitor->session_add_drm_card.notify = handle_add_drm_card; + wl_signal_add(&session->events.add_drm_card, &monitor->session_add_drm_card); + + monitor->session_destroy.notify = handle_session_destroy; + wl_signal_add(&session->events.destroy, &monitor->session_destroy); + + monitor->primary_drm_destroy.notify = handle_primary_drm_destroy; + wl_signal_add(&primary_drm->events.destroy, &monitor->primary_drm_destroy); + + monitor->multi_destroy.notify = handle_multi_destroy; + wl_signal_add(&multi->events.destroy, &monitor->multi_destroy); + + return monitor; +} diff --git a/backend/drm/properties.c b/backend/drm/properties.c new file mode 100644 index 0000000..ecd0d91 --- /dev/null +++ b/backend/drm/properties.c @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/drm/properties.h" + +/* + * Creates a mapping between property names and an array index where to store + * the ids. The prop_info arrays must be sorted by name, as bsearch is used to + * search them. + */ +struct prop_info { + const char *name; + size_t index; +}; + +static const struct prop_info connector_info[] = { +#define INDEX(name) (offsetof(union wlr_drm_connector_props, name) / sizeof(uint32_t)) + { "CRTC_ID", INDEX(crtc_id) }, + { "DPMS", INDEX(dpms) }, + { "EDID", INDEX(edid) }, + { "PATH", INDEX(path) }, + { "content type", INDEX(content_type) }, + { "link-status", INDEX(link_status) }, + { "max bpc", INDEX(max_bpc) }, + { "non-desktop", INDEX(non_desktop) }, + { "panel orientation", INDEX(panel_orientation) }, + { "subconnector", INDEX(subconnector) }, + { "vrr_capable", INDEX(vrr_capable) }, +#undef INDEX +}; + +static const struct prop_info crtc_info[] = { +#define INDEX(name) (offsetof(union wlr_drm_crtc_props, name) / sizeof(uint32_t)) + { "ACTIVE", INDEX(active) }, + { "GAMMA_LUT", INDEX(gamma_lut) }, + { "GAMMA_LUT_SIZE", INDEX(gamma_lut_size) }, + { "MODE_ID", INDEX(mode_id) }, + { "VRR_ENABLED", INDEX(vrr_enabled) }, +#undef INDEX +}; + +static const struct prop_info plane_info[] = { +#define INDEX(name) (offsetof(union wlr_drm_plane_props, name) / sizeof(uint32_t)) + { "CRTC_H", INDEX(crtc_h) }, + { "CRTC_ID", INDEX(crtc_id) }, + { "CRTC_W", INDEX(crtc_w) }, + { "CRTC_X", INDEX(crtc_x) }, + { "CRTC_Y", INDEX(crtc_y) }, + { "FB_DAMAGE_CLIPS", INDEX(fb_damage_clips) }, + { "FB_ID", INDEX(fb_id) }, + { "IN_FORMATS", INDEX(in_formats) }, + { "SRC_H", INDEX(src_h) }, + { "SRC_W", INDEX(src_w) }, + { "SRC_X", INDEX(src_x) }, + { "SRC_Y", INDEX(src_y) }, + { "rotation", INDEX(rotation) }, + { "type", INDEX(type) }, +#undef INDEX +}; + +static int cmp_prop_info(const void *arg1, const void *arg2) { + const char *key = arg1; + const struct prop_info *elem = arg2; + + return strcmp(key, elem->name); +} + +static bool scan_properties(int fd, uint32_t id, uint32_t type, uint32_t *result, + const struct prop_info *info, size_t info_len) { + drmModeObjectProperties *props = drmModeObjectGetProperties(fd, id, type); + if (!props) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM object properties"); + return false; + } + + for (uint32_t i = 0; i < props->count_props; ++i) { + drmModePropertyRes *prop = drmModeGetProperty(fd, props->props[i]); + if (!prop) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM object property"); + continue; + } + + const struct prop_info *p = + bsearch(prop->name, info, info_len, sizeof(info[0]), cmp_prop_info); + if (p) { + result[p->index] = prop->prop_id; + } + + drmModeFreeProperty(prop); + } + + drmModeFreeObjectProperties(props); + return true; +} + +bool get_drm_connector_props(int fd, uint32_t id, + union wlr_drm_connector_props *out) { + return scan_properties(fd, id, DRM_MODE_OBJECT_CONNECTOR, out->props, + connector_info, sizeof(connector_info) / sizeof(connector_info[0])); +} + +bool get_drm_crtc_props(int fd, uint32_t id, union wlr_drm_crtc_props *out) { + return scan_properties(fd, id, DRM_MODE_OBJECT_CRTC, out->props, + crtc_info, sizeof(crtc_info) / sizeof(crtc_info[0])); +} + +bool get_drm_plane_props(int fd, uint32_t id, union wlr_drm_plane_props *out) { + return scan_properties(fd, id, DRM_MODE_OBJECT_PLANE, out->props, + plane_info, sizeof(plane_info) / sizeof(plane_info[0])); +} + +bool get_drm_prop(int fd, uint32_t obj, uint32_t prop, uint64_t *ret) { + drmModeObjectProperties *props = + drmModeObjectGetProperties(fd, obj, DRM_MODE_OBJECT_ANY); + if (!props) { + return false; + } + + bool found = false; + + for (uint32_t i = 0; i < props->count_props; ++i) { + if (props->props[i] == prop) { + *ret = props->prop_values[i]; + found = true; + break; + } + } + + drmModeFreeObjectProperties(props); + return found; +} + +void *get_drm_prop_blob(int fd, uint32_t obj, uint32_t prop, size_t *ret_len) { + uint64_t blob_id; + if (!get_drm_prop(fd, obj, prop, &blob_id)) { + return NULL; + } + + drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(fd, blob_id); + if (!blob) { + return NULL; + } + + void *ptr = malloc(blob->length); + if (!ptr) { + drmModeFreePropertyBlob(blob); + return NULL; + } + + memcpy(ptr, blob->data, blob->length); + *ret_len = blob->length; + + drmModeFreePropertyBlob(blob); + return ptr; +} + +char *get_drm_prop_enum(int fd, uint32_t obj, uint32_t prop_id) { + uint64_t value; + if (!get_drm_prop(fd, obj, prop_id, &value)) { + return NULL; + } + + drmModePropertyRes *prop = drmModeGetProperty(fd, prop_id); + if (!prop) { + return NULL; + } + + char *str = NULL; + for (int i = 0; i < prop->count_enums; i++) { + if (prop->enums[i].value == value) { + str = strdup(prop->enums[i].name); + break; + } + } + + drmModeFreeProperty(prop); + + return str; +} + +bool introspect_drm_prop_range(int fd, uint32_t prop_id, + uint64_t *min, uint64_t *max) { + drmModePropertyRes *prop = drmModeGetProperty(fd, prop_id); + if (!prop) { + return false; + } + + if (drmModeGetPropertyType(prop) != DRM_MODE_PROP_RANGE) { + drmModeFreeProperty(prop); + return false; + } + + assert(prop->count_values == 2); + + if (min != NULL) { + *min = prop->values[0]; + } + if (max != NULL) { + *max = prop->values[1]; + } + + drmModeFreeProperty(prop); + return true; +} diff --git a/backend/drm/renderer.c b/backend/drm/renderer.c new file mode 100644 index 0000000..e4aadc1 --- /dev/null +++ b/backend/drm/renderer.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include "backend/drm/drm.h" +#include "backend/drm/fb.h" +#include "backend/drm/renderer.h" +#include "backend/backend.h" +#include "render/drm_format_set.h" +#include "render/allocator/allocator.h" +#include "render/pixel_format.h" +#include "render/wlr_renderer.h" + +bool init_drm_renderer(struct wlr_drm_backend *drm, + struct wlr_drm_renderer *renderer) { + renderer->wlr_rend = renderer_autocreate_with_drm_fd(drm->fd); + if (!renderer->wlr_rend) { + wlr_log(WLR_ERROR, "Failed to create renderer"); + return false; + } + + uint32_t backend_caps = backend_get_buffer_caps(&drm->backend); + renderer->allocator = allocator_autocreate_with_drm_fd(backend_caps, + renderer->wlr_rend, drm->fd); + if (renderer->allocator == NULL) { + wlr_log(WLR_ERROR, "Failed to create allocator"); + wlr_renderer_destroy(renderer->wlr_rend); + return false; + } + + return true; +} + +void finish_drm_renderer(struct wlr_drm_renderer *renderer) { + if (!renderer) { + return; + } + + wlr_allocator_destroy(renderer->allocator); + wlr_renderer_destroy(renderer->wlr_rend); +} + +void finish_drm_surface(struct wlr_drm_surface *surf) { + if (!surf || !surf->renderer) { + return; + } + + wlr_swapchain_destroy(surf->swapchain); + + *surf = (struct wlr_drm_surface){0}; +} + +bool init_drm_surface(struct wlr_drm_surface *surf, + struct wlr_drm_renderer *renderer, int width, int height, + const struct wlr_drm_format *drm_format) { + if (surf->swapchain != NULL && surf->swapchain->width == width && + surf->swapchain->height == height) { + return true; + } + + finish_drm_surface(surf); + + surf->swapchain = wlr_swapchain_create(renderer->allocator, width, height, + drm_format); + if (surf->swapchain == NULL) { + wlr_log(WLR_ERROR, "Failed to create swapchain"); + return false; + } + + surf->renderer = renderer; + + return true; +} + +struct wlr_buffer *drm_surface_blit(struct wlr_drm_surface *surf, + struct wlr_buffer *buffer) { + struct wlr_renderer *renderer = surf->renderer->wlr_rend; + + if (surf->swapchain->width != buffer->width || + surf->swapchain->height != buffer->height) { + wlr_log(WLR_ERROR, "Surface size doesn't match buffer size"); + return NULL; + } + + struct wlr_texture *tex = wlr_texture_from_buffer(renderer, buffer); + if (tex == NULL) { + wlr_log(WLR_ERROR, "Failed to import source buffer into multi-GPU renderer"); + return NULL; + } + + struct wlr_buffer *dst = wlr_swapchain_acquire(surf->swapchain, NULL); + if (!dst) { + wlr_log(WLR_ERROR, "Failed to acquire multi-GPU swapchain buffer"); + goto error_tex; + } + + struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, dst, NULL); + if (pass == NULL) { + wlr_log(WLR_ERROR, "Failed to begin render pass with multi-GPU destination buffer"); + goto error_dst; + } + + wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){ + .texture = tex, + .blend_mode = WLR_RENDER_BLEND_MODE_NONE, + }); + if (!wlr_render_pass_submit(pass)) { + wlr_log(WLR_ERROR, "Failed to submit multi-GPU render pass"); + goto error_dst; + } + + wlr_texture_destroy(tex); + + return dst; + +error_dst: + wlr_buffer_unlock(dst); +error_tex: + wlr_texture_destroy(tex); + return NULL; +} + +bool drm_plane_pick_render_format(struct wlr_drm_plane *plane, + struct wlr_drm_format *fmt, struct wlr_drm_renderer *renderer) { + const struct wlr_drm_format_set *render_formats = + wlr_renderer_get_render_formats(renderer->wlr_rend); + if (render_formats == NULL) { + wlr_log(WLR_ERROR, "Failed to get render formats"); + return false; + } + + const struct wlr_drm_format_set *plane_formats = &plane->formats; + + uint32_t format = DRM_FORMAT_ARGB8888; + if (!wlr_drm_format_set_get(&plane->formats, format)) { + const struct wlr_pixel_format_info *format_info = + drm_get_pixel_format_info(format); + assert(format_info != NULL && + format_info->opaque_substitute != DRM_FORMAT_INVALID); + format = format_info->opaque_substitute; + } + + const struct wlr_drm_format *render_format = + wlr_drm_format_set_get(render_formats, format); + if (render_format == NULL) { + wlr_log(WLR_DEBUG, "Renderer doesn't support format 0x%"PRIX32, format); + return false; + } + + const struct wlr_drm_format *plane_format = + wlr_drm_format_set_get(plane_formats, format); + if (plane_format == NULL) { + wlr_log(WLR_DEBUG, "Plane %"PRIu32" doesn't support format 0x%"PRIX32, + plane->id, format); + return false; + } + + if (!wlr_drm_format_intersect(fmt, plane_format, render_format)) { + wlr_log(WLR_DEBUG, "Failed to intersect plane and render " + "modifiers for format 0x%"PRIX32, format); + return false; + } + + if (fmt->len == 0) { + wlr_drm_format_finish(fmt); + wlr_log(WLR_DEBUG, "Failed to find matching plane and renderer modifiers"); + return false; + } + + return true; +} diff --git a/backend/drm/util.c b/backend/drm/util.c new file mode 100644 index 0000000..a14e5e1 --- /dev/null +++ b/backend/drm/util.c @@ -0,0 +1,279 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/drm/drm.h" +#include "backend/drm/util.h" + +int32_t calculate_refresh_rate(const drmModeModeInfo *mode) { + int32_t refresh = (mode->clock * 1000000LL / mode->htotal + + mode->vtotal / 2) / mode->vtotal; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + refresh *= 2; + } + + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) { + refresh /= 2; + } + + if (mode->vscan > 1) { + refresh /= mode->vscan; + } + + return refresh; +} + +enum wlr_output_mode_aspect_ratio get_picture_aspect_ratio(const drmModeModeInfo *mode) { + switch (mode->flags & DRM_MODE_FLAG_PIC_AR_MASK) { + case DRM_MODE_FLAG_PIC_AR_NONE: + return WLR_OUTPUT_MODE_ASPECT_RATIO_NONE; + case DRM_MODE_FLAG_PIC_AR_4_3: + return WLR_OUTPUT_MODE_ASPECT_RATIO_4_3; + case DRM_MODE_FLAG_PIC_AR_16_9: + return WLR_OUTPUT_MODE_ASPECT_RATIO_16_9; + case DRM_MODE_FLAG_PIC_AR_64_27: + return WLR_OUTPUT_MODE_ASPECT_RATIO_64_27; + case DRM_MODE_FLAG_PIC_AR_256_135: + return WLR_OUTPUT_MODE_ASPECT_RATIO_256_135; + default: + wlr_log(WLR_ERROR, "Unknown mode picture aspect ratio: %u", + mode->flags & DRM_MODE_FLAG_PIC_AR_MASK); + return WLR_OUTPUT_MODE_ASPECT_RATIO_NONE; + } +} + +void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data) { + struct wlr_output *output = &conn->output; + + free(output->make); + free(output->model); + free(output->serial); + output->make = NULL; + output->model = NULL; + output->serial = NULL; + + struct di_info *info = di_info_parse_edid(data, len); + if (info == NULL) { + wlr_log(WLR_ERROR, "Failed to parse EDID"); + return; + } + + const struct di_edid *edid = di_info_get_edid(info); + const struct di_edid_vendor_product *vendor_product = di_edid_get_vendor_product(edid); + char pnp_id[] = { + vendor_product->manufacturer[0], + vendor_product->manufacturer[1], + vendor_product->manufacturer[2], + '\0', + }; + const char *manu = get_pnp_manufacturer(vendor_product->manufacturer); + if (!manu) { + manu = pnp_id; + } + output->make = strdup(manu); + + output->model = di_info_get_model(info); + output->serial = di_info_get_serial(info); + + di_info_destroy(info); +} + +const char *drm_connector_status_str(drmModeConnection status) { + switch (status) { + case DRM_MODE_CONNECTED: + return "connected"; + case DRM_MODE_DISCONNECTED: + return "disconnected"; + case DRM_MODE_UNKNOWNCONNECTION: + return "unknown"; + } + return ""; +} + +static bool is_taken(size_t n, const uint32_t arr[static n], uint32_t key) { + for (size_t i = 0; i < n; ++i) { + if (arr[i] == key) { + return true; + } + } + return false; +} + +/* + * Store all of the non-recursive state in a struct, so we aren't literally + * passing 12 arguments to a function. + */ +struct match_state { + const size_t num_objs; + const uint32_t *restrict objs; + const size_t num_res; + size_t score; + size_t replaced; + uint32_t *restrict res; + uint32_t *restrict best; + const uint32_t *restrict orig; + bool exit_early; +}; + +/* + * skips: The number of SKIP elements encountered so far. + * score: The number of resources we've matched so far. + * replaced: The number of changes from the original solution. + * i: The index of the current element. + * + * This tries to match a solution as close to st->orig as it can. + * + * Returns whether we've set a new best element with this solution. + */ +static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_t replaced, size_t i) { + // Finished + if (i >= st->num_res) { + if (score > st->score || + (score == st->score && replaced < st->replaced)) { + st->score = score; + st->replaced = replaced; + memcpy(st->best, st->res, sizeof(st->best[0]) * st->num_res); + + st->exit_early = (st->score == st->num_res - skips + || st->score == st->num_objs) + && st->replaced == 0; + + return true; + } else { + return false; + } + } + + if (st->orig[i] == SKIP) { + st->res[i] = SKIP; + return match_obj_(st, skips + 1, score, replaced, i + 1); + } + + bool has_best = false; + + /* + * Attempt to use the current solution first, to try and avoid + * recalculating everything + */ + if (st->orig[i] != UNMATCHED && !is_taken(i, st->res, st->orig[i])) { + st->res[i] = st->orig[i]; + size_t obj_score = st->objs[st->res[i]] != 0 ? 1 : 0; + if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) { + has_best = true; + } + } + if (st->orig[i] == UNMATCHED) { + st->res[i] = UNMATCHED; + if (match_obj_(st, skips, score, replaced, i + 1)) { + has_best = true; + } + } + if (st->exit_early) { + return true; + } + + if (st->orig[i] != UNMATCHED) { + ++replaced; + } + + for (size_t candidate = 0; candidate < st->num_objs; ++candidate) { + // We tried this earlier + if (candidate == st->orig[i]) { + continue; + } + + // Not compatible + if (!(st->objs[candidate] & (1 << i))) { + continue; + } + + // Already taken + if (is_taken(i, st->res, candidate)) { + continue; + } + + st->res[i] = candidate; + size_t obj_score = st->objs[candidate] != 0 ? 1 : 0; + if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) { + has_best = true; + } + + if (st->exit_early) { + return true; + } + } + + if (has_best) { + return true; + } + + // Maybe this resource can't be matched + st->res[i] = UNMATCHED; + return match_obj_(st, skips, score, replaced, i + 1); +} + +size_t match_obj(size_t num_objs, const uint32_t objs[static restrict num_objs], + size_t num_res, const uint32_t res[static restrict num_res], + uint32_t out[static restrict num_res]) { + uint32_t solution[num_res]; + for (size_t i = 0; i < num_res; ++i) { + solution[i] = UNMATCHED; + } + + struct match_state st = { + .num_objs = num_objs, + .num_res = num_res, + .score = 0, + .replaced = SIZE_MAX, + .objs = objs, + .res = solution, + .best = out, + .orig = res, + .exit_early = false, + }; + + match_obj_(&st, 0, 0, 0, 0); + return st.score; +} + +void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay, + float vrefresh) { + // TODO: depending on capabilities advertised in the EDID, use reduced + // blanking if possible (and update sync polarity) + struct di_cvt_options options = { + .red_blank_ver = DI_CVT_REDUCED_BLANKING_NONE, + .h_pixels = hdisplay, + .v_lines = vdisplay, + .ip_freq_rqd = vrefresh ? vrefresh : 60, + }; + struct di_cvt_timing timing; + di_cvt_compute(&timing, &options); + + uint16_t hsync_start = hdisplay + timing.h_front_porch; + uint16_t vsync_start = timing.v_lines_rnd + timing.v_front_porch; + uint16_t hsync_end = hsync_start + timing.h_sync; + uint16_t vsync_end = vsync_start + timing.v_sync; + + *mode = (drmModeModeInfo){ + .clock = roundf(timing.act_pixel_freq * 1000), + .hdisplay = hdisplay, + .vdisplay = timing.v_lines_rnd, + .hsync_start = hsync_start, + .vsync_start = vsync_start, + .hsync_end = hsync_end, + .vsync_end = vsync_end, + .htotal = hsync_end + timing.h_back_porch, + .vtotal = vsync_end + timing.v_back_porch, + .vrefresh = roundf(timing.act_frame_rate), + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC, + }; + snprintf(mode->name, sizeof(mode->name), "%dx%d", hdisplay, vdisplay); +} diff --git a/backend/headless/backend.c b/backend/headless/backend.c new file mode 100644 index 0000000..e643a06 --- /dev/null +++ b/backend/headless/backend.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include "backend/headless.h" + +struct wlr_headless_backend *headless_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_headless(wlr_backend)); + struct wlr_headless_backend *backend = wl_container_of(wlr_backend, backend, backend); + return backend; +} + +static bool backend_start(struct wlr_backend *wlr_backend) { + struct wlr_headless_backend *backend = + headless_backend_from_backend(wlr_backend); + wlr_log(WLR_INFO, "Starting headless backend"); + + struct wlr_headless_output *output; + wl_list_for_each(output, &backend->outputs, link) { + wl_signal_emit_mutable(&backend->backend.events.new_output, + &output->wlr_output); + } + + backend->started = true; + return true; +} + +static void backend_destroy(struct wlr_backend *wlr_backend) { + struct wlr_headless_backend *backend = + headless_backend_from_backend(wlr_backend); + if (!wlr_backend) { + return; + } + + wlr_backend_finish(wlr_backend); + + struct wlr_headless_output *output, *output_tmp; + wl_list_for_each_safe(output, output_tmp, &backend->outputs, link) { + wlr_output_destroy(&output->wlr_output); + } + + wl_list_remove(&backend->event_loop_destroy.link); + + free(backend); +} + +static uint32_t get_buffer_caps(struct wlr_backend *wlr_backend) { + return WLR_BUFFER_CAP_DATA_PTR + | WLR_BUFFER_CAP_DMABUF + | WLR_BUFFER_CAP_SHM; +} + +static const struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_buffer_caps = get_buffer_caps, +}; + +static void handle_event_loop_destroy(struct wl_listener *listener, void *data) { + struct wlr_headless_backend *backend = + wl_container_of(listener, backend, event_loop_destroy); + backend_destroy(&backend->backend); +} + +struct wlr_backend *wlr_headless_backend_create(struct wl_event_loop *loop) { + wlr_log(WLR_INFO, "Creating headless backend"); + + struct wlr_headless_backend *backend = calloc(1, sizeof(*backend)); + if (!backend) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_headless_backend"); + return NULL; + } + + wlr_backend_init(&backend->backend, &backend_impl); + + backend->event_loop = loop; + wl_list_init(&backend->outputs); + + backend->event_loop_destroy.notify = handle_event_loop_destroy; + wl_event_loop_add_destroy_listener(loop, &backend->event_loop_destroy); + + return &backend->backend; +} + +bool wlr_backend_is_headless(struct wlr_backend *backend) { + return backend->impl == &backend_impl; +} diff --git a/backend/headless/meson.build b/backend/headless/meson.build new file mode 100644 index 0000000..950c071 --- /dev/null +++ b/backend/headless/meson.build @@ -0,0 +1,4 @@ +wlr_files += files( + 'backend.c', + 'output.c', +) diff --git a/backend/headless/output.c b/backend/headless/output.c new file mode 100644 index 0000000..5aaf1bd --- /dev/null +++ b/backend/headless/output.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include "backend/headless.h" +#include "types/wlr_output.h" + +static const uint32_t SUPPORTED_OUTPUT_STATE = + WLR_OUTPUT_STATE_BACKEND_OPTIONAL | + WLR_OUTPUT_STATE_BUFFER | + WLR_OUTPUT_STATE_ENABLED | + WLR_OUTPUT_STATE_MODE; + +static size_t last_output_num = 0; + +static struct wlr_headless_output *headless_output_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_headless(wlr_output)); + struct wlr_headless_output *output = wl_container_of(wlr_output, output, wlr_output); + return output; +} + +static void output_update_refresh(struct wlr_headless_output *output, + int32_t refresh) { + if (refresh <= 0) { + refresh = HEADLESS_DEFAULT_REFRESH; + } + + output->frame_delay = 1000000 / refresh; +} + +static bool output_test(struct wlr_output *wlr_output, + const struct wlr_output_state *state) { + uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE; + if (unsupported != 0) { + wlr_log(WLR_DEBUG, "Unsupported output state fields: 0x%"PRIx32, + unsupported); + return false; + } + + if (state->committed & WLR_OUTPUT_STATE_MODE) { + assert(state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM); + } + + if (state->committed & WLR_OUTPUT_STATE_LAYERS) { + for (size_t i = 0; i < state->layers_len; i++) { + state->layers[i].accepted = true; + } + } + + return true; +} + +static bool output_commit(struct wlr_output *wlr_output, + const struct wlr_output_state *state) { + struct wlr_headless_output *output = + headless_output_from_output(wlr_output); + + if (!output_test(wlr_output, state)) { + return false; + } + + if (state->committed & WLR_OUTPUT_STATE_MODE) { + output_update_refresh(output, state->custom_mode.refresh); + } + + if (output_pending_enabled(wlr_output, state)) { + struct wlr_output_event_present present_event = { + .commit_seq = wlr_output->commit_seq + 1, + .presented = true, + }; + output_defer_present(wlr_output, present_event); + + wl_event_source_timer_update(output->frame_timer, output->frame_delay); + } + + return true; +} + +static void output_destroy(struct wlr_output *wlr_output) { + struct wlr_headless_output *output = + headless_output_from_output(wlr_output); + wl_list_remove(&output->link); + wl_event_source_remove(output->frame_timer); + free(output); +} + +static const struct wlr_output_impl output_impl = { + .destroy = output_destroy, + .commit = output_commit, +}; + +bool wlr_output_is_headless(struct wlr_output *wlr_output) { + return wlr_output->impl == &output_impl; +} + +static int signal_frame(void *data) { + struct wlr_headless_output *output = data; + wlr_output_send_frame(&output->wlr_output); + return 0; +} + +struct wlr_output *wlr_headless_add_output(struct wlr_backend *wlr_backend, + unsigned int width, unsigned int height) { + struct wlr_headless_backend *backend = + headless_backend_from_backend(wlr_backend); + + struct wlr_headless_output *output = calloc(1, sizeof(*output)); + if (output == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_headless_output"); + return NULL; + } + output->backend = backend; + struct wlr_output *wlr_output = &output->wlr_output; + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_custom_mode(&state, width, height, 0); + + wlr_output_init(wlr_output, &backend->backend, &output_impl, backend->event_loop, &state); + wlr_output_state_finish(&state); + + output_update_refresh(output, 0); + + size_t output_num = ++last_output_num; + + char name[64]; + snprintf(name, sizeof(name), "HEADLESS-%zu", output_num); + wlr_output_set_name(wlr_output, name); + + char description[128]; + snprintf(description, sizeof(description), "Headless output %zu", output_num); + wlr_output_set_description(wlr_output, description); + + output->frame_timer = wl_event_loop_add_timer(backend->event_loop, signal_frame, output); + + wl_list_insert(&backend->outputs, &output->link); + + if (backend->started) { + wl_signal_emit_mutable(&backend->backend.events.new_output, wlr_output); + } + + return wlr_output; +} diff --git a/backend/libinput/backend.c b/backend/libinput/backend.c new file mode 100644 index 0000000..6e59ee3 --- /dev/null +++ b/backend/libinput/backend.c @@ -0,0 +1,245 @@ +#include +#include +#include +#include +#include +#include +#include +#include "backend/libinput.h" +#include "util/env.h" + +static struct wlr_libinput_backend *get_libinput_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_libinput(wlr_backend)); + struct wlr_libinput_backend *backend = wl_container_of(wlr_backend, backend, backend); + return backend; +} + +static int libinput_open_restricted(const char *path, + int flags, void *_backend) { + struct wlr_libinput_backend *backend = _backend; + struct wlr_device *dev = wlr_session_open_file(backend->session, path); + if (dev == NULL) { + return -1; + } + return dev->fd; +} + +static void libinput_close_restricted(int fd, void *_backend) { + struct wlr_libinput_backend *backend = _backend; + + struct wlr_device *dev; + bool found = false; + wl_list_for_each(dev, &backend->session->devices, link) { + if (dev->fd == fd) { + found = true; + break; + } + } + if (found) { + wlr_session_close_file(backend->session, dev); + } +} + +static const struct libinput_interface libinput_impl = { + .open_restricted = libinput_open_restricted, + .close_restricted = libinput_close_restricted +}; + +static int handle_libinput_readable(int fd, uint32_t mask, void *_backend) { + struct wlr_libinput_backend *backend = _backend; + int ret = libinput_dispatch(backend->libinput_context); + if (ret != 0) { + wlr_log(WLR_ERROR, "Failed to dispatch libinput: %s", strerror(-ret)); + wlr_backend_destroy(&backend->backend); + return 0; + } + struct libinput_event *event; + while ((event = libinput_get_event(backend->libinput_context))) { + handle_libinput_event(backend, event); + libinput_event_destroy(event); + } + return 0; +} + +static enum wlr_log_importance libinput_log_priority_to_wlr( + enum libinput_log_priority priority) { + switch (priority) { + case LIBINPUT_LOG_PRIORITY_ERROR: + return WLR_ERROR; + case LIBINPUT_LOG_PRIORITY_INFO: + return WLR_INFO; + default: + return WLR_DEBUG; + } +} + +static void log_libinput(struct libinput *libinput_context, + enum libinput_log_priority priority, const char *fmt, va_list args) { + enum wlr_log_importance importance = libinput_log_priority_to_wlr(priority); + static char wlr_fmt[1024]; + snprintf(wlr_fmt, sizeof(wlr_fmt), "[libinput] %s", fmt); + _wlr_vlog(importance, wlr_fmt, args); +} + +static bool backend_start(struct wlr_backend *wlr_backend) { + struct wlr_libinput_backend *backend = + get_libinput_backend_from_backend(wlr_backend); + wlr_log(WLR_DEBUG, "Starting libinput backend"); + + backend->libinput_context = libinput_udev_create_context(&libinput_impl, + backend, backend->session->udev); + if (!backend->libinput_context) { + wlr_log(WLR_ERROR, "Failed to create libinput context"); + return false; + } + + if (libinput_udev_assign_seat(backend->libinput_context, + backend->session->seat) != 0) { + wlr_log(WLR_ERROR, "Failed to assign libinput seat"); + return false; + } + + // TODO: More sophisticated logging + libinput_log_set_handler(backend->libinput_context, log_libinput); + libinput_log_set_priority(backend->libinput_context, LIBINPUT_LOG_PRIORITY_ERROR); + + int libinput_fd = libinput_get_fd(backend->libinput_context); + + if (!env_parse_bool("WLR_LIBINPUT_NO_DEVICES") && wl_list_empty(&backend->devices)) { + handle_libinput_readable(libinput_fd, WL_EVENT_READABLE, backend); + if (wl_list_empty(&backend->devices)) { + wlr_log(WLR_ERROR, "libinput initialization failed, no input devices"); + wlr_log(WLR_ERROR, "Set WLR_LIBINPUT_NO_DEVICES=1 to suppress this check"); + return false; + } + } + + if (backend->input_event) { + wl_event_source_remove(backend->input_event); + } + backend->input_event = wl_event_loop_add_fd(backend->session->event_loop, libinput_fd, + WL_EVENT_READABLE, handle_libinput_readable, backend); + if (!backend->input_event) { + wlr_log(WLR_ERROR, "Failed to create input event on event loop"); + return false; + } + wlr_log(WLR_DEBUG, "libinput successfully initialized"); + return true; +} + +static void backend_destroy(struct wlr_backend *wlr_backend) { + if (!wlr_backend) { + return; + } + struct wlr_libinput_backend *backend = + get_libinput_backend_from_backend(wlr_backend); + + struct wlr_libinput_input_device *dev, *tmp; + wl_list_for_each_safe(dev, tmp, &backend->devices, link) { + destroy_libinput_input_device(dev); + } + + wlr_backend_finish(wlr_backend); + + wl_list_remove(&backend->session_destroy.link); + wl_list_remove(&backend->session_signal.link); + + if (backend->input_event) { + wl_event_source_remove(backend->input_event); + } + libinput_unref(backend->libinput_context); + free(backend); +} + +static const struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, +}; + +bool wlr_backend_is_libinput(struct wlr_backend *b) { + return b->impl == &backend_impl; +} + +static void session_signal(struct wl_listener *listener, void *data) { + struct wlr_libinput_backend *backend = + wl_container_of(listener, backend, session_signal); + struct wlr_session *session = backend->session; + + if (!backend->libinput_context) { + return; + } + + if (session->active) { + libinput_resume(backend->libinput_context); + } else { + libinput_suspend(backend->libinput_context); + } +} + +static void handle_session_destroy(struct wl_listener *listener, void *data) { + struct wlr_libinput_backend *backend = + wl_container_of(listener, backend, session_destroy); + backend_destroy(&backend->backend); +} + +struct wlr_backend *wlr_libinput_backend_create(struct wlr_session *session) { + struct wlr_libinput_backend *backend = calloc(1, sizeof(*backend)); + if (!backend) { + wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); + return NULL; + } + wlr_backend_init(&backend->backend, &backend_impl); + + wl_list_init(&backend->devices); + + backend->session = session; + + backend->session_signal.notify = session_signal; + wl_signal_add(&session->events.active, &backend->session_signal); + + backend->session_destroy.notify = handle_session_destroy; + wl_signal_add(&session->events.destroy, &backend->session_destroy); + + return &backend->backend; +} + +struct libinput_device *wlr_libinput_get_device_handle( + struct wlr_input_device *wlr_dev) { + struct wlr_libinput_input_device *dev = NULL; + switch (wlr_dev->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + dev = device_from_keyboard(wlr_keyboard_from_input_device(wlr_dev)); + break; + case WLR_INPUT_DEVICE_POINTER: + dev = device_from_pointer(wlr_pointer_from_input_device(wlr_dev)); + break; + case WLR_INPUT_DEVICE_SWITCH: + dev = device_from_switch(wlr_switch_from_input_device(wlr_dev)); + break; + case WLR_INPUT_DEVICE_TOUCH: + dev = device_from_touch(wlr_touch_from_input_device(wlr_dev)); + break; + case WLR_INPUT_DEVICE_TABLET: + dev = device_from_tablet(wlr_tablet_from_input_device(wlr_dev)); + break; + case WLR_INPUT_DEVICE_TABLET_PAD: + dev = device_from_tablet_pad(wlr_tablet_pad_from_input_device(wlr_dev)); + break; + } + return dev->handle; +} + +uint32_t usec_to_msec(uint64_t usec) { + return (uint32_t)(usec / 1000); +} + +const char *get_libinput_device_name(struct libinput_device *device) { + // libinput guarantees that the name is non-NULL, and an empty string if + // unset. However wlroots uses NULL to indicate that the name is unset. + const char *name = libinput_device_get_name(device); + if (name[0] == '\0') { + return NULL; + } + return name; +} diff --git a/backend/libinput/events.c b/backend/libinput/events.c new file mode 100644 index 0000000..25e71b7 --- /dev/null +++ b/backend/libinput/events.c @@ -0,0 +1,263 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/libinput.h" + +void destroy_libinput_input_device(struct wlr_libinput_input_device *dev) { + if (dev->keyboard.impl) { + wlr_keyboard_finish(&dev->keyboard); + } + if (dev->pointer.impl) { + wlr_pointer_finish(&dev->pointer); + } + if (dev->switch_device.impl) { + wlr_switch_finish(&dev->switch_device); + } + if (dev->touch.impl) { + wlr_touch_finish(&dev->touch); + } + if (dev->tablet.impl) { + finish_device_tablet(dev); + } + if (dev->tablet_pad.impl) { + finish_device_tablet_pad(dev); + } + + libinput_device_unref(dev->handle); + wl_list_remove(&dev->link); + free(dev); +} + +bool wlr_input_device_is_libinput(struct wlr_input_device *wlr_dev) { + switch (wlr_dev->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + return wlr_keyboard_from_input_device(wlr_dev)->impl == + &libinput_keyboard_impl; + case WLR_INPUT_DEVICE_POINTER: + return wlr_pointer_from_input_device(wlr_dev)->impl == + &libinput_pointer_impl; + case WLR_INPUT_DEVICE_TOUCH: + return wlr_touch_from_input_device(wlr_dev)->impl == + &libinput_touch_impl; + case WLR_INPUT_DEVICE_TABLET: + return wlr_tablet_from_input_device(wlr_dev)-> impl == + &libinput_tablet_impl; + case WLR_INPUT_DEVICE_TABLET_PAD: + return wlr_tablet_pad_from_input_device(wlr_dev)->impl == + &libinput_tablet_pad_impl; + case WLR_INPUT_DEVICE_SWITCH: + return wlr_switch_from_input_device(wlr_dev)->impl == + &libinput_switch_impl; + default: + return false; + } +} + +static void handle_device_added(struct wlr_libinput_backend *backend, + struct libinput_device *libinput_dev) { + int vendor = libinput_device_get_id_vendor(libinput_dev); + int product = libinput_device_get_id_product(libinput_dev); + const char *name = libinput_device_get_name(libinput_dev); + wlr_log(WLR_DEBUG, "Adding %s [%d:%d]", name, vendor, product); + + struct wlr_libinput_input_device *dev = calloc(1, sizeof(*dev)); + if (dev == NULL) { + wlr_log_errno(WLR_ERROR, "failed to allocate wlr_libinput_input_device"); + return; + } + + dev->handle = libinput_dev; + libinput_device_ref(libinput_dev); + libinput_device_set_user_data(libinput_dev, dev); + + wl_list_insert(&backend->devices, &dev->link); + + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_KEYBOARD)) { + init_device_keyboard(dev); + + wl_signal_emit_mutable(&backend->backend.events.new_input, + &dev->keyboard.base); + } + + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_POINTER)) { + init_device_pointer(dev); + + wl_signal_emit_mutable(&backend->backend.events.new_input, + &dev->pointer.base); + } + + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_SWITCH)) { + init_device_switch(dev); + wl_signal_emit_mutable(&backend->backend.events.new_input, + &dev->switch_device.base); + } + + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_TOUCH)) { + init_device_touch(dev); + wl_signal_emit_mutable(&backend->backend.events.new_input, + &dev->touch.base); + } + + if (libinput_device_has_capability(libinput_dev, + LIBINPUT_DEVICE_CAP_TABLET_TOOL)) { + init_device_tablet(dev); + wl_signal_emit_mutable(&backend->backend.events.new_input, + &dev->tablet.base); + } + + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_TABLET_PAD)) { + init_device_tablet_pad(dev); + wl_signal_emit_mutable(&backend->backend.events.new_input, + &dev->tablet_pad.base); + } + + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_GESTURE)) { + wlr_log(WLR_DEBUG, "libinput gesture not handled"); + } +} + +static void handle_device_removed(struct wlr_libinput_backend *backend, + struct wlr_libinput_input_device *dev) { + int vendor = libinput_device_get_id_vendor(dev->handle); + int product = libinput_device_get_id_product(dev->handle); + const char *name = libinput_device_get_name(dev->handle); + wlr_log(WLR_DEBUG, "Removing %s [%d:%d]", name, vendor, product); + + destroy_libinput_input_device(dev); +} + +void handle_libinput_event(struct wlr_libinput_backend *backend, + struct libinput_event *event) { + struct libinput_device *libinput_dev = libinput_event_get_device(event); + struct wlr_libinput_input_device *dev = + libinput_device_get_user_data(libinput_dev); + enum libinput_event_type event_type = libinput_event_get_type(event); + + if (dev == NULL && event_type != LIBINPUT_EVENT_DEVICE_ADDED) { + wlr_log(WLR_ERROR, "libinput_device has no wlr_libinput_input_device"); + return; + } + + switch (event_type) { + case LIBINPUT_EVENT_DEVICE_ADDED: + handle_device_added(backend, libinput_dev); + break; + case LIBINPUT_EVENT_DEVICE_REMOVED: + handle_device_removed(backend, dev); + break; + case LIBINPUT_EVENT_KEYBOARD_KEY: + handle_keyboard_key(event, &dev->keyboard); + break; + case LIBINPUT_EVENT_POINTER_MOTION: + handle_pointer_motion(event, &dev->pointer); + break; + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: + handle_pointer_motion_abs(event, &dev->pointer); + break; + case LIBINPUT_EVENT_POINTER_BUTTON: + handle_pointer_button(event, &dev->pointer); + break; + case LIBINPUT_EVENT_POINTER_AXIS: +#if !HAVE_LIBINPUT_SCROLL_VALUE120 + /* This event must be ignored in favour of the SCROLL_* events */ + handle_pointer_axis(event, &dev->pointer); +#endif + break; +#if HAVE_LIBINPUT_SCROLL_VALUE120 + case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL: + handle_pointer_axis_value120(event, &dev->pointer, + WL_POINTER_AXIS_SOURCE_WHEEL); + break; + case LIBINPUT_EVENT_POINTER_SCROLL_FINGER: + handle_pointer_axis_value120(event, &dev->pointer, + WL_POINTER_AXIS_SOURCE_FINGER); + break; + case LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS: + handle_pointer_axis_value120(event, &dev->pointer, + WL_POINTER_AXIS_SOURCE_CONTINUOUS); + break; +#endif + case LIBINPUT_EVENT_TOUCH_DOWN: + handle_touch_down(event, &dev->touch); + break; + case LIBINPUT_EVENT_TOUCH_UP: + handle_touch_up(event, &dev->touch); + break; + case LIBINPUT_EVENT_TOUCH_MOTION: + handle_touch_motion(event, &dev->touch); + break; + case LIBINPUT_EVENT_TOUCH_CANCEL: + handle_touch_cancel(event, &dev->touch); + break; + case LIBINPUT_EVENT_TOUCH_FRAME: + handle_touch_frame(event, &dev->touch); + break; + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + handle_tablet_tool_axis(event, &dev->tablet); + break; + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + handle_tablet_tool_proximity(event, &dev->tablet); + break; + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + handle_tablet_tool_tip(event, &dev->tablet); + break; + case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: + handle_tablet_tool_button(event, &dev->tablet); + break; + case LIBINPUT_EVENT_TABLET_PAD_BUTTON: + handle_tablet_pad_button(event, &dev->tablet_pad); + break; + case LIBINPUT_EVENT_TABLET_PAD_RING: + handle_tablet_pad_ring(event, &dev->tablet_pad); + break; + case LIBINPUT_EVENT_TABLET_PAD_STRIP: + handle_tablet_pad_strip(event, &dev->tablet_pad); + break; + case LIBINPUT_EVENT_SWITCH_TOGGLE: + handle_switch_toggle(event, &dev->switch_device); + break; + case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: + handle_pointer_swipe_begin(event, &dev->pointer); + break; + case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: + handle_pointer_swipe_update(event, &dev->pointer); + break; + case LIBINPUT_EVENT_GESTURE_SWIPE_END: + handle_pointer_swipe_end(event, &dev->pointer); + break; + case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: + handle_pointer_pinch_begin(event, &dev->pointer); + break; + case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: + handle_pointer_pinch_update(event, &dev->pointer); + break; + case LIBINPUT_EVENT_GESTURE_PINCH_END: + handle_pointer_pinch_end(event, &dev->pointer); + break; +#if HAVE_LIBINPUT_HOLD_GESTURES + case LIBINPUT_EVENT_GESTURE_HOLD_BEGIN: + handle_pointer_hold_begin(event, &dev->pointer); + break; + case LIBINPUT_EVENT_GESTURE_HOLD_END: + handle_pointer_hold_end(event, &dev->pointer); + break; +#endif + default: + wlr_log(WLR_DEBUG, "Unknown libinput event %d", event_type); + break; + } +} diff --git a/backend/libinput/keyboard.c b/backend/libinput/keyboard.c new file mode 100644 index 0000000..7518453 --- /dev/null +++ b/backend/libinput/keyboard.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include "backend/libinput.h" + +struct wlr_libinput_input_device *device_from_keyboard( + struct wlr_keyboard *kb) { + assert(kb->impl == &libinput_keyboard_impl); + + struct wlr_libinput_input_device *dev = wl_container_of(kb, dev, keyboard); + return dev; +} + +static void keyboard_set_leds(struct wlr_keyboard *wlr_kb, uint32_t leds) { + struct wlr_libinput_input_device *dev = device_from_keyboard(wlr_kb); + libinput_device_led_update(dev->handle, leds); +} + +const struct wlr_keyboard_impl libinput_keyboard_impl = { + .name = "libinput-keyboard", + .led_update = keyboard_set_leds +}; + +void init_device_keyboard(struct wlr_libinput_input_device *dev) { + const char *name = get_libinput_device_name(dev->handle); + struct wlr_keyboard *wlr_kb = &dev->keyboard; + wlr_keyboard_init(wlr_kb, &libinput_keyboard_impl, name); + + libinput_device_led_update(dev->handle, 0); +} + +void handle_keyboard_key(struct libinput_event *event, + struct wlr_keyboard *kb) { + struct libinput_event_keyboard *kbevent = + libinput_event_get_keyboard_event(event); + struct wlr_keyboard_key_event wlr_event = { + .time_msec = usec_to_msec(libinput_event_keyboard_get_time_usec(kbevent)), + .keycode = libinput_event_keyboard_get_key(kbevent), + .update_state = true, + }; + switch (libinput_event_keyboard_get_key_state(kbevent)) { + case LIBINPUT_KEY_STATE_RELEASED: + wlr_event.state = WL_KEYBOARD_KEY_STATE_RELEASED; + break; + case LIBINPUT_KEY_STATE_PRESSED: + wlr_event.state = WL_KEYBOARD_KEY_STATE_PRESSED; + break; + } + wlr_keyboard_notify_key(kb, &wlr_event); +} diff --git a/backend/libinput/meson.build b/backend/libinput/meson.build new file mode 100644 index 0000000..0dfac5d --- /dev/null +++ b/backend/libinput/meson.build @@ -0,0 +1,34 @@ +msg = ['Required for libinput backend support.'] +if 'libinput' in backends + msg += 'Install "libinput" or disable the libinput backend.' +endif + +libinput = dependency( + 'libinput', + version: '>=1.14.0', + required: 'libinput' in backends, + not_found_message: '\n'.join(msg), +) + +if not (libinput.found() and features['session']) + subdir_done() +endif + +wlr_files += files( + 'backend.c', + 'events.c', + 'keyboard.c', + 'pointer.c', + 'switch.c', + 'tablet_pad.c', + 'tablet_tool.c', + 'touch.c', +) + +features += { 'libinput-backend': true } +wlr_deps += libinput + +# libinput hold gestures and high resolution scroll are available since 1.19.0 +internal_config.set10('HAVE_LIBINPUT_HOLD_GESTURES', libinput.version().version_compare('>=1.19.0')) +internal_config.set10('HAVE_LIBINPUT_SCROLL_VALUE120', libinput.version().version_compare('>=1.19.0')) +internal_config.set10('HAVE_LIBINPUT_BUSTYPE', libinput.version().version_compare('>=1.26.0')) diff --git a/backend/libinput/pointer.c b/backend/libinput/pointer.c new file mode 100644 index 0000000..034b178 --- /dev/null +++ b/backend/libinput/pointer.c @@ -0,0 +1,286 @@ +#include +#include +#include +#include "backend/libinput.h" + +const struct wlr_pointer_impl libinput_pointer_impl = { + .name = "libinput-pointer", +}; + +void init_device_pointer(struct wlr_libinput_input_device *dev) { + const char *name = get_libinput_device_name(dev->handle); + struct wlr_pointer *wlr_pointer = &dev->pointer; + wlr_pointer_init(wlr_pointer, &libinput_pointer_impl, name); +} + +struct wlr_libinput_input_device *device_from_pointer( + struct wlr_pointer *wlr_pointer) { + assert(wlr_pointer->impl == &libinput_pointer_impl); + + struct wlr_libinput_input_device *dev = + wl_container_of(wlr_pointer, dev, pointer); + return dev; +} + +void handle_pointer_motion(struct libinput_event *event, + struct wlr_pointer *pointer) { + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_pointer_motion_event wlr_event = { + .pointer = pointer, + .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), + .delta_x = libinput_event_pointer_get_dx(pevent), + .delta_y = libinput_event_pointer_get_dy(pevent), + .unaccel_dx = libinput_event_pointer_get_dx_unaccelerated(pevent), + .unaccel_dy = libinput_event_pointer_get_dy_unaccelerated(pevent), + }; + wl_signal_emit_mutable(&pointer->events.motion, &wlr_event); + wl_signal_emit_mutable(&pointer->events.frame, pointer); +} + +void handle_pointer_motion_abs(struct libinput_event *event, + struct wlr_pointer *pointer) { + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_pointer_motion_absolute_event wlr_event = { + .pointer = pointer, + .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), + .x = libinput_event_pointer_get_absolute_x_transformed(pevent, 1), + .y = libinput_event_pointer_get_absolute_y_transformed(pevent, 1), + }; + wl_signal_emit_mutable(&pointer->events.motion_absolute, &wlr_event); + wl_signal_emit_mutable(&pointer->events.frame, pointer); +} + +void handle_pointer_button(struct libinput_event *event, + struct wlr_pointer *pointer) { + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_pointer_button_event wlr_event = { + .pointer = pointer, + .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), + .button = libinput_event_pointer_get_button(pevent), + }; + // Ignore events which aren't a seat-wide state change. For instance, if + // the same button is pressed twice on the same seat, ignore the second + // press. + uint32_t seat_count = libinput_event_pointer_get_seat_button_count(pevent); + switch (libinput_event_pointer_get_button_state(pevent)) { + case LIBINPUT_BUTTON_STATE_PRESSED: + wlr_event.state = WL_POINTER_BUTTON_STATE_PRESSED; + if (seat_count != 1) { + return; + } + break; + case LIBINPUT_BUTTON_STATE_RELEASED: + wlr_event.state = WL_POINTER_BUTTON_STATE_RELEASED; + if (seat_count != 0) { + return; + } + break; + } + wl_signal_emit_mutable(&pointer->events.button, &wlr_event); + wl_signal_emit_mutable(&pointer->events.frame, pointer); +} + +void handle_pointer_axis(struct libinput_event *event, + struct wlr_pointer *pointer) { + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_pointer_axis_event wlr_event = { + .pointer = pointer, + .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), + }; + switch (libinput_event_pointer_get_axis_source(pevent)) { + case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL: + wlr_event.source = WL_POINTER_AXIS_SOURCE_WHEEL; + break; + case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: + wlr_event.source = WL_POINTER_AXIS_SOURCE_FINGER; + break; + case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: + wlr_event.source = WL_POINTER_AXIS_SOURCE_CONTINUOUS; + break; + case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT: + wlr_event.source = WL_POINTER_AXIS_SOURCE_WHEEL_TILT; + break; + } + const enum libinput_pointer_axis axes[] = { + LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, + LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, + }; + for (size_t i = 0; i < sizeof(axes) / sizeof(axes[0]); ++i) { + if (!libinput_event_pointer_has_axis(pevent, axes[i])) { + continue; + } + + switch (axes[i]) { + case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL: + wlr_event.orientation = WL_POINTER_AXIS_VERTICAL_SCROLL; + break; + case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL: + wlr_event.orientation = WL_POINTER_AXIS_HORIZONTAL_SCROLL; + break; + } + wlr_event.delta = + libinput_event_pointer_get_axis_value(pevent, axes[i]); + wlr_event.delta_discrete = + libinput_event_pointer_get_axis_value_discrete(pevent, axes[i]); + wlr_event.delta_discrete *= WLR_POINTER_AXIS_DISCRETE_STEP; + wlr_event.relative_direction = WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL; + if (libinput_device_config_scroll_get_natural_scroll_enabled(libinput_event_get_device(event))) { + wlr_event.relative_direction = WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED; + } + wl_signal_emit_mutable(&pointer->events.axis, &wlr_event); + } + wl_signal_emit_mutable(&pointer->events.frame, pointer); +} + +#if HAVE_LIBINPUT_SCROLL_VALUE120 +void handle_pointer_axis_value120(struct libinput_event *event, + struct wlr_pointer *pointer, enum wl_pointer_axis_source source) { + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_pointer_axis_event wlr_event = { + .pointer = pointer, + .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), + .source = source, + }; + + const enum libinput_pointer_axis axes[] = { + LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, + LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, + }; + for (size_t i = 0; i < sizeof(axes) / sizeof(axes[0]); ++i) { + if (!libinput_event_pointer_has_axis(pevent, axes[i])) { + continue; + } + switch (axes[i]) { + case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL: + wlr_event.orientation = WL_POINTER_AXIS_VERTICAL_SCROLL; + break; + case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL: + wlr_event.orientation = WL_POINTER_AXIS_HORIZONTAL_SCROLL; + break; + } + wlr_event.delta = + libinput_event_pointer_get_scroll_value(pevent, axes[i]); + if (source == WL_POINTER_AXIS_SOURCE_WHEEL) { + wlr_event.delta_discrete = + libinput_event_pointer_get_scroll_value_v120(pevent, axes[i]); + } + wl_signal_emit_mutable(&pointer->events.axis, &wlr_event); + } + wl_signal_emit_mutable(&pointer->events.frame, pointer); +} +#endif + +void handle_pointer_swipe_begin(struct libinput_event *event, + struct wlr_pointer *pointer) { + struct libinput_event_gesture *gevent = + libinput_event_get_gesture_event(event); + struct wlr_pointer_swipe_begin_event wlr_event = { + .pointer = pointer, + .time_msec = + usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), + .fingers = libinput_event_gesture_get_finger_count(gevent), + }; + wl_signal_emit_mutable(&pointer->events.swipe_begin, &wlr_event); +} + +void handle_pointer_swipe_update(struct libinput_event *event, + struct wlr_pointer *pointer) { + struct libinput_event_gesture *gevent = + libinput_event_get_gesture_event(event); + struct wlr_pointer_swipe_update_event wlr_event = { + .pointer = pointer, + .time_msec = + usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), + .fingers = libinput_event_gesture_get_finger_count(gevent), + .dx = libinput_event_gesture_get_dx(gevent), + .dy = libinput_event_gesture_get_dy(gevent), + }; + wl_signal_emit_mutable(&pointer->events.swipe_update, &wlr_event); +} + +void handle_pointer_swipe_end(struct libinput_event *event, + struct wlr_pointer *pointer) { + struct libinput_event_gesture *gevent = + libinput_event_get_gesture_event(event); + struct wlr_pointer_swipe_end_event wlr_event = { + .pointer = pointer, + .time_msec = + usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), + .cancelled = libinput_event_gesture_get_cancelled(gevent), + }; + wl_signal_emit_mutable(&pointer->events.swipe_end, &wlr_event); +} + +void handle_pointer_pinch_begin(struct libinput_event *event, + struct wlr_pointer *pointer) { + struct libinput_event_gesture *gevent = + libinput_event_get_gesture_event(event); + struct wlr_pointer_pinch_begin_event wlr_event = { + .pointer = pointer, + .time_msec = + usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), + .fingers = libinput_event_gesture_get_finger_count(gevent), + }; + wl_signal_emit_mutable(&pointer->events.pinch_begin, &wlr_event); +} + +void handle_pointer_pinch_update(struct libinput_event *event, + struct wlr_pointer *pointer) { + struct libinput_event_gesture *gevent = + libinput_event_get_gesture_event(event); + struct wlr_pointer_pinch_update_event wlr_event = { + .pointer = pointer, + .time_msec = + usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), + .fingers = libinput_event_gesture_get_finger_count(gevent), + .dx = libinput_event_gesture_get_dx(gevent), + .dy = libinput_event_gesture_get_dy(gevent), + .scale = libinput_event_gesture_get_scale(gevent), + .rotation = libinput_event_gesture_get_angle_delta(gevent), + }; + wl_signal_emit_mutable(&pointer->events.pinch_update, &wlr_event); +} + +void handle_pointer_pinch_end(struct libinput_event *event, + struct wlr_pointer *pointer) { + struct libinput_event_gesture *gevent = + libinput_event_get_gesture_event(event); + struct wlr_pointer_pinch_end_event wlr_event = { + .pointer = pointer, + .time_msec = + usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), + .cancelled = libinput_event_gesture_get_cancelled(gevent), + }; + wl_signal_emit_mutable(&pointer->events.pinch_end, &wlr_event); +} + +void handle_pointer_hold_begin(struct libinput_event *event, + struct wlr_pointer *pointer) { + struct libinput_event_gesture *gevent = + libinput_event_get_gesture_event(event); + struct wlr_pointer_hold_begin_event wlr_event = { + .pointer = pointer, + .time_msec = + usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), + .fingers = libinput_event_gesture_get_finger_count(gevent), + }; + wl_signal_emit_mutable(&pointer->events.hold_begin, &wlr_event); +} + +void handle_pointer_hold_end(struct libinput_event *event, + struct wlr_pointer *pointer) { + struct libinput_event_gesture *gevent = + libinput_event_get_gesture_event(event); + struct wlr_pointer_hold_end_event wlr_event = { + .pointer = pointer, + .time_msec = + usec_to_msec(libinput_event_gesture_get_time_usec(gevent)), + .cancelled = libinput_event_gesture_get_cancelled(gevent), + }; + wl_signal_emit_mutable(&pointer->events.hold_end, &wlr_event); +} diff --git a/backend/libinput/switch.c b/backend/libinput/switch.c new file mode 100644 index 0000000..abeec86 --- /dev/null +++ b/backend/libinput/switch.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include "backend/libinput.h" + +const struct wlr_switch_impl libinput_switch_impl = { + .name = "libinput-switch", +}; + +void init_device_switch(struct wlr_libinput_input_device *dev) { + const char *name = get_libinput_device_name(dev->handle); + struct wlr_switch *wlr_switch = &dev->switch_device; + wlr_switch_init(wlr_switch, &libinput_switch_impl, name); +} + +struct wlr_libinput_input_device *device_from_switch( + struct wlr_switch *wlr_switch) { + assert(wlr_switch->impl == &libinput_switch_impl); + + struct wlr_libinput_input_device *dev = + wl_container_of(wlr_switch, dev, switch_device); + return dev; +} + +void handle_switch_toggle(struct libinput_event *event, + struct wlr_switch *wlr_switch) { + struct libinput_event_switch *sevent = + libinput_event_get_switch_event (event); + struct wlr_switch_toggle_event wlr_event = { + .time_msec = usec_to_msec(libinput_event_switch_get_time_usec(sevent)), + }; + switch (libinput_event_switch_get_switch(sevent)) { + case LIBINPUT_SWITCH_LID: + wlr_event.switch_type = WLR_SWITCH_TYPE_LID; + break; + case LIBINPUT_SWITCH_TABLET_MODE: + wlr_event.switch_type = WLR_SWITCH_TYPE_TABLET_MODE; + break; + } + switch (libinput_event_switch_get_switch_state(sevent)) { + case LIBINPUT_SWITCH_STATE_OFF: + wlr_event.switch_state = WLR_SWITCH_STATE_OFF; + break; + case LIBINPUT_SWITCH_STATE_ON: + wlr_event.switch_state = WLR_SWITCH_STATE_ON; + break; + } + wl_signal_emit_mutable(&wlr_switch->events.toggle, &wlr_event); +} diff --git a/backend/libinput/tablet_pad.c b/backend/libinput/tablet_pad.c new file mode 100644 index 0000000..98b3e4f --- /dev/null +++ b/backend/libinput/tablet_pad.c @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include +#include +#include "backend/libinput.h" + +const struct wlr_tablet_pad_impl libinput_tablet_pad_impl = { + .name = "libinput-tablet-pad", +}; + +static void group_destroy(struct wlr_tablet_pad_group *group) { + free(group->buttons); + free(group->strips); + free(group->rings); + free(group); +} + +static void add_pad_group_from_libinput(struct wlr_tablet_pad *pad, + struct libinput_device *device, unsigned int index) { + struct libinput_tablet_pad_mode_group *li_group = + libinput_device_tablet_pad_get_mode_group(device, index); + struct wlr_tablet_pad_group *group = calloc(1, sizeof(*group)); + if (!group) { + wlr_log_errno(WLR_ERROR, "failed to allocate wlr_tablet_pad_group"); + return; + } + + for (size_t i = 0; i < pad->ring_count; ++i) { + if (libinput_tablet_pad_mode_group_has_ring(li_group, i)) { + ++group->ring_count; + } + } + group->rings = calloc(group->ring_count, sizeof(unsigned int)); + if (group->rings == NULL) { + goto group_fail; + } + + size_t ring = 0; + for (size_t i = 0; i < pad->ring_count; ++i) { + if (libinput_tablet_pad_mode_group_has_ring(li_group, i)) { + group->rings[ring++] = i; + } + } + + for (size_t i = 0; i < pad->strip_count; ++i) { + if (libinput_tablet_pad_mode_group_has_strip(li_group, i)) { + ++group->strip_count; + } + } + group->strips = calloc(group->strip_count, sizeof(unsigned int)); + if (group->strips == NULL) { + goto group_fail; + } + size_t strip = 0; + for (size_t i = 0; i < pad->strip_count; ++i) { + if (libinput_tablet_pad_mode_group_has_strip(li_group, i)) { + group->strips[strip++] = i; + } + } + + for (size_t i = 0; i < pad->button_count; ++i) { + if (libinput_tablet_pad_mode_group_has_button(li_group, i)) { + ++group->button_count; + } + } + group->buttons = calloc(group->button_count, sizeof(unsigned int)); + if (group->buttons == NULL) { + goto group_fail; + } + size_t button = 0; + for (size_t i = 0; i < pad->button_count; ++i) { + if (libinput_tablet_pad_mode_group_has_button(li_group, i)) { + group->buttons[button++] = i; + } + } + + group->mode_count = libinput_tablet_pad_mode_group_get_num_modes(li_group); + + libinput_tablet_pad_mode_group_ref(li_group); + + wl_list_insert(&pad->groups, &group->link); + return; + +group_fail: + wlr_log(WLR_ERROR, "failed to configure wlr_tablet_pad_group"); + group_destroy(group); +} + +void init_device_tablet_pad(struct wlr_libinput_input_device *dev) { + struct libinput_device *handle = dev->handle; + const char *name = get_libinput_device_name(handle); + struct wlr_tablet_pad *wlr_tablet_pad = &dev->tablet_pad; + wlr_tablet_pad_init(wlr_tablet_pad, &libinput_tablet_pad_impl, name); + + wlr_tablet_pad->button_count = + libinput_device_tablet_pad_get_num_buttons(handle); + wlr_tablet_pad->ring_count = + libinput_device_tablet_pad_get_num_rings(handle); + wlr_tablet_pad->strip_count = + libinput_device_tablet_pad_get_num_strips(handle); + + struct udev_device *udev = libinput_device_get_udev_device(handle); + char **dst = wl_array_add(&wlr_tablet_pad->paths, sizeof(char *)); + *dst = strdup(udev_device_get_syspath(udev)); + + int groups = libinput_device_tablet_pad_get_num_mode_groups(handle); + for (int i = 0; i < groups; ++i) { + add_pad_group_from_libinput(wlr_tablet_pad, handle, i); + } +} + +void finish_device_tablet_pad(struct wlr_libinput_input_device *dev) { + struct wlr_tablet_pad_group *group, *tmp; + wl_list_for_each_safe(group, tmp, &dev->tablet_pad.groups, link) { + group_destroy(group); + } + + wlr_tablet_pad_finish(&dev->tablet_pad); + + int groups = libinput_device_tablet_pad_get_num_mode_groups(dev->handle); + for (int i = 0; i < groups; ++i) { + struct libinput_tablet_pad_mode_group *li_group = + libinput_device_tablet_pad_get_mode_group(dev->handle, i); + libinput_tablet_pad_mode_group_unref(li_group); + } +} + +struct wlr_libinput_input_device *device_from_tablet_pad( + struct wlr_tablet_pad *wlr_tablet_pad) { + assert(wlr_tablet_pad->impl == &libinput_tablet_pad_impl); + + struct wlr_libinput_input_device *dev = + wl_container_of(wlr_tablet_pad, dev, tablet_pad); + return dev; +} + +void handle_tablet_pad_button(struct libinput_event *event, + struct wlr_tablet_pad *tablet_pad) { + struct libinput_event_tablet_pad *pevent = + libinput_event_get_tablet_pad_event(event); + struct wlr_tablet_pad_button_event wlr_event = { + .time_msec = usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)), + .button = libinput_event_tablet_pad_get_button_number(pevent), + .mode = libinput_event_tablet_pad_get_mode(pevent), + .group = libinput_tablet_pad_mode_group_get_index( + libinput_event_tablet_pad_get_mode_group(pevent)), + }; + switch (libinput_event_tablet_pad_get_button_state(pevent)) { + case LIBINPUT_BUTTON_STATE_PRESSED: + wlr_event.state = WLR_BUTTON_PRESSED; + break; + case LIBINPUT_BUTTON_STATE_RELEASED: + wlr_event.state = WLR_BUTTON_RELEASED; + break; + } + wl_signal_emit_mutable(&tablet_pad->events.button, &wlr_event); +} + +void handle_tablet_pad_ring(struct libinput_event *event, + struct wlr_tablet_pad *tablet_pad) { + struct libinput_event_tablet_pad *pevent = + libinput_event_get_tablet_pad_event(event); + struct wlr_tablet_pad_ring_event wlr_event = { + .time_msec = usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)), + .ring = libinput_event_tablet_pad_get_ring_number(pevent), + .position = libinput_event_tablet_pad_get_ring_position(pevent), + .mode = libinput_event_tablet_pad_get_mode(pevent), + }; + switch (libinput_event_tablet_pad_get_ring_source(pevent)) { + case LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN: + wlr_event.source = WLR_TABLET_PAD_RING_SOURCE_UNKNOWN; + break; + case LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER: + wlr_event.source = WLR_TABLET_PAD_RING_SOURCE_FINGER; + break; + } + wl_signal_emit_mutable(&tablet_pad->events.ring, &wlr_event); +} + +void handle_tablet_pad_strip(struct libinput_event *event, + struct wlr_tablet_pad *tablet_pad) { + struct libinput_event_tablet_pad *pevent = + libinput_event_get_tablet_pad_event(event); + struct wlr_tablet_pad_strip_event wlr_event = { + .time_msec = usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)), + .strip = libinput_event_tablet_pad_get_strip_number(pevent), + .position = libinput_event_tablet_pad_get_strip_position(pevent), + .mode = libinput_event_tablet_pad_get_mode(pevent), + }; + switch (libinput_event_tablet_pad_get_strip_source(pevent)) { + case LIBINPUT_TABLET_PAD_STRIP_SOURCE_UNKNOWN: + wlr_event.source = WLR_TABLET_PAD_STRIP_SOURCE_UNKNOWN; + break; + case LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER: + wlr_event.source = WLR_TABLET_PAD_STRIP_SOURCE_FINGER; + break; + } + wl_signal_emit_mutable(&tablet_pad->events.strip, &wlr_event); +} diff --git a/backend/libinput/tablet_tool.c b/backend/libinput/tablet_tool.c new file mode 100644 index 0000000..cc9e609 --- /dev/null +++ b/backend/libinput/tablet_tool.c @@ -0,0 +1,277 @@ +#include +#include +#include +#include +#include +#include +#include +#include "backend/libinput.h" + +struct tablet_tool { + struct wlr_tablet_tool wlr_tool; + struct libinput_tablet_tool *handle; + struct wl_list link; // wlr_libinput_input_device.tablet_tools +}; + +const struct wlr_tablet_impl libinput_tablet_impl = { + .name = "libinput-tablet-tool", +}; + +void init_device_tablet(struct wlr_libinput_input_device *dev) { + const char *name = get_libinput_device_name(dev->handle); + struct wlr_tablet *wlr_tablet = &dev->tablet; + wlr_tablet_init(wlr_tablet, &libinput_tablet_impl, name); + +#if HAVE_LIBINPUT_BUSTYPE + if (libinput_device_get_id_bustype(dev->handle) == BUS_USB) +#endif + { + wlr_tablet->usb_vendor_id = libinput_device_get_id_vendor(dev->handle); + wlr_tablet->usb_product_id = libinput_device_get_id_product(dev->handle); + } + + libinput_device_get_size(dev->handle, &wlr_tablet->width_mm, + &wlr_tablet->height_mm); + + struct udev_device *udev = libinput_device_get_udev_device(dev->handle); + char **dst = wl_array_add(&wlr_tablet->paths, sizeof(char *)); + *dst = strdup(udev_device_get_syspath(udev)); + + wl_list_init(&dev->tablet_tools); +} + +static void tool_destroy(struct tablet_tool *tool) { + wl_signal_emit_mutable(&tool->wlr_tool.events.destroy, &tool->wlr_tool); + libinput_tablet_tool_unref(tool->handle); + libinput_tablet_tool_set_user_data(tool->handle, NULL); + wl_list_remove(&tool->link); + free(tool); +} + +void finish_device_tablet(struct wlr_libinput_input_device *dev) { + struct tablet_tool *tool, *tmp; + wl_list_for_each_safe(tool, tmp, &dev->tablet_tools, link) { + tool_destroy(tool); + } + + wlr_tablet_finish(&dev->tablet); +} + +struct wlr_libinput_input_device *device_from_tablet( + struct wlr_tablet *wlr_tablet) { + assert(wlr_tablet->impl == &libinput_tablet_impl); + + struct wlr_libinput_input_device *dev = + wl_container_of(wlr_tablet, dev, tablet); + return dev; +} + +static enum wlr_tablet_tool_type wlr_type_from_libinput_type( + enum libinput_tablet_tool_type value) { + switch (value) { + case LIBINPUT_TABLET_TOOL_TYPE_PEN: + return WLR_TABLET_TOOL_TYPE_PEN; + case LIBINPUT_TABLET_TOOL_TYPE_ERASER: + return WLR_TABLET_TOOL_TYPE_ERASER; + case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: + return WLR_TABLET_TOOL_TYPE_BRUSH; + case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: + return WLR_TABLET_TOOL_TYPE_PENCIL; + case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: + return WLR_TABLET_TOOL_TYPE_AIRBRUSH; + case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: + return WLR_TABLET_TOOL_TYPE_MOUSE; + case LIBINPUT_TABLET_TOOL_TYPE_LENS: + return WLR_TABLET_TOOL_TYPE_LENS; + case LIBINPUT_TABLET_TOOL_TYPE_TOTEM: + return WLR_TABLET_TOOL_TYPE_TOTEM; + } + abort(); // unreachable +} + +static struct tablet_tool *get_tablet_tool( + struct wlr_libinput_input_device *dev, + struct libinput_tablet_tool *libinput_tool) { + struct tablet_tool *tool = + libinput_tablet_tool_get_user_data(libinput_tool); + if (tool) { + return tool; + } + + tool = calloc(1, sizeof(*tool)); + if (tool == NULL) { + wlr_log_errno(WLR_ERROR, "failed to allocate wlr_libinput_tablet_tool"); + return NULL; + } + + tool->wlr_tool.type = wlr_type_from_libinput_type( + libinput_tablet_tool_get_type(libinput_tool)); + tool->wlr_tool.hardware_serial = + libinput_tablet_tool_get_serial(libinput_tool); + tool->wlr_tool.hardware_wacom = + libinput_tablet_tool_get_tool_id(libinput_tool); + + tool->wlr_tool.pressure = libinput_tablet_tool_has_pressure(libinput_tool); + tool->wlr_tool.distance = libinput_tablet_tool_has_distance(libinput_tool); + tool->wlr_tool.tilt = libinput_tablet_tool_has_tilt(libinput_tool); + tool->wlr_tool.rotation = libinput_tablet_tool_has_rotation(libinput_tool); + tool->wlr_tool.slider = libinput_tablet_tool_has_slider(libinput_tool); + tool->wlr_tool.wheel = libinput_tablet_tool_has_wheel(libinput_tool); + + wl_signal_init(&tool->wlr_tool.events.destroy); + + tool->handle = libinput_tablet_tool_ref(libinput_tool); + libinput_tablet_tool_set_user_data(libinput_tool, tool); + + wl_list_insert(&dev->tablet_tools, &tool->link); + return tool; +} + +void handle_tablet_tool_axis(struct libinput_event *event, + struct wlr_tablet *wlr_tablet) { + struct libinput_event_tablet_tool *tevent = + libinput_event_get_tablet_tool_event(event); + struct wlr_libinput_input_device *dev = device_from_tablet(wlr_tablet); + struct tablet_tool *tool = + get_tablet_tool(dev, libinput_event_tablet_tool_get_tool(tevent)); + + struct wlr_tablet_tool_axis_event wlr_event = { + .tablet = wlr_tablet, + .tool = &tool->wlr_tool, + .time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)), + }; + if (libinput_event_tablet_tool_x_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_X; + wlr_event.x = libinput_event_tablet_tool_get_x_transformed(tevent, 1); + wlr_event.dx = libinput_event_tablet_tool_get_dx(tevent); + } + if (libinput_event_tablet_tool_y_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_Y; + wlr_event.y = libinput_event_tablet_tool_get_y_transformed(tevent, 1); + wlr_event.dy = libinput_event_tablet_tool_get_dy(tevent); + } + if (libinput_event_tablet_tool_pressure_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_PRESSURE; + wlr_event.pressure = libinput_event_tablet_tool_get_pressure(tevent); + } + if (libinput_event_tablet_tool_distance_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_DISTANCE; + wlr_event.distance = libinput_event_tablet_tool_get_distance(tevent); + } + if (libinput_event_tablet_tool_tilt_x_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_X; + wlr_event.tilt_x = libinput_event_tablet_tool_get_tilt_x(tevent); + } + if (libinput_event_tablet_tool_tilt_y_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_Y; + wlr_event.tilt_y = libinput_event_tablet_tool_get_tilt_y(tevent); + } + if (libinput_event_tablet_tool_rotation_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_ROTATION; + wlr_event.rotation = libinput_event_tablet_tool_get_rotation(tevent); + } + if (libinput_event_tablet_tool_slider_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_SLIDER; + wlr_event.slider = libinput_event_tablet_tool_get_slider_position(tevent); + } + if (libinput_event_tablet_tool_wheel_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_WHEEL; + wlr_event.wheel_delta = libinput_event_tablet_tool_get_wheel_delta(tevent); + } + wl_signal_emit_mutable(&wlr_tablet->events.axis, &wlr_event); +} + +void handle_tablet_tool_proximity(struct libinput_event *event, + struct wlr_tablet *wlr_tablet) { + struct libinput_event_tablet_tool *tevent = + libinput_event_get_tablet_tool_event(event); + struct wlr_libinput_input_device *dev = device_from_tablet(wlr_tablet); + struct tablet_tool *tool = + get_tablet_tool(dev, libinput_event_tablet_tool_get_tool(tevent)); + + struct wlr_tablet_tool_proximity_event wlr_event = { + .tablet = wlr_tablet, + .tool = &tool->wlr_tool, + .time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)), + .x = libinput_event_tablet_tool_get_x_transformed(tevent, 1), + .y = libinput_event_tablet_tool_get_y_transformed(tevent, 1), + }; + + switch (libinput_event_tablet_tool_get_proximity_state(tevent)) { + case LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT: + wlr_event.state = WLR_TABLET_TOOL_PROXIMITY_OUT; + break; + case LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN: + wlr_event.state = WLR_TABLET_TOOL_PROXIMITY_IN; + break; + } + wl_signal_emit_mutable(&wlr_tablet->events.proximity, &wlr_event); + + if (libinput_event_tablet_tool_get_proximity_state(tevent) == + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN) { + handle_tablet_tool_axis(event, wlr_tablet); + } + + // If the tool is not unique, libinput will not find it again after the + // proximity out, so we should destroy it + if (!libinput_tablet_tool_is_unique(tool->handle) + && libinput_event_tablet_tool_get_proximity_state(tevent) == + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT) { + // The tool isn't unique, it can't be on multiple tablets + tool_destroy(tool); + } +} + +void handle_tablet_tool_tip(struct libinput_event *event, + struct wlr_tablet *wlr_tablet) { + handle_tablet_tool_axis(event, wlr_tablet); + struct libinput_event_tablet_tool *tevent = + libinput_event_get_tablet_tool_event(event); + struct wlr_libinput_input_device *dev = device_from_tablet(wlr_tablet); + struct tablet_tool *tool = + get_tablet_tool(dev, libinput_event_tablet_tool_get_tool(tevent)); + + struct wlr_tablet_tool_tip_event wlr_event = { + .tablet = wlr_tablet, + .tool = &tool->wlr_tool, + .time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)), + .x = libinput_event_tablet_tool_get_x_transformed(tevent, 1), + .y = libinput_event_tablet_tool_get_y_transformed(tevent, 1), + }; + + switch (libinput_event_tablet_tool_get_tip_state(tevent)) { + case LIBINPUT_TABLET_TOOL_TIP_UP: + wlr_event.state = WLR_TABLET_TOOL_TIP_UP; + break; + case LIBINPUT_TABLET_TOOL_TIP_DOWN: + wlr_event.state = WLR_TABLET_TOOL_TIP_DOWN; + break; + } + wl_signal_emit_mutable(&wlr_tablet->events.tip, &wlr_event); +} + +void handle_tablet_tool_button(struct libinput_event *event, + struct wlr_tablet *wlr_tablet) { + handle_tablet_tool_axis(event, wlr_tablet); + struct libinput_event_tablet_tool *tevent = + libinput_event_get_tablet_tool_event(event); + struct wlr_libinput_input_device *dev = device_from_tablet(wlr_tablet); + struct tablet_tool *tool = + get_tablet_tool(dev, libinput_event_tablet_tool_get_tool(tevent)); + + struct wlr_tablet_tool_button_event wlr_event = { + .tablet = wlr_tablet, + .tool = &tool->wlr_tool, + .time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)), + .button = libinput_event_tablet_tool_get_button(tevent), + }; + switch (libinput_event_tablet_tool_get_button_state(tevent)) { + case LIBINPUT_BUTTON_STATE_RELEASED: + wlr_event.state = WLR_BUTTON_RELEASED; + break; + case LIBINPUT_BUTTON_STATE_PRESSED: + wlr_event.state = WLR_BUTTON_PRESSED; + break; + } + wl_signal_emit_mutable(&wlr_tablet->events.button, &wlr_event); +} diff --git a/backend/libinput/touch.c b/backend/libinput/touch.c new file mode 100644 index 0000000..077cdf2 --- /dev/null +++ b/backend/libinput/touch.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include "backend/libinput.h" + +const struct wlr_touch_impl libinput_touch_impl = { + .name = "libinput-touch", +}; + +void init_device_touch(struct wlr_libinput_input_device *dev) { + const char *name = get_libinput_device_name(dev->handle); + struct wlr_touch *wlr_touch = &dev->touch; + wlr_touch_init(wlr_touch, &libinput_touch_impl, name); + + libinput_device_get_size(dev->handle, &wlr_touch->width_mm, + &wlr_touch->height_mm); +} + +struct wlr_libinput_input_device *device_from_touch( + struct wlr_touch *wlr_touch) { + assert(wlr_touch->impl == &libinput_touch_impl); + + struct wlr_libinput_input_device *dev = + wl_container_of(wlr_touch, dev, touch); + return dev; +} + +void handle_touch_down(struct libinput_event *event, + struct wlr_touch *touch) { + struct libinput_event_touch *tevent = + libinput_event_get_touch_event(event); + struct wlr_touch_down_event wlr_event = { 0 }; + wlr_event.touch = touch; + wlr_event.time_msec = + usec_to_msec(libinput_event_touch_get_time_usec(tevent)); + wlr_event.touch_id = libinput_event_touch_get_seat_slot(tevent); + wlr_event.x = libinput_event_touch_get_x_transformed(tevent, 1); + wlr_event.y = libinput_event_touch_get_y_transformed(tevent, 1); + wl_signal_emit_mutable(&touch->events.down, &wlr_event); +} + +void handle_touch_up(struct libinput_event *event, + struct wlr_touch *touch) { + struct libinput_event_touch *tevent = + libinput_event_get_touch_event(event); + struct wlr_touch_up_event wlr_event = { + .touch = touch, + .time_msec = usec_to_msec(libinput_event_touch_get_time_usec(tevent)), + .touch_id = libinput_event_touch_get_seat_slot(tevent), + }; + wl_signal_emit_mutable(&touch->events.up, &wlr_event); +} + +void handle_touch_motion(struct libinput_event *event, + struct wlr_touch *touch) { + struct libinput_event_touch *tevent = + libinput_event_get_touch_event(event); + struct wlr_touch_motion_event wlr_event = { + .touch = touch, + .time_msec = usec_to_msec(libinput_event_touch_get_time_usec(tevent)), + .touch_id = libinput_event_touch_get_seat_slot(tevent), + .x = libinput_event_touch_get_x_transformed(tevent, 1), + .y = libinput_event_touch_get_y_transformed(tevent, 1), + }; + wl_signal_emit_mutable(&touch->events.motion, &wlr_event); +} + +void handle_touch_cancel(struct libinput_event *event, + struct wlr_touch *touch) { + struct libinput_event_touch *tevent = + libinput_event_get_touch_event(event); + struct wlr_touch_cancel_event wlr_event = { + .touch = touch, + .time_msec = usec_to_msec(libinput_event_touch_get_time_usec(tevent)), + .touch_id = libinput_event_touch_get_seat_slot(tevent), + }; + wl_signal_emit_mutable(&touch->events.cancel, &wlr_event); +} + +void handle_touch_frame(struct libinput_event *event, + struct wlr_touch *touch) { + wl_signal_emit_mutable(&touch->events.frame, NULL); +} diff --git a/backend/meson.build b/backend/meson.build new file mode 100644 index 0000000..ed977d3 --- /dev/null +++ b/backend/meson.build @@ -0,0 +1,29 @@ +wlr_files += files('backend.c') + +all_backends = ['drm', 'libinput', 'x11'] +backends = get_option('backends') +if 'auto' in backends and get_option('auto_features').enabled() + backends = all_backends +elif 'auto' in backends and get_option('auto_features').disabled() + backends = [] +endif + +session_required = 'drm' in backends or 'libinput' in backends or get_option('session').enabled() +if get_option('session').disabled() + if session_required + error('Session support is required for the DRM or libinput backends') + endif + session_required = disabler() +endif + +subdir('session') + +foreach backend : all_backends + if backend in backends or 'auto' in backends + subdir(backend) + endif +endforeach + +subdir('multi') +subdir('wayland') +subdir('headless') diff --git a/backend/multi/backend.c b/backend/multi/backend.c new file mode 100644 index 0000000..740e1d6 --- /dev/null +++ b/backend/multi/backend.c @@ -0,0 +1,227 @@ +#include +#include +#include +#include +#include +#include +#include +#include "backend/backend.h" +#include "backend/multi.h" + +struct subbackend_state { + struct wlr_backend *backend; + struct wlr_backend *container; + struct wl_listener new_input; + struct wl_listener new_output; + struct wl_listener destroy; + struct wl_list link; +}; + +static struct wlr_multi_backend *multi_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_multi(wlr_backend)); + struct wlr_multi_backend *backend = wl_container_of(wlr_backend, backend, backend); + return backend; +} + +static bool multi_backend_start(struct wlr_backend *wlr_backend) { + struct wlr_multi_backend *backend = multi_backend_from_backend(wlr_backend); + struct subbackend_state *sub; + wl_list_for_each(sub, &backend->backends, link) { + if (!wlr_backend_start(sub->backend)) { + wlr_log(WLR_ERROR, "Failed to initialize backend."); + return false; + } + } + return true; +} + +static void subbackend_state_destroy(struct subbackend_state *sub) { + wl_list_remove(&sub->new_input.link); + wl_list_remove(&sub->new_output.link); + wl_list_remove(&sub->destroy.link); + wl_list_remove(&sub->link); + free(sub); +} + +static void multi_backend_destroy(struct wlr_backend *wlr_backend) { + struct wlr_multi_backend *backend = multi_backend_from_backend(wlr_backend); + + wl_list_remove(&backend->event_loop_destroy.link); + + wlr_backend_finish(wlr_backend); + + // Some backends may depend on other backends, ie. destroying a backend may + // also destroy other backends + while (!wl_list_empty(&backend->backends)) { + struct subbackend_state *sub = + wl_container_of(backend->backends.next, sub, link); + wlr_backend_destroy(sub->backend); + } + + free(backend); +} + +static int multi_backend_get_drm_fd(struct wlr_backend *backend) { + struct wlr_multi_backend *multi = multi_backend_from_backend(backend); + + struct subbackend_state *sub; + wl_list_for_each(sub, &multi->backends, link) { + if (sub->backend->impl->get_drm_fd) { + return wlr_backend_get_drm_fd(sub->backend); + } + } + + return -1; +} + +static uint32_t multi_backend_get_buffer_caps(struct wlr_backend *backend) { + struct wlr_multi_backend *multi = multi_backend_from_backend(backend); + + if (wl_list_empty(&multi->backends)) { + return 0; + } + + uint32_t caps = WLR_BUFFER_CAP_DATA_PTR | WLR_BUFFER_CAP_DMABUF + | WLR_BUFFER_CAP_SHM; + + struct subbackend_state *sub; + wl_list_for_each(sub, &multi->backends, link) { + uint32_t backend_caps = backend_get_buffer_caps(sub->backend); + if (backend_caps != 0) { + // only count backend capable of presenting a buffer + caps = caps & backend_caps; + } + } + + return caps; +} + +static const struct wlr_backend_impl backend_impl = { + .start = multi_backend_start, + .destroy = multi_backend_destroy, + .get_drm_fd = multi_backend_get_drm_fd, + .get_buffer_caps = multi_backend_get_buffer_caps, +}; + +static void handle_event_loop_destroy(struct wl_listener *listener, void *data) { + struct wlr_multi_backend *backend = + wl_container_of(listener, backend, event_loop_destroy); + multi_backend_destroy((struct wlr_backend*)backend); +} + +struct wlr_backend *wlr_multi_backend_create(struct wl_event_loop *loop) { + struct wlr_multi_backend *backend = calloc(1, sizeof(*backend)); + if (!backend) { + wlr_log(WLR_ERROR, "Backend allocation failed"); + return NULL; + } + + wl_list_init(&backend->backends); + wlr_backend_init(&backend->backend, &backend_impl); + + wl_signal_init(&backend->events.backend_add); + wl_signal_init(&backend->events.backend_remove); + + backend->event_loop_destroy.notify = handle_event_loop_destroy; + wl_event_loop_add_destroy_listener(loop, &backend->event_loop_destroy); + + return &backend->backend; +} + +bool wlr_backend_is_multi(struct wlr_backend *b) { + return b->impl == &backend_impl; +} + +static void new_input_reemit(struct wl_listener *listener, void *data) { + struct subbackend_state *state = wl_container_of(listener, + state, new_input); + wl_signal_emit_mutable(&state->container->events.new_input, data); +} + +static void new_output_reemit(struct wl_listener *listener, void *data) { + struct subbackend_state *state = wl_container_of(listener, + state, new_output); + wl_signal_emit_mutable(&state->container->events.new_output, data); +} + +static void handle_subbackend_destroy(struct wl_listener *listener, + void *data) { + struct subbackend_state *state = wl_container_of(listener, state, destroy); + subbackend_state_destroy(state); +} + +static struct subbackend_state *multi_backend_get_subbackend(struct wlr_multi_backend *multi, + struct wlr_backend *backend) { + struct subbackend_state *sub = NULL; + wl_list_for_each(sub, &multi->backends, link) { + if (sub->backend == backend) { + return sub; + } + } + return NULL; +} + +bool wlr_multi_backend_add(struct wlr_backend *_multi, + struct wlr_backend *backend) { + assert(_multi && backend); + assert(_multi != backend); + + struct wlr_multi_backend *multi = multi_backend_from_backend(_multi); + + if (multi_backend_get_subbackend(multi, backend)) { + // already added + return true; + } + + struct subbackend_state *sub = calloc(1, sizeof(*sub)); + if (sub == NULL) { + wlr_log(WLR_ERROR, "Could not add backend: allocation failed"); + return false; + } + wl_list_insert(multi->backends.prev, &sub->link); + + sub->backend = backend; + sub->container = &multi->backend; + + wl_signal_add(&backend->events.destroy, &sub->destroy); + sub->destroy.notify = handle_subbackend_destroy; + + wl_signal_add(&backend->events.new_input, &sub->new_input); + sub->new_input.notify = new_input_reemit; + + wl_signal_add(&backend->events.new_output, &sub->new_output); + sub->new_output.notify = new_output_reemit; + + wl_signal_emit_mutable(&multi->events.backend_add, backend); + return true; +} + +void wlr_multi_backend_remove(struct wlr_backend *_multi, + struct wlr_backend *backend) { + struct wlr_multi_backend *multi = multi_backend_from_backend(_multi); + + struct subbackend_state *sub = + multi_backend_get_subbackend(multi, backend); + + if (sub) { + wl_signal_emit_mutable(&multi->events.backend_remove, backend); + subbackend_state_destroy(sub); + } +} + +bool wlr_multi_is_empty(struct wlr_backend *_backend) { + assert(wlr_backend_is_multi(_backend)); + struct wlr_multi_backend *backend = (struct wlr_multi_backend *)_backend; + return wl_list_length(&backend->backends) < 1; +} + +void wlr_multi_for_each_backend(struct wlr_backend *_backend, + void (*callback)(struct wlr_backend *backend, void *data), void *data) { + assert(wlr_backend_is_multi(_backend)); + struct wlr_multi_backend *backend = (struct wlr_multi_backend *)_backend; + struct subbackend_state *sub; + wl_list_for_each(sub, &backend->backends, link) { + callback(sub->backend, data); + } +} diff --git a/backend/multi/meson.build b/backend/multi/meson.build new file mode 100644 index 0000000..be4abfb --- /dev/null +++ b/backend/multi/meson.build @@ -0,0 +1 @@ +wlr_files += files('backend.c') diff --git a/backend/session/meson.build b/backend/session/meson.build new file mode 100644 index 0000000..4c20ee9 --- /dev/null +++ b/backend/session/meson.build @@ -0,0 +1,17 @@ +msg = 'Required for session support.' +udev = dependency('libudev', required: session_required, not_found_message: msg) +libseat = dependency( + 'libseat', + version: '>=0.2.0', + fallback: 'seatd', + default_options: ['server=disabled', 'man-pages=disabled', 'examples=disabled'], + required: session_required, + not_found_message: msg, +) +if not (udev.found() and libseat.found()) + subdir_done() +endif + +wlr_files += files('session.c') +wlr_deps += [udev, libseat] +features += { 'session': true } diff --git a/backend/session/session.c b/backend/session/session.c new file mode 100644 index 0000000..5fb20c2 --- /dev/null +++ b/backend/session/session.c @@ -0,0 +1,551 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/session/session.h" +#include "util/time.h" + +#include + +#define WAIT_GPU_TIMEOUT 10000 // ms + +static void handle_enable_seat(struct libseat *seat, void *data) { + struct wlr_session *session = data; + session->active = true; + wl_signal_emit_mutable(&session->events.active, NULL); +} + +static void handle_disable_seat(struct libseat *seat, void *data) { + struct wlr_session *session = data; + session->active = false; + wl_signal_emit_mutable(&session->events.active, NULL); + libseat_disable_seat(session->seat_handle); +} + +static int libseat_event(int fd, uint32_t mask, void *data) { + struct wlr_session *session = data; + if (libseat_dispatch(session->seat_handle, 0) == -1) { + wlr_log_errno(WLR_ERROR, "Failed to dispatch libseat"); + wlr_session_destroy(session); + } + return 1; +} + +static struct libseat_seat_listener seat_listener = { + .enable_seat = handle_enable_seat, + .disable_seat = handle_disable_seat, +}; + +static enum wlr_log_importance libseat_log_level_to_wlr( + enum libseat_log_level level) { + switch (level) { + case LIBSEAT_LOG_LEVEL_ERROR: + return WLR_ERROR; + case LIBSEAT_LOG_LEVEL_INFO: + return WLR_INFO; + default: + return WLR_DEBUG; + } +} + +static void log_libseat(enum libseat_log_level level, + const char *fmt, va_list args) { + enum wlr_log_importance importance = libseat_log_level_to_wlr(level); + + static char wlr_fmt[1024]; + snprintf(wlr_fmt, sizeof(wlr_fmt), "[libseat] %s", fmt); + + _wlr_vlog(importance, wlr_fmt, args); +} + +static int libseat_session_init(struct wlr_session *session, + struct wl_event_loop *event_loop) { + libseat_set_log_handler(log_libseat); + libseat_set_log_level(LIBSEAT_LOG_LEVEL_INFO); + + // libseat will take care of updating the logind state if necessary + setenv("XDG_SESSION_TYPE", "wayland", 1); + + session->seat_handle = libseat_open_seat(&seat_listener, session); + if (session->seat_handle == NULL) { + wlr_log_errno(WLR_ERROR, "Unable to create seat"); + return -1; + } + + const char *seat_name = libseat_seat_name(session->seat_handle); + if (seat_name == NULL) { + wlr_log_errno(WLR_ERROR, "Unable to get seat info"); + goto error; + } + snprintf(session->seat, sizeof(session->seat), "%s", seat_name); + + session->libseat_event = wl_event_loop_add_fd(event_loop, libseat_get_fd(session->seat_handle), + WL_EVENT_READABLE, libseat_event, session); + if (session->libseat_event == NULL) { + wlr_log(WLR_ERROR, "Failed to create libseat event source"); + goto error; + } + + // We may have received enable_seat immediately after the open_seat result, + // so, dispatch once without timeout to speed up activation. + if (libseat_dispatch(session->seat_handle, 0) == -1) { + wlr_log_errno(WLR_ERROR, "libseat dispatch failed"); + goto error_dispatch; + } + + wlr_log(WLR_INFO, "Successfully loaded libseat session"); + return 0; + +error_dispatch: + wl_event_source_remove(session->libseat_event); + session->libseat_event = NULL; +error: + libseat_close_seat(session->seat_handle); + session->seat_handle = NULL; + return -1; +} + +static void libseat_session_finish(struct wlr_session *session) { + libseat_close_seat(session->seat_handle); + wl_event_source_remove(session->libseat_event); + session->seat_handle = NULL; + session->libseat_event = NULL; +} + +static bool is_drm_card(const char *sysname) { + const char prefix[] = DRM_PRIMARY_MINOR_NAME; + if (strncmp(sysname, prefix, strlen(prefix)) != 0) { + return false; + } + for (size_t i = strlen(prefix); sysname[i] != '\0'; i++) { + if (sysname[i] < '0' || sysname[i] > '9') { + return false; + } + } + return true; +} + +static void read_udev_change_event(struct wlr_device_change_event *event, + struct udev_device *udev_dev) { + const char *hotplug = udev_device_get_property_value(udev_dev, "HOTPLUG"); + if (hotplug != NULL && strcmp(hotplug, "1") == 0) { + event->type = WLR_DEVICE_HOTPLUG; + struct wlr_device_hotplug_event *hotplug = &event->hotplug; + + const char *connector = + udev_device_get_property_value(udev_dev, "CONNECTOR"); + if (connector != NULL) { + hotplug->connector_id = strtoul(connector, NULL, 10); + } + + const char *prop = + udev_device_get_property_value(udev_dev, "PROPERTY"); + if (prop != NULL) { + hotplug->prop_id = strtoul(prop, NULL, 10); + } + + return; + } + + const char *lease = udev_device_get_property_value(udev_dev, "LEASE"); + if (lease != NULL && strcmp(lease, "1") == 0) { + event->type = WLR_DEVICE_LEASE; + return; + } +} + +static int handle_udev_event(int fd, uint32_t mask, void *data) { + struct wlr_session *session = data; + + struct udev_device *udev_dev = udev_monitor_receive_device(session->mon); + if (!udev_dev) { + return 1; + } + + const char *sysname = udev_device_get_sysname(udev_dev); + const char *devnode = udev_device_get_devnode(udev_dev); + const char *action = udev_device_get_action(udev_dev); + wlr_log(WLR_DEBUG, "udev event for %s (%s)", sysname, action); + + if (!is_drm_card(sysname) || !action || !devnode) { + goto out; + } + + const char *seat = udev_device_get_property_value(udev_dev, "ID_SEAT"); + if (!seat) { + seat = "seat0"; + } + if (session->seat[0] != '\0' && strcmp(session->seat, seat) != 0) { + goto out; + } + + if (strcmp(action, "add") == 0) { + wlr_log(WLR_DEBUG, "DRM device %s added", sysname); + struct wlr_session_add_event event = { + .path = devnode, + }; + wl_signal_emit_mutable(&session->events.add_drm_card, &event); + } else if (strcmp(action, "change") == 0 || strcmp(action, "remove") == 0) { + dev_t devnum = udev_device_get_devnum(udev_dev); + struct wlr_device *dev; + wl_list_for_each(dev, &session->devices, link) { + if (dev->dev != devnum) { + continue; + } + + if (strcmp(action, "change") == 0) { + wlr_log(WLR_DEBUG, "DRM device %s changed", sysname); + struct wlr_device_change_event event = {0}; + read_udev_change_event(&event, udev_dev); + wl_signal_emit_mutable(&dev->events.change, &event); + } else if (strcmp(action, "remove") == 0) { + wlr_log(WLR_DEBUG, "DRM device %s removed", sysname); + wl_signal_emit_mutable(&dev->events.remove, NULL); + } else { + assert(0); + } + break; + } + } + +out: + udev_device_unref(udev_dev); + return 1; +} + +static void handle_event_loop_destroy(struct wl_listener *listener, void *data) { + struct wlr_session *session = + wl_container_of(listener, session, event_loop_destroy); + wlr_session_destroy(session); +} + +struct wlr_session *wlr_session_create(struct wl_event_loop *event_loop) { + struct wlr_session *session = calloc(1, sizeof(*session)); + if (!session) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + session->event_loop = event_loop; + wl_signal_init(&session->events.active); + wl_signal_init(&session->events.add_drm_card); + wl_signal_init(&session->events.destroy); + wl_list_init(&session->devices); + + if (libseat_session_init(session, event_loop) == -1) { + wlr_log(WLR_ERROR, "Failed to load session backend"); + goto error_open; + } + + session->udev = udev_new(); + if (!session->udev) { + wlr_log_errno(WLR_ERROR, "Failed to create udev context"); + goto error_session; + } + + session->mon = udev_monitor_new_from_netlink(session->udev, "udev"); + if (!session->mon) { + wlr_log_errno(WLR_ERROR, "Failed to create udev monitor"); + goto error_udev; + } + + udev_monitor_filter_add_match_subsystem_devtype(session->mon, "drm", NULL); + udev_monitor_enable_receiving(session->mon); + + int fd = udev_monitor_get_fd(session->mon); + + session->udev_event = wl_event_loop_add_fd(event_loop, fd, + WL_EVENT_READABLE, handle_udev_event, session); + if (!session->udev_event) { + wlr_log_errno(WLR_ERROR, "Failed to create udev event source"); + goto error_mon; + } + + session->event_loop_destroy.notify = handle_event_loop_destroy; + wl_event_loop_add_destroy_listener(event_loop, &session->event_loop_destroy); + + return session; + +error_mon: + udev_monitor_unref(session->mon); +error_udev: + udev_unref(session->udev); +error_session: + libseat_session_finish(session); +error_open: + free(session); + return NULL; +} + +void wlr_session_destroy(struct wlr_session *session) { + if (!session) { + return; + } + + wl_signal_emit_mutable(&session->events.destroy, session); + wl_list_remove(&session->event_loop_destroy.link); + + wl_event_source_remove(session->udev_event); + udev_monitor_unref(session->mon); + udev_unref(session->udev); + + struct wlr_device *dev, *tmp_dev; + wl_list_for_each_safe(dev, tmp_dev, &session->devices, link) { + wlr_session_close_file(session, dev); + } + + libseat_session_finish(session); + free(session); +} + +struct wlr_device *wlr_session_open_file(struct wlr_session *session, + const char *path) { + int fd; + int device_id = libseat_open_device(session->seat_handle, path, &fd); + if (device_id == -1) { + wlr_log_errno(WLR_ERROR, "Failed to open device: '%s'", path); + return NULL; + } + + struct wlr_device *dev = malloc(sizeof(*dev)); + if (!dev) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error; + } + + struct stat st; + if (fstat(fd, &st) < 0) { + wlr_log_errno(WLR_ERROR, "Stat failed"); + goto error; + } + + dev->fd = fd; + dev->dev = st.st_rdev; + dev->device_id = device_id; + wl_signal_init(&dev->events.change); + wl_signal_init(&dev->events.remove); + wl_list_insert(&session->devices, &dev->link); + + return dev; + +error: + libseat_close_device(session->seat_handle, device_id); + free(dev); + close(fd); + return NULL; +} + +void wlr_session_close_file(struct wlr_session *session, + struct wlr_device *dev) { + if (libseat_close_device(session->seat_handle, dev->device_id) == -1) { + wlr_log_errno(WLR_ERROR, "Failed to close device %d", dev->device_id); + } + close(dev->fd); + wl_list_remove(&dev->link); + free(dev); +} + +bool wlr_session_change_vt(struct wlr_session *session, unsigned vt) { + if (!session) { + return false; + } + return libseat_switch_session(session->seat_handle, vt) == 0; +} + +/* Tests if 'path' is KMS compatible by trying to open it. Returns the opened + * device on success. */ +struct wlr_device *session_open_if_kms(struct wlr_session *restrict session, + const char *restrict path) { + if (!path) { + return NULL; + } + + struct wlr_device *dev = wlr_session_open_file(session, path); + if (!dev) { + return NULL; + } + + if (!drmIsKMS(dev->fd)) { + wlr_log(WLR_DEBUG, "Ignoring '%s': not a KMS device", path); + wlr_session_close_file(session, dev); + return NULL; + } + + return dev; +} + +static ssize_t explicit_find_gpus(struct wlr_session *session, + size_t ret_len, struct wlr_device *ret[static ret_len], const char *str) { + char *gpus = strdup(str); + if (!gpus) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return -1; + } + + size_t i = 0; + char *save; + char *ptr = strtok_r(gpus, ":", &save); + do { + if (i >= ret_len) { + break; + } + + ret[i] = session_open_if_kms(session, ptr); + if (!ret[i]) { + wlr_log(WLR_ERROR, "Unable to open %s as DRM device", ptr); + } else { + ++i; + } + } while ((ptr = strtok_r(NULL, ":", &save))); + + free(gpus); + return i; +} + +static struct udev_enumerate *enumerate_drm_cards(struct udev *udev) { + struct udev_enumerate *en = udev_enumerate_new(udev); + if (!en) { + wlr_log(WLR_ERROR, "udev_enumerate_new failed"); + return NULL; + } + + udev_enumerate_add_match_subsystem(en, "drm"); + udev_enumerate_add_match_sysname(en, DRM_PRIMARY_MINOR_NAME "[0-9]*"); + + if (udev_enumerate_scan_devices(en) != 0) { + wlr_log(WLR_ERROR, "udev_enumerate_scan_devices failed"); + udev_enumerate_unref(en); + return NULL; + } + + return en; +} + +struct find_gpus_add_handler { + bool added; + struct wl_listener listener; +}; + +static void find_gpus_handle_add(struct wl_listener *listener, void *data) { + struct find_gpus_add_handler *handler = + wl_container_of(listener, handler, listener); + handler->added = true; +} + +ssize_t wlr_session_find_gpus(struct wlr_session *session, + size_t ret_len, struct wlr_device **ret) { + const char *explicit = getenv("WLR_DRM_DEVICES"); + if (explicit) { + return explicit_find_gpus(session, ret_len, ret, explicit); + } + + struct udev_enumerate *en = enumerate_drm_cards(session->udev); + if (!en) { + return -1; + } + + if (udev_enumerate_get_list_entry(en) == NULL) { + udev_enumerate_unref(en); + wlr_log(WLR_INFO, "Waiting for a DRM card device"); + + struct find_gpus_add_handler handler = {0}; + handler.listener.notify = find_gpus_handle_add; + wl_signal_add(&session->events.add_drm_card, &handler.listener); + + int64_t started_at = get_current_time_msec(); + int64_t timeout = WAIT_GPU_TIMEOUT; + while (!handler.added) { + int ret = wl_event_loop_dispatch(session->event_loop, (int)timeout); + if (ret < 0) { + wlr_log_errno(WLR_ERROR, "Failed to wait for DRM card device: " + "wl_event_loop_dispatch failed"); + udev_enumerate_unref(en); + return -1; + } + + int64_t now = get_current_time_msec(); + if (now >= started_at + WAIT_GPU_TIMEOUT) { + break; + } + timeout = started_at + WAIT_GPU_TIMEOUT - now; + } + + wl_list_remove(&handler.listener.link); + + en = enumerate_drm_cards(session->udev); + if (!en) { + return -1; + } + } + + struct udev_list_entry *entry; + size_t i = 0; + + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(en)) { + if (i == ret_len) { + break; + } + + bool is_boot_vga = false; + + const char *path = udev_list_entry_get_name(entry); + struct udev_device *dev = udev_device_new_from_syspath(session->udev, path); + if (!dev) { + continue; + } + + const char *seat = udev_device_get_property_value(dev, "ID_SEAT"); + if (!seat) { + seat = "seat0"; + } + if (session->seat[0] && strcmp(session->seat, seat) != 0) { + udev_device_unref(dev); + continue; + } + + // This is owned by 'dev', so we don't need to free it + struct udev_device *pci = + udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); + + if (pci) { + const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); + if (id && strcmp(id, "1") == 0) { + is_boot_vga = true; + } + } + + struct wlr_device *wlr_dev = + session_open_if_kms(session, udev_device_get_devnode(dev)); + if (!wlr_dev) { + udev_device_unref(dev); + continue; + } + + udev_device_unref(dev); + + ret[i] = wlr_dev; + if (is_boot_vga) { + struct wlr_device *tmp = ret[0]; + ret[0] = ret[i]; + ret[i] = tmp; + } + + ++i; + } + + udev_enumerate_unref(en); + + return i; +} diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c new file mode 100644 index 0000000..9522de4 --- /dev/null +++ b/backend/wayland/backend.c @@ -0,0 +1,701 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "backend/wayland.h" +#include "render/drm_format_set.h" +#include "render/pixel_format.h" + +#include "drm-client-protocol.h" +#include "linux-dmabuf-v1-client-protocol.h" +#include "pointer-gestures-unstable-v1-client-protocol.h" +#include "presentation-time-client-protocol.h" +#include "xdg-activation-v1-client-protocol.h" +#include "xdg-decoration-unstable-v1-client-protocol.h" +#include "xdg-shell-client-protocol.h" +#include "tablet-unstable-v2-client-protocol.h" +#include "relative-pointer-unstable-v1-client-protocol.h" +#include "viewporter-client-protocol.h" + +struct wlr_wl_linux_dmabuf_feedback_v1 { + struct wlr_wl_backend *backend; + dev_t main_device_id; + struct wlr_wl_linux_dmabuf_v1_table_entry *format_table; + size_t format_table_size; + + dev_t tranche_target_device_id; +}; + +struct wlr_wl_linux_dmabuf_v1_table_entry { + uint32_t format; + uint32_t pad; /* unused */ + uint64_t modifier; +}; + +struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_wl(wlr_backend)); + struct wlr_wl_backend *backend = wl_container_of(wlr_backend, backend, backend); + return backend; +} + +static int dispatch_events(int fd, uint32_t mask, void *data) { + struct wlr_wl_backend *wl = data; + + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { + if (mask & WL_EVENT_ERROR) { + wlr_log(WLR_ERROR, "Failed to read from remote Wayland display"); + } + wlr_backend_destroy(&wl->backend); + return 0; + } + + int count = 0; + if (mask & WL_EVENT_READABLE) { + count = wl_display_dispatch(wl->remote_display); + } + if (mask & WL_EVENT_WRITABLE) { + wl_display_flush(wl->remote_display); + } + if (mask == 0) { + count = wl_display_dispatch_pending(wl->remote_display); + wl_display_flush(wl->remote_display); + } + + if (count < 0) { + wlr_log(WLR_ERROR, "Failed to dispatch remote Wayland display"); + wlr_backend_destroy(&wl->backend); + return 0; + } + return count; +} + +static void xdg_wm_base_handle_ping(void *data, + struct xdg_wm_base *base, uint32_t serial) { + xdg_wm_base_pong(base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + xdg_wm_base_handle_ping, +}; + +static void presentation_handle_clock_id(void *data, + struct wp_presentation *presentation, uint32_t clock) { + struct wlr_wl_backend *wl = data; + + if (clock != CLOCK_MONOTONIC) { + wp_presentation_destroy(wl->presentation); + wl->presentation = NULL; + } +} + +static const struct wp_presentation_listener presentation_listener = { + .clock_id = presentation_handle_clock_id, +}; + +static void linux_dmabuf_v1_handle_format(void *data, + struct zwp_linux_dmabuf_v1 *linux_dmabuf_v1, uint32_t format) { + // Note, this event is deprecated + struct wlr_wl_backend *wl = data; + + wlr_drm_format_set_add(&wl->linux_dmabuf_v1_formats, format, + DRM_FORMAT_MOD_INVALID); +} + +static void linux_dmabuf_v1_handle_modifier(void *data, + struct zwp_linux_dmabuf_v1 *linux_dmabuf_v1, uint32_t format, + uint32_t modifier_hi, uint32_t modifier_lo) { + struct wlr_wl_backend *wl = data; + + uint64_t modifier = ((uint64_t)modifier_hi << 32) | modifier_lo; + wlr_drm_format_set_add(&wl->linux_dmabuf_v1_formats, format, modifier); +} + +static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_v1_listener = { + .format = linux_dmabuf_v1_handle_format, + .modifier = linux_dmabuf_v1_handle_modifier, +}; + +static void linux_dmabuf_feedback_v1_handle_done(void *data, + struct zwp_linux_dmabuf_feedback_v1 *feedback) { + // This space is intentionally left blank +} + +static void linux_dmabuf_feedback_v1_handle_format_table(void *data, + struct zwp_linux_dmabuf_feedback_v1 *feedback, int fd, uint32_t size) { + struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data; + + feedback_data->format_table = NULL; + + void *table_data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (table_data == MAP_FAILED) { + wlr_log_errno(WLR_ERROR, "failed to mmap DMA-BUF format table"); + } else { + feedback_data->format_table = table_data; + feedback_data->format_table_size = size; + } + close(fd); +} + +static void linux_dmabuf_feedback_v1_handle_main_device(void *data, + struct zwp_linux_dmabuf_feedback_v1 *feedback, + struct wl_array *dev_id_arr) { + struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data; + + dev_t dev_id; + assert(dev_id_arr->size == sizeof(dev_id)); + memcpy(&dev_id, dev_id_arr->data, sizeof(dev_id)); + + feedback_data->main_device_id = dev_id; + + drmDevice *device = NULL; + if (drmGetDeviceFromDevId(dev_id, 0, &device) != 0) { + wlr_log_errno(WLR_ERROR, "drmGetDeviceFromDevId failed"); + return; + } + + const char *name = NULL; + if (device->available_nodes & (1 << DRM_NODE_RENDER)) { + name = device->nodes[DRM_NODE_RENDER]; + } else { + // Likely a split display/render setup. Pick the primary node and hope + // Mesa will open the right render node under-the-hood. + assert(device->available_nodes & (1 << DRM_NODE_PRIMARY)); + name = device->nodes[DRM_NODE_PRIMARY]; + wlr_log(WLR_DEBUG, "DRM device %s has no render node, " + "falling back to primary node", name); + } + + feedback_data->backend->drm_render_name = strdup(name); + + drmFreeDevice(&device); +} + +static void linux_dmabuf_feedback_v1_handle_tranche_done(void *data, + struct zwp_linux_dmabuf_feedback_v1 *feedback) { + struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data; + feedback_data->tranche_target_device_id = 0; +} + +static void linux_dmabuf_feedback_v1_handle_tranche_target_device(void *data, + struct zwp_linux_dmabuf_feedback_v1 *feedback, + struct wl_array *dev_id_arr) { + struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data; + + dev_t dev_id; + assert(dev_id_arr->size == sizeof(dev_id)); + memcpy(&dev_id, dev_id_arr->data, sizeof(dev_id)); + + feedback_data->tranche_target_device_id = dev_id; +} + +static void linux_dmabuf_feedback_v1_handle_tranche_formats(void *data, + struct zwp_linux_dmabuf_feedback_v1 *feedback, + struct wl_array *indices_arr) { + struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data; + + if (feedback_data->format_table == NULL) { + return; + } + if (feedback_data->tranche_target_device_id != feedback_data->main_device_id) { + return; + } + + size_t table_cap = feedback_data->format_table_size / + sizeof(struct wlr_wl_linux_dmabuf_v1_table_entry); + uint16_t *index_ptr; + wl_array_for_each(index_ptr, indices_arr) { + assert(*index_ptr < table_cap); + const struct wlr_wl_linux_dmabuf_v1_table_entry *entry = + &feedback_data->format_table[*index_ptr]; + wlr_drm_format_set_add(&feedback_data->backend->linux_dmabuf_v1_formats, + entry->format, entry->modifier); + } +} + +static void linux_dmabuf_feedback_v1_handle_tranche_flags(void *data, + struct zwp_linux_dmabuf_feedback_v1 *feedback, uint32_t flags) { + // TODO: handle SCANOUT flag +} + +static const struct zwp_linux_dmabuf_feedback_v1_listener + linux_dmabuf_feedback_v1_listener = { + .done = linux_dmabuf_feedback_v1_handle_done, + .format_table = linux_dmabuf_feedback_v1_handle_format_table, + .main_device = linux_dmabuf_feedback_v1_handle_main_device, + .tranche_done = linux_dmabuf_feedback_v1_handle_tranche_done, + .tranche_target_device = linux_dmabuf_feedback_v1_handle_tranche_target_device, + .tranche_formats = linux_dmabuf_feedback_v1_handle_tranche_formats, + .tranche_flags = linux_dmabuf_feedback_v1_handle_tranche_flags, +}; + +static bool device_has_name(const drmDevice *device, const char *name) { + for (size_t i = 0; i < DRM_NODE_MAX; i++) { + if (!(device->available_nodes & (1 << i))) { + continue; + } + if (strcmp(device->nodes[i], name) == 0) { + return true; + } + } + return false; +} + +static char *get_render_name(const char *name) { + uint32_t flags = 0; + int devices_len = drmGetDevices2(flags, NULL, 0); + if (devices_len < 0) { + wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len)); + return NULL; + } + drmDevice **devices = calloc(devices_len, sizeof(*devices)); + if (devices == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + devices_len = drmGetDevices2(flags, devices, devices_len); + if (devices_len < 0) { + free(devices); + wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len)); + return NULL; + } + + const drmDevice *match = NULL; + for (int i = 0; i < devices_len; i++) { + if (device_has_name(devices[i], name)) { + match = devices[i]; + break; + } + } + + char *render_name = NULL; + if (match == NULL) { + wlr_log(WLR_ERROR, "Cannot find DRM device %s", name); + } else if (!(match->available_nodes & (1 << DRM_NODE_RENDER))) { + // Likely a split display/render setup. Pick the primary node and hope + // Mesa will open the right render node under-the-hood. + wlr_log(WLR_DEBUG, "DRM device %s has no render node, " + "falling back to primary node", name); + assert(match->available_nodes & (1 << DRM_NODE_PRIMARY)); + render_name = strdup(match->nodes[DRM_NODE_PRIMARY]); + } else { + render_name = strdup(match->nodes[DRM_NODE_RENDER]); + } + + for (int i = 0; i < devices_len; i++) { + drmFreeDevice(&devices[i]); + } + free(devices); + + return render_name; +} + +static void legacy_drm_handle_device(void *data, struct wl_drm *drm, + const char *name) { + struct wlr_wl_backend *wl = data; + wl->drm_render_name = get_render_name(name); +} + +static void legacy_drm_handle_format(void *data, struct wl_drm *drm, + uint32_t format) { + // This space is intentionally left blank +} + +static void legacy_drm_handle_authenticated(void *data, struct wl_drm *drm) { + // This space is intentionally left blank +} + +static void legacy_drm_handle_capabilities(void *data, struct wl_drm *drm, + uint32_t caps) { + // This space is intentionally left blank +} + +static const struct wl_drm_listener legacy_drm_listener = { + .device = legacy_drm_handle_device, + .format = legacy_drm_handle_format, + .authenticated = legacy_drm_handle_authenticated, + .capabilities = legacy_drm_handle_capabilities, +}; + +static void shm_handle_format(void *data, struct wl_shm *shm, + uint32_t shm_format) { + struct wlr_wl_backend *wl = data; + uint32_t drm_format = convert_wl_shm_format_to_drm(shm_format); + wlr_drm_format_set_add(&wl->shm_formats, drm_format, DRM_FORMAT_MOD_INVALID); +} + +static const struct wl_shm_listener shm_listener = { + .format = shm_handle_format, +}; + +static void registry_global(void *data, struct wl_registry *registry, + uint32_t name, const char *iface, uint32_t version) { + struct wlr_wl_backend *wl = data; + + wlr_log(WLR_DEBUG, "Remote wayland global: %s v%" PRIu32, iface, version); + + if (strcmp(iface, wl_compositor_interface.name) == 0) { + wl->compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, 4); + } else if (strcmp(iface, wl_seat_interface.name) == 0) { + uint32_t target_version = version; + if (version < 5) { + target_version = 5; + } + if (version > 9) { + target_version = 9; + } + struct wl_seat *wl_seat = wl_registry_bind(registry, name, + &wl_seat_interface, target_version); + if (!create_wl_seat(wl_seat, wl, name)) { + wl_seat_destroy(wl_seat); + } + } else if (strcmp(iface, xdg_wm_base_interface.name) == 0) { + wl->xdg_wm_base = wl_registry_bind(registry, name, + &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(wl->xdg_wm_base, &xdg_wm_base_listener, NULL); + } else if (strcmp(iface, zxdg_decoration_manager_v1_interface.name) == 0) { + wl->zxdg_decoration_manager_v1 = wl_registry_bind(registry, name, + &zxdg_decoration_manager_v1_interface, 1); + } else if (strcmp(iface, zwp_pointer_gestures_v1_interface.name) == 0) { + wl->zwp_pointer_gestures_v1 = wl_registry_bind(registry, name, + &zwp_pointer_gestures_v1_interface, version < 3 ? version : 3); + } else if (strcmp(iface, wp_presentation_interface.name) == 0) { + wl->presentation = wl_registry_bind(registry, name, + &wp_presentation_interface, 1); + wp_presentation_add_listener(wl->presentation, + &presentation_listener, wl); + } else if (strcmp(iface, zwp_tablet_manager_v2_interface.name) == 0) { + wl->tablet_manager = wl_registry_bind(registry, name, + &zwp_tablet_manager_v2_interface, 1); + } else if (strcmp(iface, zwp_linux_dmabuf_v1_interface.name) == 0 && + version >= 3) { + wl->zwp_linux_dmabuf_v1 = wl_registry_bind(registry, name, + &zwp_linux_dmabuf_v1_interface, version >= 4 ? 4 : version); + zwp_linux_dmabuf_v1_add_listener(wl->zwp_linux_dmabuf_v1, + &linux_dmabuf_v1_listener, wl); + } else if (strcmp(iface, zwp_relative_pointer_manager_v1_interface.name) == 0) { + wl->zwp_relative_pointer_manager_v1 = wl_registry_bind(registry, name, + &zwp_relative_pointer_manager_v1_interface, 1); + } else if (strcmp(iface, wl_drm_interface.name) == 0) { + wl->legacy_drm = wl_registry_bind(registry, name, &wl_drm_interface, 1); + wl_drm_add_listener(wl->legacy_drm, &legacy_drm_listener, wl); + } else if (strcmp(iface, wl_shm_interface.name) == 0) { + wl->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + wl_shm_add_listener(wl->shm, &shm_listener, wl); + } else if (strcmp(iface, xdg_activation_v1_interface.name) == 0) { + wl->activation_v1 = wl_registry_bind(registry, name, + &xdg_activation_v1_interface, 1); + } else if (strcmp(iface, wl_subcompositor_interface.name) == 0) { + wl->subcompositor = wl_registry_bind(registry, name, + &wl_subcompositor_interface, 1); + } else if (strcmp(iface, wp_viewporter_interface.name) == 0) { + wl->viewporter = wl_registry_bind(registry, name, + &wp_viewporter_interface, 1); + } +} + +static void registry_global_remove(void *data, struct wl_registry *registry, + uint32_t name) { + struct wlr_wl_backend *wl = data; + + struct wlr_wl_seat *seat; + wl_list_for_each(seat, &wl->seats, link) { + if (seat->global_name == name) { + destroy_wl_seat(seat); + break; + } + } +} + +static const struct wl_registry_listener registry_listener = { + .global = registry_global, + .global_remove = registry_global_remove +}; + +/* + * Initializes the wayland backend. Opens a connection to a remote wayland + * compositor and creates surfaces for each output, then registers globals on + * the specified display. + */ +static bool backend_start(struct wlr_backend *backend) { + struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend); + wlr_log(WLR_INFO, "Starting Wayland backend"); + + wl->started = true; + + struct wlr_wl_seat *seat; + wl_list_for_each(seat, &wl->seats, link) { + if (seat->wl_keyboard) { + init_seat_keyboard(seat); + } + + if (seat->wl_touch) { + init_seat_touch(seat); + } + + if (wl->tablet_manager) { + init_seat_tablet(seat); + } + } + + for (size_t i = 0; i < wl->requested_outputs; ++i) { + wlr_wl_output_create(&wl->backend); + } + + return true; +} + +static void backend_destroy(struct wlr_backend *backend) { + if (!backend) { + return; + } + + struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend); + + struct wlr_wl_output *output, *tmp_output; + wl_list_for_each_safe(output, tmp_output, &wl->outputs, link) { + wlr_output_destroy(&output->wlr_output); + } + + // Avoid using wl_list_for_each_safe() here: destroying a buffer may + // have the side-effect of destroying the next one in the list + while (!wl_list_empty(&wl->buffers)) { + struct wlr_wl_buffer *buffer = wl_container_of(wl->buffers.next, buffer, link); + destroy_wl_buffer(buffer); + } + + wlr_backend_finish(backend); + + wl_list_remove(&wl->event_loop_destroy.link); + + wl_event_source_remove(wl->remote_display_src); + + close(wl->drm_fd); + + wlr_drm_format_set_finish(&wl->shm_formats); + wlr_drm_format_set_finish(&wl->linux_dmabuf_v1_formats); + + struct wlr_wl_seat *seat, *tmp_seat; + wl_list_for_each_safe(seat, tmp_seat, &wl->seats, link) { + destroy_wl_seat(seat); + } + + if (wl->activation_v1) { + xdg_activation_v1_destroy(wl->activation_v1); + } + if (wl->zxdg_decoration_manager_v1) { + zxdg_decoration_manager_v1_destroy(wl->zxdg_decoration_manager_v1); + } + if (wl->zwp_pointer_gestures_v1) { + zwp_pointer_gestures_v1_destroy(wl->zwp_pointer_gestures_v1); + } + if (wl->tablet_manager) { + zwp_tablet_manager_v2_destroy(wl->tablet_manager); + } + if (wl->presentation) { + wp_presentation_destroy(wl->presentation); + } + if (wl->zwp_linux_dmabuf_v1) { + zwp_linux_dmabuf_v1_destroy(wl->zwp_linux_dmabuf_v1); + } + if (wl->legacy_drm != NULL) { + wl_drm_destroy(wl->legacy_drm); + } + if (wl->shm) { + wl_shm_destroy(wl->shm); + } + if (wl->zwp_relative_pointer_manager_v1) { + zwp_relative_pointer_manager_v1_destroy(wl->zwp_relative_pointer_manager_v1); + } + if (wl->subcompositor) { + wl_subcompositor_destroy(wl->subcompositor); + } + if (wl->viewporter) { + wp_viewporter_destroy(wl->viewporter); + } + free(wl->drm_render_name); + free(wl->activation_token); + xdg_wm_base_destroy(wl->xdg_wm_base); + wl_compositor_destroy(wl->compositor); + wl_registry_destroy(wl->registry); + wl_display_flush(wl->remote_display); + if (wl->own_remote_display) { + wl_display_disconnect(wl->remote_display); + } + free(wl); +} + +static int backend_get_drm_fd(struct wlr_backend *backend) { + struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend); + return wl->drm_fd; +} + +static uint32_t get_buffer_caps(struct wlr_backend *backend) { + struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend); + return (wl->zwp_linux_dmabuf_v1 ? WLR_BUFFER_CAP_DMABUF : 0) + | (wl->shm ? WLR_BUFFER_CAP_SHM : 0); +} + +static const struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_drm_fd = backend_get_drm_fd, + .get_buffer_caps = get_buffer_caps, +}; + +bool wlr_backend_is_wl(struct wlr_backend *b) { + return b->impl == &backend_impl; +} + +static void handle_event_loop_destroy(struct wl_listener *listener, void *data) { + struct wlr_wl_backend *wl = wl_container_of(listener, wl, event_loop_destroy); + backend_destroy(&wl->backend); +} + +struct wlr_backend *wlr_wl_backend_create(struct wl_event_loop *loop, + struct wl_display *remote_display) { + wlr_log(WLR_INFO, "Creating wayland backend"); + + struct wlr_wl_backend *wl = calloc(1, sizeof(*wl)); + if (!wl) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + wlr_backend_init(&wl->backend, &backend_impl); + + wl->event_loop = loop; + wl_list_init(&wl->outputs); + wl_list_init(&wl->seats); + wl_list_init(&wl->buffers); + + if (remote_display != NULL) { + wl->remote_display = remote_display; + } else { + wl->remote_display = wl_display_connect(NULL); + if (!wl->remote_display) { + wlr_log_errno(WLR_ERROR, "Could not connect to remote display"); + goto error_wl; + } + wl->own_remote_display = true; + } + + wl->registry = wl_display_get_registry(wl->remote_display); + if (!wl->registry) { + wlr_log_errno(WLR_ERROR, "Could not obtain reference to remote registry"); + goto error_display; + } + wl_registry_add_listener(wl->registry, ®istry_listener, wl); + + wl_display_roundtrip(wl->remote_display); // get globals + + if (!wl->compositor) { + wlr_log(WLR_ERROR, + "Remote Wayland compositor does not support wl_compositor"); + goto error_registry; + } + if (!wl->xdg_wm_base) { + wlr_log(WLR_ERROR, + "Remote Wayland compositor does not support xdg-shell"); + goto error_registry; + } + + struct zwp_linux_dmabuf_feedback_v1 *linux_dmabuf_feedback_v1 = NULL; + struct wlr_wl_linux_dmabuf_feedback_v1 feedback_data = { .backend = wl }; + if (wl->zwp_linux_dmabuf_v1 != NULL && + zwp_linux_dmabuf_v1_get_version(wl->zwp_linux_dmabuf_v1) >= + ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION) { + linux_dmabuf_feedback_v1 = + zwp_linux_dmabuf_v1_get_default_feedback(wl->zwp_linux_dmabuf_v1); + if (linux_dmabuf_feedback_v1 == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + goto error_registry; + } + zwp_linux_dmabuf_feedback_v1_add_listener(linux_dmabuf_feedback_v1, + &linux_dmabuf_feedback_v1_listener, &feedback_data); + + if (wl->legacy_drm != NULL) { + wl_drm_destroy(wl->legacy_drm); + wl->legacy_drm = NULL; + } + } + + wl_display_roundtrip(wl->remote_display); // get linux-dmabuf formats + + if (feedback_data.format_table != NULL) { + munmap(feedback_data.format_table, feedback_data.format_table_size); + } + if (linux_dmabuf_feedback_v1 != NULL) { + zwp_linux_dmabuf_feedback_v1_destroy(linux_dmabuf_feedback_v1); + } + + int fd = wl_display_get_fd(wl->remote_display); + wl->remote_display_src = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + dispatch_events, wl); + if (!wl->remote_display_src) { + wlr_log(WLR_ERROR, "Failed to create event source"); + goto error_registry; + } + wl_event_source_check(wl->remote_display_src); + + if (wl->drm_render_name != NULL) { + wlr_log(WLR_DEBUG, "Opening DRM render node %s", wl->drm_render_name); + wl->drm_fd = open(wl->drm_render_name, O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (wl->drm_fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open DRM render node %s", + wl->drm_render_name); + goto error_remote_display_src; + } + } else { + wl->drm_fd = -1; + } + + wl->event_loop_destroy.notify = handle_event_loop_destroy; + wl_event_loop_add_destroy_listener(loop, &wl->event_loop_destroy); + + const char *token = getenv("XDG_ACTIVATION_TOKEN"); + if (token != NULL) { + wl->activation_token = strdup(token); + unsetenv("XDG_ACTIVATION_TOKEN"); + } + + return &wl->backend; + +error_remote_display_src: + wl_event_source_remove(wl->remote_display_src); +error_registry: + free(wl->drm_render_name); + if (wl->compositor) { + wl_compositor_destroy(wl->compositor); + } + if (wl->xdg_wm_base) { + xdg_wm_base_destroy(wl->xdg_wm_base); + } + wl_registry_destroy(wl->registry); +error_display: + if (wl->own_remote_display) { + wl_display_disconnect(wl->remote_display); + } +error_wl: + wlr_backend_finish(&wl->backend); + free(wl); + return NULL; +} + +struct wl_display *wlr_wl_backend_get_remote_display(struct wlr_backend *backend) { + struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend); + return wl->remote_display; +} diff --git a/backend/wayland/meson.build b/backend/wayland/meson.build new file mode 100644 index 0000000..a480347 --- /dev/null +++ b/backend/wayland/meson.build @@ -0,0 +1,30 @@ +wayland_client = dependency('wayland-client', + fallback: 'wayland', + default_options: wayland_project_options, +) +wlr_deps += wayland_client + +wlr_files += files( + 'backend.c', + 'output.c', + 'seat.c', + 'pointer.c', + 'tablet_v2.c', +) + +client_protos = [ + 'drm', + 'linux-dmabuf-v1', + 'pointer-gestures-unstable-v1', + 'presentation-time', + 'relative-pointer-unstable-v1', + 'tablet-unstable-v2', + 'viewporter', + 'xdg-activation-v1', + 'xdg-decoration-unstable-v1', + 'xdg-shell', +] + +foreach proto : client_protos + wlr_files += protocols_client_header[proto] +endforeach diff --git a/backend/wayland/output.c b/backend/wayland/output.c new file mode 100644 index 0000000..2d3e326 --- /dev/null +++ b/backend/wayland/output.c @@ -0,0 +1,932 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "backend/wayland.h" +#include "render/pixel_format.h" +#include "render/wlr_renderer.h" +#include "types/wlr_output.h" + +#include "linux-dmabuf-v1-client-protocol.h" +#include "presentation-time-client-protocol.h" +#include "viewporter-client-protocol.h" +#include "xdg-activation-v1-client-protocol.h" +#include "xdg-decoration-unstable-v1-client-protocol.h" +#include "xdg-shell-client-protocol.h" + +static const uint32_t SUPPORTED_OUTPUT_STATE = + WLR_OUTPUT_STATE_BACKEND_OPTIONAL | + WLR_OUTPUT_STATE_BUFFER | + WLR_OUTPUT_STATE_ENABLED | + WLR_OUTPUT_STATE_MODE | + WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED; + +static size_t last_output_num = 0; + +static const char *surface_tag = "wlr_wl_output"; + +static struct wlr_wl_output *get_wl_output_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_wl(wlr_output)); + struct wlr_wl_output *output = wl_container_of(wlr_output, output, wlr_output); + return output; +} + +struct wlr_wl_output *get_wl_output_from_surface(struct wlr_wl_backend *wl, + struct wl_surface *surface) { + if (wl_proxy_get_tag((struct wl_proxy *)surface) != &surface_tag) { + return NULL; + } + struct wlr_wl_output *output = wl_surface_get_user_data(surface); + assert(output != NULL); + if (output->backend != wl) { + return NULL; + } + return output; +} + +static void surface_frame_callback(void *data, struct wl_callback *cb, + uint32_t time) { + struct wlr_wl_output *output = data; + + if (cb == NULL) { + return; + } + + assert(output->frame_callback == cb); + wl_callback_destroy(cb); + output->frame_callback = NULL; + + wlr_output_send_frame(&output->wlr_output); +} + +static const struct wl_callback_listener frame_listener = { + .done = surface_frame_callback +}; + +static void presentation_feedback_destroy( + struct wlr_wl_presentation_feedback *feedback) { + wl_list_remove(&feedback->link); + wp_presentation_feedback_destroy(feedback->feedback); + free(feedback); +} + +static void presentation_feedback_handle_sync_output(void *data, + struct wp_presentation_feedback *feedback, struct wl_output *output) { + // This space is intentionally left blank +} + +static void presentation_feedback_handle_presented(void *data, + struct wp_presentation_feedback *wp_feedback, uint32_t tv_sec_hi, + uint32_t tv_sec_lo, uint32_t tv_nsec, uint32_t refresh_ns, + uint32_t seq_hi, uint32_t seq_lo, uint32_t flags) { + struct wlr_wl_presentation_feedback *feedback = data; + + struct timespec t = { + .tv_sec = ((uint64_t)tv_sec_hi << 32) | tv_sec_lo, + .tv_nsec = tv_nsec, + }; + struct wlr_output_event_present event = { + .commit_seq = feedback->commit_seq, + .presented = true, + .when = &t, + .seq = ((uint64_t)seq_hi << 32) | seq_lo, + .refresh = refresh_ns, + .flags = flags, + }; + wlr_output_send_present(&feedback->output->wlr_output, &event); + + presentation_feedback_destroy(feedback); +} + +static void presentation_feedback_handle_discarded(void *data, + struct wp_presentation_feedback *wp_feedback) { + struct wlr_wl_presentation_feedback *feedback = data; + + struct wlr_output_event_present event = { + .commit_seq = feedback->commit_seq, + .presented = false, + }; + wlr_output_send_present(&feedback->output->wlr_output, &event); + + presentation_feedback_destroy(feedback); +} + +static const struct wp_presentation_feedback_listener + presentation_feedback_listener = { + .sync_output = presentation_feedback_handle_sync_output, + .presented = presentation_feedback_handle_presented, + .discarded = presentation_feedback_handle_discarded, +}; + +void destroy_wl_buffer(struct wlr_wl_buffer *buffer) { + if (buffer == NULL) { + return; + } + wl_list_remove(&buffer->buffer_destroy.link); + wl_list_remove(&buffer->link); + wl_buffer_destroy(buffer->wl_buffer); + if (!buffer->released) { + wlr_buffer_unlock(buffer->buffer); + } + free(buffer); +} + +static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer) { + struct wlr_wl_buffer *buffer = data; + buffer->released = true; + wlr_buffer_unlock(buffer->buffer); // might free buffer +} + +static const struct wl_buffer_listener buffer_listener = { + .release = buffer_handle_release, +}; + +static void buffer_handle_buffer_destroy(struct wl_listener *listener, + void *data) { + struct wlr_wl_buffer *buffer = + wl_container_of(listener, buffer, buffer_destroy); + destroy_wl_buffer(buffer); +} + +static bool test_buffer(struct wlr_wl_backend *wl, + struct wlr_buffer *wlr_buffer) { + struct wlr_dmabuf_attributes dmabuf; + struct wlr_shm_attributes shm; + if (wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf)) { + return wlr_drm_format_set_has(&wl->linux_dmabuf_v1_formats, + dmabuf.format, dmabuf.modifier); + } else if (wlr_buffer_get_shm(wlr_buffer, &shm)) { + return wlr_drm_format_set_has(&wl->shm_formats, shm.format, + DRM_FORMAT_MOD_INVALID); + } else { + return false; + } +} + +static struct wl_buffer *import_dmabuf(struct wlr_wl_backend *wl, + struct wlr_dmabuf_attributes *dmabuf) { + uint32_t modifier_hi = dmabuf->modifier >> 32; + uint32_t modifier_lo = (uint32_t)dmabuf->modifier; + struct zwp_linux_buffer_params_v1 *params = + zwp_linux_dmabuf_v1_create_params(wl->zwp_linux_dmabuf_v1); + for (int i = 0; i < dmabuf->n_planes; i++) { + zwp_linux_buffer_params_v1_add(params, dmabuf->fd[i], i, + dmabuf->offset[i], dmabuf->stride[i], modifier_hi, modifier_lo); + } + + struct wl_buffer *wl_buffer = zwp_linux_buffer_params_v1_create_immed( + params, dmabuf->width, dmabuf->height, dmabuf->format, 0); + zwp_linux_buffer_params_v1_destroy(params); + // TODO: handle create() errors + return wl_buffer; +} + +static struct wl_buffer *import_shm(struct wlr_wl_backend *wl, + struct wlr_shm_attributes *shm) { + enum wl_shm_format wl_shm_format = convert_drm_format_to_wl_shm(shm->format); + uint32_t size = shm->stride * shm->height; + struct wl_shm_pool *pool = wl_shm_create_pool(wl->shm, shm->fd, size); + if (pool == NULL) { + return NULL; + } + struct wl_buffer *wl_buffer = wl_shm_pool_create_buffer(pool, shm->offset, + shm->width, shm->height, shm->stride, wl_shm_format); + wl_shm_pool_destroy(pool); + return wl_buffer; +} + +static struct wlr_wl_buffer *create_wl_buffer(struct wlr_wl_backend *wl, + struct wlr_buffer *wlr_buffer) { + if (!test_buffer(wl, wlr_buffer)) { + return NULL; + } + + struct wlr_dmabuf_attributes dmabuf; + struct wlr_shm_attributes shm; + struct wl_buffer *wl_buffer; + if (wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf)) { + wl_buffer = import_dmabuf(wl, &dmabuf); + } else if (wlr_buffer_get_shm(wlr_buffer, &shm)) { + wl_buffer = import_shm(wl, &shm); + } else { + return NULL; + } + if (wl_buffer == NULL) { + return NULL; + } + + struct wlr_wl_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + wl_buffer_destroy(wl_buffer); + return NULL; + } + buffer->wl_buffer = wl_buffer; + buffer->buffer = wlr_buffer_lock(wlr_buffer); + wl_list_insert(&wl->buffers, &buffer->link); + + wl_buffer_add_listener(wl_buffer, &buffer_listener, buffer); + + buffer->buffer_destroy.notify = buffer_handle_buffer_destroy; + wl_signal_add(&wlr_buffer->events.destroy, &buffer->buffer_destroy); + + return buffer; +} + +static struct wlr_wl_buffer *get_or_create_wl_buffer(struct wlr_wl_backend *wl, + struct wlr_buffer *wlr_buffer) { + struct wlr_wl_buffer *buffer; + wl_list_for_each(buffer, &wl->buffers, link) { + // We can only re-use a wlr_wl_buffer if the parent compositor has + // released it, because wl_buffer.release is per-wl_buffer, not per + // wl_surface.commit. + if (buffer->buffer == wlr_buffer && buffer->released) { + buffer->released = false; + wlr_buffer_lock(buffer->buffer); + return buffer; + } + } + + return create_wl_buffer(wl, wlr_buffer); +} + +static bool output_test(struct wlr_output *wlr_output, + const struct wlr_output_state *state) { + struct wlr_wl_output *output = + get_wl_output_from_output(wlr_output); + + uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE; + if (unsupported != 0) { + wlr_log(WLR_DEBUG, "Unsupported output state fields: 0x%"PRIx32, + unsupported); + return false; + } + + // Adaptive sync is effectively always enabled when using the Wayland + // backend. This is not something we have control over, so we set the state + // to enabled on creating the output and never allow changing it. + assert(wlr_output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED); + if (state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) { + if (!state->adaptive_sync_enabled) { + wlr_log(WLR_DEBUG, "Disabling adaptive sync is not supported"); + return false; + } + } + + if (state->committed & WLR_OUTPUT_STATE_MODE) { + assert(state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM); + + if (state->custom_mode.refresh != 0) { + wlr_log(WLR_DEBUG, "Refresh rates are not supported"); + return false; + } + } + + if ((state->committed & WLR_OUTPUT_STATE_BUFFER) && + !test_buffer(output->backend, state->buffer)) { + wlr_log(WLR_DEBUG, "Unsupported buffer format"); + return false; + } + + if (state->committed & WLR_OUTPUT_STATE_LAYERS) { + // If we can't use a sub-surface for a layer, then we can't use a + // sub-surface for any layer underneath + bool supported = output->backend->subcompositor != NULL; + for (ssize_t i = state->layers_len - 1; i >= 0; i--) { + struct wlr_output_layer_state *layer_state = &state->layers[i]; + if (layer_state->buffer != NULL) { + int x = layer_state->dst_box.x; + int y = layer_state->dst_box.y; + int width = layer_state->dst_box.width; + int height = layer_state->dst_box.height; + bool needs_viewport = width != layer_state->buffer->width || + height != layer_state->buffer->height; + if (!wlr_fbox_empty(&layer_state->src_box)) { + needs_viewport = needs_viewport || + layer_state->src_box.x != 0 || + layer_state->src_box.y != 0 || + layer_state->src_box.width != width || + layer_state->src_box.height != height; + } + if (x < 0 || y < 0 || + x + width > wlr_output->width || + y + height > wlr_output->height || + (output->backend->viewporter == NULL && needs_viewport)) { + supported = false; + } + supported = supported && + test_buffer(output->backend, layer_state->buffer); + } + layer_state->accepted = supported; + } + } + + return true; +} + +static void output_layer_handle_addon_destroy(struct wlr_addon *addon) { + struct wlr_wl_output_layer *layer = wl_container_of(addon, layer, addon); + + wlr_addon_finish(&layer->addon); + if (layer->viewport != NULL) { + wp_viewport_destroy(layer->viewport); + } + wl_subsurface_destroy(layer->subsurface); + wl_surface_destroy(layer->surface); + free(layer); +} + +static const struct wlr_addon_interface output_layer_addon_impl = { + .name = "wlr_wl_output_layer", + .destroy = output_layer_handle_addon_destroy, +}; + +static struct wlr_wl_output_layer *get_or_create_output_layer( + struct wlr_wl_output *output, struct wlr_output_layer *wlr_layer) { + assert(output->backend->subcompositor != NULL); + + struct wlr_wl_output_layer *layer; + struct wlr_addon *addon = wlr_addon_find(&wlr_layer->addons, output, + &output_layer_addon_impl); + if (addon != NULL) { + layer = wl_container_of(addon, layer, addon); + return layer; + } + + layer = calloc(1, sizeof(*layer)); + if (layer == NULL) { + return NULL; + } + + wlr_addon_init(&layer->addon, &wlr_layer->addons, output, + &output_layer_addon_impl); + + layer->surface = wl_compositor_create_surface(output->backend->compositor); + layer->subsurface = wl_subcompositor_get_subsurface( + output->backend->subcompositor, layer->surface, output->surface); + + // Set an empty input region so that input events are handled by the main + // surface + struct wl_region *region = wl_compositor_create_region(output->backend->compositor); + wl_surface_set_input_region(layer->surface, region); + wl_region_destroy(region); + + if (output->backend->viewporter != NULL) { + layer->viewport = wp_viewporter_get_viewport(output->backend->viewporter, layer->surface); + } + + return layer; +} + +static bool has_layers_order_changed(struct wlr_wl_output *output, + struct wlr_output_layer_state *layers, size_t layers_len) { + // output_basic_check() ensures that layers_len equals the number of + // registered output layers + size_t i = 0; + struct wlr_output_layer *layer; + wl_list_for_each(layer, &output->wlr_output.layers, link) { + assert(i < layers_len); + const struct wlr_output_layer_state *layer_state = &layers[i]; + if (layer_state->layer != layer) { + return true; + } + i++; + } + assert(i == layers_len); + return false; +} + +static void output_layer_unmap(struct wlr_wl_output_layer *layer) { + if (!layer->mapped) { + return; + } + + wl_surface_attach(layer->surface, NULL, 0, 0); + wl_surface_commit(layer->surface); + layer->mapped = false; +} + +static void damage_surface(struct wl_surface *surface, + const pixman_region32_t *damage) { + if (damage == NULL) { + wl_surface_damage_buffer(surface, + 0, 0, INT32_MAX, INT32_MAX); + return; + } + + int rects_len; + const pixman_box32_t *rects = pixman_region32_rectangles(damage, &rects_len); + for (int i = 0; i < rects_len; i++) { + const pixman_box32_t *r = &rects[i]; + wl_surface_damage_buffer(surface, r->x1, r->y1, + r->x2 - r->x1, r->y2 - r->y1); + } +} + +static bool output_layer_commit(struct wlr_wl_output *output, + struct wlr_wl_output_layer *layer, + const struct wlr_output_layer_state *state) { + if (state->layer->dst_box.x != state->dst_box.x || + state->layer->dst_box.y != state->dst_box.y) { + wl_subsurface_set_position(layer->subsurface, state->dst_box.x, state->dst_box.y); + } + + if (state->buffer == NULL) { + output_layer_unmap(layer); + return true; + } + + struct wlr_wl_buffer *buffer = + get_or_create_wl_buffer(output->backend, state->buffer); + if (buffer == NULL) { + return false; + } + + if (layer->viewport != NULL && + (state->layer->dst_box.width != state->dst_box.width || + state->layer->dst_box.height != state->dst_box.height)) { + wp_viewport_set_destination(layer->viewport, state->dst_box.width, state->dst_box.height); + } + if (layer->viewport != NULL && !wlr_fbox_equal(&state->layer->src_box, &state->src_box)) { + struct wlr_fbox src_box = state->src_box; + if (wlr_fbox_empty(&src_box)) { + // -1 resets the box + src_box = (struct wlr_fbox){ + .x = -1, + .y = -1, + .width = -1, + .height = -1, + }; + } + wp_viewport_set_source(layer->viewport, + wl_fixed_from_double(src_box.x), + wl_fixed_from_double(src_box.y), + wl_fixed_from_double(src_box.width), + wl_fixed_from_double(src_box.height)); + } + + wl_surface_attach(layer->surface, buffer->wl_buffer, 0, 0); + damage_surface(layer->surface, state->damage); + wl_surface_commit(layer->surface); + layer->mapped = true; + return true; +} + +static bool commit_layers(struct wlr_wl_output *output, + struct wlr_output_layer_state *layers, size_t layers_len) { + if (output->backend->subcompositor == NULL) { + return true; + } + + bool reordered = has_layers_order_changed(output, layers, layers_len); + + struct wlr_wl_output_layer *prev_layer = NULL; + for (size_t i = 0; i < layers_len; i++) { + struct wlr_wl_output_layer *layer = + get_or_create_output_layer(output, layers[i].layer); + if (layer == NULL) { + return false; + } + + if (!layers[i].accepted) { + output_layer_unmap(layer); + continue; + } + + if (prev_layer != NULL && reordered) { + wl_subsurface_place_above(layer->subsurface, + prev_layer->surface); + } + + if (!output_layer_commit(output, layer, &layers[i])) { + return false; + } + + prev_layer = layer; + } + + return true; +} + +static bool output_commit(struct wlr_output *wlr_output, + const struct wlr_output_state *state) { + struct wlr_wl_output *output = + get_wl_output_from_output(wlr_output); + + if (!output_test(wlr_output, state)) { + return false; + } + + if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) { + wl_surface_attach(output->surface, NULL, 0, 0); + wl_surface_commit(output->surface); + } + + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + const pixman_region32_t *damage = NULL; + if (state->committed & WLR_OUTPUT_STATE_DAMAGE) { + damage = &state->damage; + } + + struct wlr_buffer *wlr_buffer = state->buffer; + struct wlr_wl_buffer *buffer = + get_or_create_wl_buffer(output->backend, wlr_buffer); + if (buffer == NULL) { + return false; + } + + wl_surface_attach(output->surface, buffer->wl_buffer, 0, 0); + damage_surface(output->surface, damage); + } + + if ((state->committed & WLR_OUTPUT_STATE_LAYERS) && + !commit_layers(output, state->layers, state->layers_len)) { + return false; + } + + if (output_pending_enabled(wlr_output, state)) { + if (output->frame_callback != NULL) { + wl_callback_destroy(output->frame_callback); + } + output->frame_callback = wl_surface_frame(output->surface); + wl_callback_add_listener(output->frame_callback, &frame_listener, output); + + struct wp_presentation_feedback *wp_feedback = NULL; + if (output->backend->presentation != NULL) { + wp_feedback = wp_presentation_feedback(output->backend->presentation, + output->surface); + } + + wl_surface_commit(output->surface); + + if (wp_feedback != NULL) { + struct wlr_wl_presentation_feedback *feedback = + calloc(1, sizeof(*feedback)); + if (feedback == NULL) { + wp_presentation_feedback_destroy(wp_feedback); + return false; + } + feedback->output = output; + feedback->feedback = wp_feedback; + feedback->commit_seq = output->wlr_output.commit_seq + 1; + wl_list_insert(&output->presentation_feedbacks, &feedback->link); + + wp_presentation_feedback_add_listener(wp_feedback, + &presentation_feedback_listener, feedback); + } else { + struct wlr_output_event_present present_event = { + .commit_seq = wlr_output->commit_seq + 1, + .presented = true, + }; + output_defer_present(wlr_output, present_event); + } + } + + wl_display_flush(output->backend->remote_display); + + return true; +} + +static bool output_set_cursor(struct wlr_output *wlr_output, + struct wlr_buffer *wlr_buffer, int hotspot_x, int hotspot_y) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + struct wlr_wl_backend *backend = output->backend; + + output->cursor.hotspot_x = hotspot_x; + output->cursor.hotspot_y = hotspot_y; + + if (output->cursor.surface == NULL) { + output->cursor.surface = + wl_compositor_create_surface(backend->compositor); + } + struct wl_surface *surface = output->cursor.surface; + + if (wlr_buffer != NULL) { + struct wlr_wl_buffer *buffer = + get_or_create_wl_buffer(output->backend, wlr_buffer); + if (buffer == NULL) { + return false; + } + + wl_surface_attach(surface, buffer->wl_buffer, 0, 0); + wl_surface_damage_buffer(surface, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_commit(surface); + } else { + wl_surface_attach(surface, NULL, 0, 0); + wl_surface_commit(surface); + } + + update_wl_output_cursor(output); + wl_display_flush(backend->remote_display); + return true; +} + +static const struct wlr_drm_format_set *output_get_formats( + struct wlr_output *wlr_output, uint32_t buffer_caps) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + if (buffer_caps & WLR_BUFFER_CAP_DMABUF) { + return &output->backend->linux_dmabuf_v1_formats; + } else if (buffer_caps & WLR_BUFFER_CAP_SHM) { + return &output->backend->shm_formats; + } + return NULL; +} + +static void output_destroy(struct wlr_output *wlr_output) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + if (output == NULL) { + return; + } + + wl_list_remove(&output->link); + + if (output->cursor.surface) { + wl_surface_destroy(output->cursor.surface); + } + + if (output->frame_callback) { + wl_callback_destroy(output->frame_callback); + } + + struct wlr_wl_presentation_feedback *feedback, *feedback_tmp; + wl_list_for_each_safe(feedback, feedback_tmp, + &output->presentation_feedbacks, link) { + presentation_feedback_destroy(feedback); + } + + if (output->zxdg_toplevel_decoration_v1) { + zxdg_toplevel_decoration_v1_destroy(output->zxdg_toplevel_decoration_v1); + } + if (output->xdg_toplevel) { + xdg_toplevel_destroy(output->xdg_toplevel); + } + if (output->xdg_surface) { + xdg_surface_destroy(output->xdg_surface); + } + if (output->own_surface) { + wl_surface_destroy(output->surface); + } + wl_display_flush(output->backend->remote_display); + free(output); +} + +void update_wl_output_cursor(struct wlr_wl_output *output) { + struct wlr_wl_pointer *pointer = output->cursor.pointer; + if (pointer) { + assert(pointer->output == output); + assert(output->enter_serial); + + struct wlr_wl_seat *seat = pointer->seat; + wl_pointer_set_cursor(seat->wl_pointer, output->enter_serial, + output->cursor.surface, output->cursor.hotspot_x, + output->cursor.hotspot_y); + } +} + +static bool output_move_cursor(struct wlr_output *_output, int x, int y) { + // TODO: only return true if x == current x and y == current y + return true; +} + +static const struct wlr_output_impl output_impl = { + .destroy = output_destroy, + .test = output_test, + .commit = output_commit, + .set_cursor = output_set_cursor, + .move_cursor = output_move_cursor, + .get_cursor_formats = output_get_formats, + .get_primary_formats = output_get_formats, +}; + +bool wlr_output_is_wl(struct wlr_output *wlr_output) { + return wlr_output->impl == &output_impl; +} + +static void xdg_surface_handle_configure(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) { + struct wlr_wl_output *output = data; + assert(output && output->xdg_surface == xdg_surface); + + output->configured = true; + xdg_surface_ack_configure(xdg_surface, serial); + + // nothing else? +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_handle_configure, +}; + +static void xdg_toplevel_handle_configure(void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height, struct wl_array *states) { + struct wlr_wl_output *output = data; + assert(output && output->xdg_toplevel == xdg_toplevel); + + if (width == 0 || height == 0) { + return; + } + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_custom_mode(&state, width, height, 0); + wlr_output_send_request_state(&output->wlr_output, &state); + wlr_output_state_finish(&state); +} + +static void xdg_toplevel_handle_close(void *data, + struct xdg_toplevel *xdg_toplevel) { + struct wlr_wl_output *output = data; + assert(output && output->xdg_toplevel == xdg_toplevel); + + wlr_output_destroy(&output->wlr_output); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, + .close = xdg_toplevel_handle_close, +}; + +static struct wlr_wl_output *output_create(struct wlr_wl_backend *backend, + struct wl_surface *surface) { + struct wlr_wl_output *output = calloc(1, sizeof(*output)); + if (output == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_wl_output"); + return NULL; + } + struct wlr_output *wlr_output = &output->wlr_output; + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_custom_mode(&state, 1280, 720, 0); + + wlr_output_init(wlr_output, &backend->backend, &output_impl, + backend->event_loop, &state); + wlr_output_state_finish(&state); + + wlr_output->adaptive_sync_status = WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED; + + size_t output_num = ++last_output_num; + + char name[64]; + snprintf(name, sizeof(name), "WL-%zu", output_num); + wlr_output_set_name(wlr_output, name); + + char description[128]; + snprintf(description, sizeof(description), "Wayland output %zu", output_num); + wlr_output_set_description(wlr_output, description); + + output->surface = surface; + output->backend = backend; + wl_list_init(&output->presentation_feedbacks); + + wl_proxy_set_tag((struct wl_proxy *)output->surface, &surface_tag); + wl_surface_set_user_data(output->surface, output); + + wl_list_insert(&backend->outputs, &output->link); + + return output; +} + +static void output_start(struct wlr_wl_output *output) { + struct wlr_output *wlr_output = &output->wlr_output; + struct wlr_wl_backend *backend = output->backend; + + wl_signal_emit_mutable(&backend->backend.events.new_output, wlr_output); + + struct wlr_wl_seat *seat; + wl_list_for_each(seat, &backend->seats, link) { + if (seat->wl_pointer) { + create_pointer(seat, output); + } + } +} + +struct wlr_output *wlr_wl_output_create(struct wlr_backend *wlr_backend) { + struct wlr_wl_backend *backend = get_wl_backend_from_backend(wlr_backend); + if (!backend->started) { + ++backend->requested_outputs; + return NULL; + } + + struct wl_surface *surface = wl_compositor_create_surface(backend->compositor); + if (surface == NULL) { + wlr_log(WLR_ERROR, "Could not create output surface"); + return NULL; + } + + struct wlr_wl_output *output = output_create(backend, surface); + if (output == NULL) { + wl_surface_destroy(surface); + return NULL; + } + + output->own_surface = true; + + output->xdg_surface = + xdg_wm_base_get_xdg_surface(backend->xdg_wm_base, output->surface); + if (!output->xdg_surface) { + wlr_log_errno(WLR_ERROR, "Could not get xdg surface"); + goto error; + } + + output->xdg_toplevel = + xdg_surface_get_toplevel(output->xdg_surface); + if (!output->xdg_toplevel) { + wlr_log_errno(WLR_ERROR, "Could not get xdg toplevel"); + goto error; + } + + if (backend->zxdg_decoration_manager_v1) { + output->zxdg_toplevel_decoration_v1 = + zxdg_decoration_manager_v1_get_toplevel_decoration( + backend->zxdg_decoration_manager_v1, output->xdg_toplevel); + if (!output->zxdg_toplevel_decoration_v1) { + wlr_log_errno(WLR_ERROR, "Could not get xdg toplevel decoration"); + goto error; + } + zxdg_toplevel_decoration_v1_set_mode(output->zxdg_toplevel_decoration_v1, + ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + } + + wlr_wl_output_set_title(&output->wlr_output, NULL); + + xdg_toplevel_set_app_id(output->xdg_toplevel, "wlroots"); + xdg_surface_add_listener(output->xdg_surface, + &xdg_surface_listener, output); + xdg_toplevel_add_listener(output->xdg_toplevel, + &xdg_toplevel_listener, output); + wl_surface_commit(output->surface); + + while (!output->configured) { + int ret = wl_event_loop_dispatch(backend->event_loop, -1); + if (ret < 0) { + wlr_log(WLR_ERROR, "wl_event_loop_dispatch() failed"); + goto error; + } + } + + output_start(output); + + // TODO: let the compositor do this bit + if (backend->activation_v1 && backend->activation_token) { + xdg_activation_v1_activate(backend->activation_v1, + backend->activation_token, output->surface); + } + + return &output->wlr_output; + +error: + wlr_output_destroy(&output->wlr_output); + return NULL; +} + +struct wlr_output *wlr_wl_output_create_from_surface(struct wlr_backend *wlr_backend, + struct wl_surface *surface) { + struct wlr_wl_backend *backend = get_wl_backend_from_backend(wlr_backend); + assert(backend->started); + + struct wlr_wl_output *output = output_create(backend, surface); + if (output == NULL) { + wl_surface_destroy(surface); + return NULL; + } + + output_start(output); + + return &output->wlr_output; +} + +void wlr_wl_output_set_title(struct wlr_output *output, const char *title) { + struct wlr_wl_output *wl_output = get_wl_output_from_output(output); + assert(wl_output->xdg_toplevel != NULL); + + char wl_title[32]; + if (title == NULL) { + if (snprintf(wl_title, sizeof(wl_title), "wlroots - %s", output->name) <= 0) { + return; + } + title = wl_title; + } + + xdg_toplevel_set_title(wl_output->xdg_toplevel, title); + wl_display_flush(wl_output->backend->remote_display); +} + +struct wl_surface *wlr_wl_output_get_surface(struct wlr_output *output) { + struct wlr_wl_output *wl_output = get_wl_output_from_output(output); + return wl_output->surface; +} diff --git a/backend/wayland/pointer.c b/backend/wayland/pointer.c new file mode 100644 index 0000000..f795d12 --- /dev/null +++ b/backend/wayland/pointer.c @@ -0,0 +1,557 @@ + +#include +#include +#include +#include +#include + +#include "backend/wayland.h" + +#include "pointer-gestures-unstable-v1-client-protocol.h" +#include "relative-pointer-unstable-v1-client-protocol.h" + +static struct wlr_wl_pointer *output_get_pointer(struct wlr_wl_output *output, + const struct wl_pointer *wl_pointer) { + struct wlr_wl_seat *seat; + wl_list_for_each(seat, &output->backend->seats, link) { + if (seat->wl_pointer != wl_pointer) { + continue; + } + + struct wlr_wl_pointer *pointer; + wl_list_for_each(pointer, &seat->pointers, link) { + if (pointer->output == output) { + return pointer; + } + } + } + + return NULL; +} + +static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, + wl_fixed_t sy) { + struct wlr_wl_seat *seat = data; + if (surface == NULL) { + return; + } + + struct wlr_wl_output *output = get_wl_output_from_surface(seat->backend, surface); + if (output == NULL) { + return; + } + + struct wlr_wl_pointer *pointer = output_get_pointer(output, wl_pointer); + seat->active_pointer = pointer; + + // Manage cursor icon/rendering on output + struct wlr_wl_pointer *current = output->cursor.pointer; + if (current && current != pointer) { + wlr_log(WLR_INFO, "Ignoring seat '%s' pointer in favor of seat '%s'", + seat->name, current->seat->name); + return; + } + + output->enter_serial = serial; + output->cursor.pointer = pointer; + update_wl_output_cursor(output); +} + +static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface) { + struct wlr_wl_seat *seat = data; + if (surface == NULL) { + return; + } + + struct wlr_wl_output *output = get_wl_output_from_surface(seat->backend, surface); + if (output == NULL) { + return; + } + + if (seat->active_pointer != NULL && + seat->active_pointer->output == output) { + seat->active_pointer = NULL; + } + + if (output->cursor.pointer == seat->active_pointer) { + output->enter_serial = 0; + output->cursor.pointer = NULL; + } +} + +static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_output *wlr_output = &pointer->output->wlr_output; + struct wlr_pointer_motion_absolute_event event = { + .pointer = &pointer->wlr_pointer, + .time_msec = time, + .x = wl_fixed_to_double(sx) / wlr_output->width, + .y = wl_fixed_to_double(sy) / wlr_output->height, + }; + wl_signal_emit_mutable(&pointer->wlr_pointer.events.motion_absolute, &event); +} + +static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_pointer_button_event event = { + .pointer = &pointer->wlr_pointer, + .button = button, + .state = state, + .time_msec = time, + }; + wl_signal_emit_mutable(&pointer->wlr_pointer.events.button, &event); +} + +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_pointer_axis_event event = { + .pointer = &pointer->wlr_pointer, + .delta = wl_fixed_to_double(value), + .delta_discrete = pointer->axis_discrete, + .orientation = axis, + .time_msec = time, + .source = pointer->axis_source, + .relative_direction = pointer->axis_relative_direction, + }; + wl_signal_emit_mutable(&pointer->wlr_pointer.events.axis, &event); + + pointer->axis_discrete = 0; +} + +static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + wl_signal_emit_mutable(&pointer->wlr_pointer.events.frame, + &pointer->wlr_pointer); +} + +static void pointer_handle_axis_source(void *data, + struct wl_pointer *wl_pointer, uint32_t axis_source) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + pointer->axis_source = axis_source; +} + +static void pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_pointer_axis_event event = { + .pointer = &pointer->wlr_pointer, + .delta = 0, + .delta_discrete = 0, + .orientation = axis, + .time_msec = time, + .source = pointer->axis_source, + }; + wl_signal_emit_mutable(&pointer->wlr_pointer.events.axis, &event); +} + +static void pointer_handle_axis_discrete(void *data, + struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + pointer->axis_discrete = discrete * WLR_POINTER_AXIS_DISCRETE_STEP; +} + +static void pointer_handle_axis_value120(void *data, + struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + pointer->axis_discrete = value120; +} + +static void pointer_handle_axis_relative_direction(void *data, + struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + pointer->axis_relative_direction = direction; +} + +static const struct wl_pointer_listener pointer_listener = { + .enter = pointer_handle_enter, + .leave = pointer_handle_leave, + .motion = pointer_handle_motion, + .button = pointer_handle_button, + .axis = pointer_handle_axis, + .frame = pointer_handle_frame, + .axis_source = pointer_handle_axis_source, + .axis_stop = pointer_handle_axis_stop, + .axis_discrete = pointer_handle_axis_discrete, + .axis_value120 = pointer_handle_axis_value120, + .axis_relative_direction = pointer_handle_axis_relative_direction, +}; + +static void gesture_swipe_begin(void *data, + struct zwp_pointer_gesture_swipe_v1 *zwp_pointer_gesture_swipe_v1, + uint32_t serial, uint32_t time, struct wl_surface *surface, + uint32_t fingers) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + pointer->fingers = fingers; + + struct wlr_pointer_swipe_begin_event wlr_event = { + .pointer = &pointer->wlr_pointer, + .time_msec = time, + .fingers = fingers, + }; + wl_signal_emit_mutable(&pointer->wlr_pointer.events.swipe_begin, &wlr_event); +} + +static void gesture_swipe_update(void *data, + struct zwp_pointer_gesture_swipe_v1 *zwp_pointer_gesture_swipe_v1, + uint32_t time, wl_fixed_t dx, wl_fixed_t dy) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_pointer_swipe_update_event wlr_event = { + .pointer = &pointer->wlr_pointer, + .time_msec = time, + .fingers = pointer->fingers, + .dx = wl_fixed_to_double(dx), + .dy = wl_fixed_to_double(dy), + }; + wl_signal_emit_mutable(&pointer->wlr_pointer.events.swipe_update, &wlr_event); +} + +static void gesture_swipe_end(void *data, + struct zwp_pointer_gesture_swipe_v1 *zwp_pointer_gesture_swipe_v1, + uint32_t serial, uint32_t time, int32_t cancelled) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_pointer_swipe_end_event wlr_event = { + .pointer = &pointer->wlr_pointer, + .time_msec = time, + .cancelled = cancelled, + }; + wl_signal_emit_mutable(&pointer->wlr_pointer.events.swipe_end, &wlr_event); +} + +static const struct zwp_pointer_gesture_swipe_v1_listener gesture_swipe_impl = { + .begin = gesture_swipe_begin, + .update = gesture_swipe_update, + .end = gesture_swipe_end, +}; + +static void gesture_pinch_begin(void *data, + struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, + uint32_t serial, uint32_t time, struct wl_surface *surface, + uint32_t fingers) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + pointer->fingers = fingers; + + struct wlr_pointer_pinch_begin_event wlr_event = { + .pointer = &pointer->wlr_pointer, + .time_msec = time, + .fingers = pointer->fingers, + }; + + wl_signal_emit_mutable(&pointer->wlr_pointer.events.pinch_begin, &wlr_event); +} + +static void gesture_pinch_update(void *data, + struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, + uint32_t time, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t scale, + wl_fixed_t rotation) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_pointer_pinch_update_event wlr_event = { + .pointer = &pointer->wlr_pointer, + .time_msec = time, + .fingers = pointer->fingers, + .dx = wl_fixed_to_double(dx), + .dy = wl_fixed_to_double(dy), + .scale = wl_fixed_to_double(scale), + .rotation = wl_fixed_to_double(rotation), + }; + wl_signal_emit_mutable(&pointer->wlr_pointer.events.pinch_update, &wlr_event); +} + +static void gesture_pinch_end(void *data, + struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, + uint32_t serial, uint32_t time, int32_t cancelled) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_pointer_pinch_end_event wlr_event = { + .pointer = &pointer->wlr_pointer, + .time_msec = time, + .cancelled = cancelled, + }; + wl_signal_emit_mutable(&pointer->wlr_pointer.events.pinch_end, &wlr_event); +} + +static const struct zwp_pointer_gesture_pinch_v1_listener gesture_pinch_impl = { + .begin = gesture_pinch_begin, + .update = gesture_pinch_update, + .end = gesture_pinch_end, +}; + +static void gesture_hold_begin(void *data, + struct zwp_pointer_gesture_hold_v1 *zwp_pointer_gesture_hold_v1, + uint32_t serial, uint32_t time, struct wl_surface *surface, + uint32_t fingers) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + pointer->fingers = fingers; + + struct wlr_pointer_hold_begin_event wlr_event = { + .pointer = &pointer->wlr_pointer, + .time_msec = time, + .fingers = fingers, + }; + wl_signal_emit_mutable(&pointer->wlr_pointer.events.hold_begin, &wlr_event); +} + +static void gesture_hold_end(void *data, + struct zwp_pointer_gesture_hold_v1 *zwp_pointer_gesture_hold_v1, + uint32_t serial, uint32_t time, int32_t cancelled) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_pointer_hold_end_event wlr_event = { + .pointer = &pointer->wlr_pointer, + .time_msec = time, + .cancelled = cancelled, + }; + wl_signal_emit_mutable(&pointer->wlr_pointer.events.hold_end, &wlr_event); +} + +static const struct zwp_pointer_gesture_hold_v1_listener gesture_hold_impl = { + .begin = gesture_hold_begin, + .end = gesture_hold_end, +}; + +static void relative_pointer_handle_relative_motion(void *data, + struct zwp_relative_pointer_v1 *relative_pointer, uint32_t utime_hi, + uint32_t utime_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, + wl_fixed_t dy_unaccel) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_pointer *pointer = seat->active_pointer; + if (pointer == NULL) { + return; + } + + uint64_t time_usec = (uint64_t)utime_hi << 32 | utime_lo; + + struct wlr_pointer_motion_event wlr_event = { + .pointer = &pointer->wlr_pointer, + .time_msec = (uint32_t)(time_usec / 1000), + .delta_x = wl_fixed_to_double(dx), + .delta_y = wl_fixed_to_double(dy), + .unaccel_dx = wl_fixed_to_double(dx_unaccel), + .unaccel_dy = wl_fixed_to_double(dy_unaccel), + }; + wl_signal_emit_mutable(&pointer->wlr_pointer.events.motion, &wlr_event); +} + +static const struct zwp_relative_pointer_v1_listener relative_pointer_listener = { + .relative_motion = relative_pointer_handle_relative_motion, +}; + +const struct wlr_pointer_impl wl_pointer_impl = { + .name = "wl-pointer", +}; + +static void destroy_pointer(struct wlr_wl_pointer *pointer) { + if (pointer->output->cursor.pointer == pointer) { + pointer->output->cursor.pointer = NULL; + } + if (pointer->seat->active_pointer == pointer) { + pointer->seat->active_pointer = NULL; + } + + wlr_pointer_finish(&pointer->wlr_pointer); + wl_list_remove(&pointer->output_destroy.link); + wl_list_remove(&pointer->link); + free(pointer); +} + +static void pointer_output_destroy(struct wl_listener *listener, void *data) { + struct wlr_wl_pointer *pointer = + wl_container_of(listener, pointer, output_destroy); + destroy_pointer(pointer); +} + +void create_pointer(struct wlr_wl_seat *seat, struct wlr_wl_output *output) { + assert(seat->wl_pointer); + + if (output_get_pointer(output, seat->wl_pointer)) { + wlr_log(WLR_DEBUG, + "pointer for output '%s' from seat '%s' already exists", + output->wlr_output.name, seat->name); + return; + } + + wlr_log(WLR_DEBUG, "creating pointer for output '%s' from seat '%s'", + output->wlr_output.name, seat->name); + + struct wlr_wl_pointer *pointer = calloc(1, sizeof(*pointer)); + if (pointer == NULL) { + wlr_log(WLR_ERROR, "failed to allocate wlr_wl_pointer"); + return; + } + + char name[64] = {0}; + snprintf(name, sizeof(name), "wayland-pointer-%s", seat->name); + wlr_pointer_init(&pointer->wlr_pointer, &wl_pointer_impl, name); + + pointer->wlr_pointer.output_name = strdup(output->wlr_output.name); + + pointer->seat = seat; + pointer->output = output; + + wl_signal_add(&output->wlr_output.events.destroy, &pointer->output_destroy); + pointer->output_destroy.notify = pointer_output_destroy; + + wl_signal_emit_mutable(&seat->backend->backend.events.new_input, + &pointer->wlr_pointer.base); + + wl_list_insert(&seat->pointers, &pointer->link); +} + +void init_seat_pointer(struct wlr_wl_seat *seat) { + assert(seat->wl_pointer); + + struct wlr_wl_backend *backend = seat->backend; + + wl_list_init(&seat->pointers); + + struct wlr_wl_output *output; + wl_list_for_each(output, &backend->outputs, link) { + create_pointer(seat, output); + } + + if (backend->zwp_pointer_gestures_v1) { + uint32_t version = zwp_pointer_gestures_v1_get_version( + backend->zwp_pointer_gestures_v1); + + seat->gesture_swipe = zwp_pointer_gestures_v1_get_swipe_gesture( + backend->zwp_pointer_gestures_v1, seat->wl_pointer); + zwp_pointer_gesture_swipe_v1_add_listener(seat->gesture_swipe, + &gesture_swipe_impl, seat); + + seat->gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture( + backend->zwp_pointer_gestures_v1, seat->wl_pointer); + zwp_pointer_gesture_pinch_v1_add_listener(seat->gesture_pinch, + &gesture_pinch_impl, seat); + + if (version >= ZWP_POINTER_GESTURES_V1_GET_HOLD_GESTURE) { + seat->gesture_hold = zwp_pointer_gestures_v1_get_hold_gesture( + backend->zwp_pointer_gestures_v1, seat->wl_pointer); + zwp_pointer_gesture_hold_v1_add_listener(seat->gesture_hold, + &gesture_hold_impl, seat); + } + } + + if (backend->zwp_relative_pointer_manager_v1) { + seat->relative_pointer = + zwp_relative_pointer_manager_v1_get_relative_pointer( + backend->zwp_relative_pointer_manager_v1, seat->wl_pointer); + zwp_relative_pointer_v1_add_listener(seat->relative_pointer, + &relative_pointer_listener, seat); + } + + wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); +} + +void finish_seat_pointer(struct wlr_wl_seat *seat) { + assert(seat->wl_pointer); + + wl_pointer_release(seat->wl_pointer); + + struct wlr_wl_pointer *pointer, *tmp; + wl_list_for_each_safe(pointer, tmp, &seat->pointers, link) { + destroy_pointer(pointer); + } + + if (seat->gesture_swipe != NULL) { + zwp_pointer_gesture_swipe_v1_destroy(seat->gesture_swipe); + } + if (seat->gesture_pinch != NULL) { + zwp_pointer_gesture_pinch_v1_destroy(seat->gesture_pinch); + } + if (seat->gesture_hold != NULL) { + zwp_pointer_gesture_hold_v1_destroy(seat->gesture_hold); + } + if (seat->relative_pointer != NULL) { + zwp_relative_pointer_v1_destroy(seat->relative_pointer); + } + + seat->wl_pointer = NULL; + seat->active_pointer = NULL; +} diff --git a/backend/wayland/seat.c b/backend/wayland/seat.c new file mode 100644 index 0000000..1fa7e3e --- /dev/null +++ b/backend/wayland/seat.c @@ -0,0 +1,387 @@ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "backend/wayland.h" +#include "util/time.h" + +static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) { + close(fd); +} + +static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { + struct wlr_keyboard *keyboard = data; + + uint32_t *keycode_ptr; + wl_array_for_each(keycode_ptr, keys) { + struct wlr_keyboard_key_event event = { + .keycode = *keycode_ptr, + .state = WL_KEYBOARD_KEY_STATE_PRESSED, + .time_msec = get_current_time_msec(), + .update_state = false, + }; + wlr_keyboard_notify_key(keyboard, &event); + } +} + +static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface) { + struct wlr_keyboard *keyboard = data; + + size_t num_keycodes = keyboard->num_keycodes; + uint32_t pressed[num_keycodes + 1]; + memcpy(pressed, keyboard->keycodes, + num_keycodes * sizeof(uint32_t)); + + for (size_t i = 0; i < num_keycodes; ++i) { + uint32_t keycode = pressed[i]; + + struct wlr_keyboard_key_event event = { + .keycode = keycode, + .state = WL_KEYBOARD_KEY_STATE_RELEASED, + .time_msec = get_current_time_msec(), + .update_state = false, + }; + wlr_keyboard_notify_key(keyboard, &event); + } +} + +static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + struct wlr_keyboard *keyboard = data; + + struct wlr_keyboard_key_event wlr_event = { + .keycode = key, + .state = state, + .time_msec = time, + .update_state = false, + }; + wlr_keyboard_notify_key(keyboard, &wlr_event); +} + +static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) { + struct wlr_keyboard *keyboard = data; + wlr_keyboard_notify_modifiers(keyboard, mods_depressed, mods_latched, + mods_locked, group); +} + +static void keyboard_handle_repeat_info(void *data, + struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { + // This space is intentionally left blank +} + +static const struct wl_keyboard_listener keyboard_listener = { + .keymap = keyboard_handle_keymap, + .enter = keyboard_handle_enter, + .leave = keyboard_handle_leave, + .key = keyboard_handle_key, + .modifiers = keyboard_handle_modifiers, + .repeat_info = keyboard_handle_repeat_info +}; + +static const struct wlr_keyboard_impl keyboard_impl = { + .name = "wl-keyboard", +}; + +void init_seat_keyboard(struct wlr_wl_seat *seat) { + assert(seat->wl_keyboard); + + char name[128] = {0}; + snprintf(name, sizeof(name), "wayland-keyboard-%s", seat->name); + + wlr_keyboard_init(&seat->wlr_keyboard, &keyboard_impl, name); + wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener, + &seat->wlr_keyboard); + + wl_signal_emit_mutable(&seat->backend->backend.events.new_input, + &seat->wlr_keyboard.base); +} + +static void touch_coordinates_to_absolute(struct wlr_wl_seat *seat, + wl_fixed_t x, wl_fixed_t y, double *sx, double *sy) { + /** + * TODO: multi-output touch support + * Although the wayland backend supports multi-output pointers, the support + * for multi-output touch has been left on the side for simplicity reasons. + * If this is a feature you want/need, please open an issue on the wlroots + * tracker here https://gitlab.freedesktop.org/wlroots/wlroots/-/issues + */ + struct wlr_wl_output *output, *tmp; + wl_list_for_each_safe(output, tmp, &seat->backend->outputs, link) { + *sx = wl_fixed_to_double(x) / output->wlr_output.width; + *sy = wl_fixed_to_double(y) / output->wlr_output.height; + return; // Choose the first output in the list + } + + *sx = *sy = 0; +} + +static void touch_handle_down(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, struct wl_surface *surface, + int32_t id, wl_fixed_t x, wl_fixed_t y) { + struct wlr_wl_seat *seat = data; + struct wlr_touch *touch = &seat->wlr_touch; + + struct wlr_wl_touch_points *points = &seat->touch_points; + assert(points->len != sizeof(points->ids) / sizeof(points->ids[0])); + points->ids[points->len++] = id; + + struct wlr_touch_down_event event = { + .touch = touch, + .time_msec = time, + .touch_id = id, + }; + touch_coordinates_to_absolute(seat, x, y, &event.x, &event.y); + wl_signal_emit_mutable(&touch->events.down, &event); +} + +static bool remove_touch_point(struct wlr_wl_touch_points *points, int32_t id) { + size_t i = 0; + for (; i < points->len; i++) { + if (points->ids[i] == id) { + size_t remaining = points->len - i - 1; + memmove(&points->ids[i], &points->ids[i + 1], remaining * sizeof(id)); + points->len--; + return true; + } + } + return false; +} + +static void touch_handle_up(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, int32_t id) { + struct wlr_wl_seat *seat = data; + struct wlr_touch *touch = &seat->wlr_touch; + + remove_touch_point(&seat->touch_points, id); + + struct wlr_touch_up_event event = { + .touch = touch, + .time_msec = time, + .touch_id = id, + }; + wl_signal_emit_mutable(&touch->events.up, &event); +} + +static void touch_handle_motion(void *data, struct wl_touch *wl_touch, + uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { + struct wlr_wl_seat *seat = data; + struct wlr_touch *touch = &seat->wlr_touch; + + struct wlr_touch_motion_event event = { + .touch = touch, + .time_msec = time, + .touch_id = id, + }; + + touch_coordinates_to_absolute(seat, x, y, &event.x, &event.y); + wl_signal_emit_mutable(&touch->events.motion, &event); +} + +static void touch_handle_frame(void *data, struct wl_touch *wl_touch) { + struct wlr_wl_seat *seat = data; + wl_signal_emit_mutable(&seat->wlr_touch.events.frame, NULL); +} + +static void touch_handle_cancel(void *data, struct wl_touch *wl_touch) { + struct wlr_wl_seat *seat = data; + struct wlr_touch *touch = &seat->wlr_touch; + + // wayland's cancel event applies to all active touch points + for (size_t i = 0; i < seat->touch_points.len; i++) { + struct wlr_touch_cancel_event event = { + .touch = touch, + .time_msec = 0, + .touch_id = seat->touch_points.ids[i], + }; + wl_signal_emit_mutable(&touch->events.cancel, &event); + } + seat->touch_points.len = 0; +} + +static void touch_handle_shape(void *data, struct wl_touch *wl_touch, + int32_t id, wl_fixed_t major, wl_fixed_t minor) { + // no-op +} + +static void touch_handle_orientation(void *data, struct wl_touch *wl_touch, + int32_t id, wl_fixed_t orientation) { + // no-op +} + +static const struct wl_touch_listener touch_listener = { + .down = touch_handle_down, + .up = touch_handle_up, + .motion = touch_handle_motion, + .frame = touch_handle_frame, + .cancel = touch_handle_cancel, + .shape = touch_handle_shape, + .orientation = touch_handle_orientation, +}; + +static const struct wlr_touch_impl touch_impl = { + .name = "wl-touch", +}; + +void init_seat_touch(struct wlr_wl_seat *seat) { + assert(seat->wl_touch); + + char name[128] = {0}; + snprintf(name, sizeof(name), "wayland-touch-%s", seat->name); + + wlr_touch_init(&seat->wlr_touch, &touch_impl, name); + + struct wlr_wl_output *output; + wl_list_for_each(output, &seat->backend->outputs, link) { + /* Multi-output touch not supproted */ + seat->wlr_touch.output_name = strdup(output->wlr_output.name); + break; + } + + wl_touch_add_listener(seat->wl_touch, &touch_listener, seat); + wl_signal_emit_mutable(&seat->backend->backend.events.new_input, + &seat->wlr_touch.base); +} + +static const struct wl_seat_listener seat_listener; + +bool create_wl_seat(struct wl_seat *wl_seat, struct wlr_wl_backend *wl, + uint32_t global_name) { + struct wlr_wl_seat *seat = calloc(1, sizeof(*seat)); + if (!seat) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return false; + } + seat->wl_seat = wl_seat; + seat->backend = wl; + seat->global_name = global_name; + wl_list_insert(&wl->seats, &seat->link); + wl_seat_add_listener(wl_seat, &seat_listener, seat); + return true; +} + +void destroy_wl_seat(struct wlr_wl_seat *seat) { + if (seat->wl_touch) { + wl_touch_release(seat->wl_touch); + wlr_touch_finish(&seat->wlr_touch); + } + if (seat->wl_pointer) { + finish_seat_pointer(seat); + } + if (seat->wl_keyboard) { + wl_keyboard_release(seat->wl_keyboard); + + if (seat->backend->started) { + wlr_keyboard_finish(&seat->wlr_keyboard); + } + } + if (seat->zwp_tablet_seat_v2) { + finish_seat_tablet(seat); + } + + free(seat->name); + assert(seat->wl_seat); + wl_seat_destroy(seat->wl_seat); + + wl_list_remove(&seat->link); + free(seat); +} + +bool wlr_input_device_is_wl(struct wlr_input_device *dev) { + switch (dev->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + return wlr_keyboard_from_input_device(dev)->impl == &keyboard_impl; + case WLR_INPUT_DEVICE_POINTER: + return wlr_pointer_from_input_device(dev)->impl == &wl_pointer_impl; + case WLR_INPUT_DEVICE_TOUCH: + return wlr_touch_from_input_device(dev)->impl == &touch_impl; + case WLR_INPUT_DEVICE_TABLET: + return wlr_tablet_from_input_device(dev)-> impl == &wl_tablet_impl; + case WLR_INPUT_DEVICE_TABLET_PAD: + return wlr_tablet_pad_from_input_device(dev)->impl == &wl_tablet_pad_impl; + default: + return false; + } +} + +static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) { + struct wlr_wl_seat *seat = data; + struct wlr_wl_backend *backend = seat->backend; + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer == NULL) { + wlr_log(WLR_DEBUG, "seat '%s' offering pointer", seat->name); + + seat->wl_pointer = wl_seat_get_pointer(wl_seat); + init_seat_pointer(seat); + } + if (!(caps & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer != NULL) { + wlr_log(WLR_DEBUG, "seat '%s' dropping pointer", seat->name); + finish_seat_pointer(seat); + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && seat->wl_keyboard == NULL) { + wlr_log(WLR_DEBUG, "seat '%s' offering keyboard", seat->name); + + struct wl_keyboard *wl_keyboard = wl_seat_get_keyboard(wl_seat); + seat->wl_keyboard = wl_keyboard; + + if (backend->started) { + init_seat_keyboard(seat); + } + } + if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && seat->wl_keyboard != NULL) { + wlr_log(WLR_DEBUG, "seat '%s' dropping keyboard", seat->name); + + wl_keyboard_release(seat->wl_keyboard); + wlr_keyboard_finish(&seat->wlr_keyboard); + + seat->wl_keyboard = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch == NULL) { + wlr_log(WLR_DEBUG, "seat '%s' offering touch", seat->name); + + seat->wl_touch = wl_seat_get_touch(wl_seat); + if (backend->started) { + init_seat_touch(seat); + } + } + if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch != NULL) { + wlr_log(WLR_DEBUG, "seat '%s' dropping touch", seat->name); + + wl_touch_release(seat->wl_touch); + wlr_touch_finish(&seat->wlr_touch); + seat->wl_touch = NULL; + } +} + +static void seat_handle_name(void *data, struct wl_seat *wl_seat, + const char *name) { + struct wlr_wl_seat *seat = data; + free(seat->name); + seat->name = strdup(name); +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = seat_handle_name, +}; diff --git a/backend/wayland/tablet_v2.c b/backend/wayland/tablet_v2.c new file mode 100644 index 0000000..53e7c91 --- /dev/null +++ b/backend/wayland/tablet_v2.c @@ -0,0 +1,929 @@ + +#include +#include +#include +#include +#include +#include +#include + +#include "backend/wayland.h" +#include "util/time.h" + +#include "tablet-unstable-v2-client-protocol.h" + +struct tablet_tool { + /* static */ + struct wlr_wl_seat *seat; + + /* semi-static */ + struct wlr_wl_output *output; + double pre_x, pre_y; + + /* per frame */ + double x, y; + + double pressure; + double distance; + double tilt_x, tilt_y; + double rotation; + double slider; + double wheel_delta; + + bool is_in; + bool is_out; + + bool is_up; + bool is_down; +}; + +struct tablet_pad_ring { + struct wl_list link; // tablet_pad_group.rings + /* static */ + struct zwp_tablet_pad_ring_v2 *ring; + struct tablet_pad_group *group; + size_t index; + + /* per frame */ + enum wlr_tablet_pad_ring_source source; + double angle; + bool stopped; +}; + +struct tablet_pad_strip { + struct wl_list link; // tablet_pad_group.strips + struct zwp_tablet_pad_strip_v2 *strip; + struct tablet_pad_group *group; + size_t index; + + enum wlr_tablet_pad_strip_source source; + double position; + bool stopped; +}; + +struct tablet_pad_group { + struct zwp_tablet_pad_group_v2 *pad_group; + struct wlr_tablet_pad *pad; + unsigned int mode; + + struct wlr_tablet_pad_group group; + + struct wl_list rings; // tablet_pad_ring.link + struct wl_list strips; // tablet_pad_strips.link +}; + +static void handle_tablet_pad_ring_source(void *data, + struct zwp_tablet_pad_ring_v2 *zwp_tablet_pad_ring_v2, + uint32_t source) { + struct tablet_pad_ring *ring = data; + ring->source = source; +} + +static void handle_tablet_pad_ring_angle(void *data, + struct zwp_tablet_pad_ring_v2 *zwp_tablet_pad_ring_v2, + wl_fixed_t degrees) { + struct tablet_pad_ring *ring = data; + ring->angle = wl_fixed_to_double(degrees); +} + +static void handle_tablet_pad_ring_stop(void *data, + struct zwp_tablet_pad_ring_v2 *zwp_tablet_pad_ring_v2) { + struct tablet_pad_ring *ring = data; + ring->stopped = true; +} + +static void handle_tablet_pad_ring_frame(void *data, + struct zwp_tablet_pad_ring_v2 *zwp_tablet_pad_ring_v2, + uint32_t time) { + struct tablet_pad_ring *ring = data; + + struct wlr_tablet_pad_ring_event evt = { + .time_msec = time, + .source = ring->source, + .ring = ring->index, + .position = ring->angle, + .mode = ring->group->mode, + }; + + if (ring->angle >= 0) { + wl_signal_emit_mutable(&ring->group->pad->events.ring, &evt); + } + if (ring->stopped) { + evt.position = -1; + wl_signal_emit_mutable(&ring->group->pad->events.ring, &evt); + } + + ring->angle = -1; + ring->stopped = false; + ring->source = 0; +} + +static const struct zwp_tablet_pad_ring_v2_listener tablet_pad_ring_listener = { + .source = handle_tablet_pad_ring_source, + .angle = handle_tablet_pad_ring_angle, + .stop = handle_tablet_pad_ring_stop, + .frame = handle_tablet_pad_ring_frame, +}; + +static void handle_tablet_pad_strip_source(void *data, + struct zwp_tablet_pad_strip_v2 *zwp_tablet_pad_strip_v2, + uint32_t source) { + struct tablet_pad_strip *strip = data; + strip->source = source; +} + +static void handle_tablet_pad_strip_position(void *data, + struct zwp_tablet_pad_strip_v2 *zwp_tablet_pad_strip_v2, + uint32_t position) { + struct tablet_pad_strip *strip = data; + strip->position = (double) position / 65536.0; +} + +static void handle_tablet_pad_strip_stop(void *data, + struct zwp_tablet_pad_strip_v2 *zwp_tablet_pad_strip_v2) { + struct tablet_pad_strip *strip = data; + strip->stopped = true; +} + +static void handle_tablet_pad_strip_frame(void *data, + struct zwp_tablet_pad_strip_v2 *zwp_tablet_pad_strip_v2, + uint32_t time) { + struct tablet_pad_strip *strip = data; + + struct wlr_tablet_pad_strip_event evt = { + .time_msec = time, + .source = strip->source, + .strip = strip->index, + .position = strip->position, + .mode = strip->group->mode, + }; + + if (strip->position >= 0) { + wl_signal_emit_mutable(&strip->group->pad->events.strip, &evt); + } + if (strip->stopped) { + evt.position = -1; + wl_signal_emit_mutable(&strip->group->pad->events.strip, &evt); + } + + strip->position = -1; + strip->stopped = false; + strip->source = 0; +} + +static const struct zwp_tablet_pad_strip_v2_listener tablet_pad_strip_listener = { + .source = handle_tablet_pad_strip_source, + .position = handle_tablet_pad_strip_position, + .stop = handle_tablet_pad_strip_stop, + .frame = handle_tablet_pad_strip_frame, +}; + +static void handle_tablet_pad_group_buttons(void *data, + struct zwp_tablet_pad_group_v2 *pad_group, + struct wl_array *buttons) { + struct tablet_pad_group *group = data; + + free(group->group.buttons); + group->group.buttons = calloc(1, buttons->size); + if (!group->group.buttons) { + // FIXME: Add actual error handling + return; + } + + group->group.button_count = buttons->size / sizeof(int); + memcpy(group->group.buttons, buttons->data, buttons->size); +} + +static void handle_tablet_pad_group_modes(void *data, + struct zwp_tablet_pad_group_v2 *pad_group, uint32_t modes) { + struct tablet_pad_group *group = data; + + group->group.mode_count = modes; +} + +static void handle_tablet_pad_group_ring(void *data, + struct zwp_tablet_pad_group_v2 *pad_group, + struct zwp_tablet_pad_ring_v2 *ring) { + struct tablet_pad_group *group = data; + struct tablet_pad_ring *tablet_ring = calloc(1, sizeof(*tablet_ring)); + if (!tablet_ring) { + zwp_tablet_pad_ring_v2_destroy(ring); + return; + } + tablet_ring->index = group->pad->ring_count++; + tablet_ring->group = group; + zwp_tablet_pad_ring_v2_add_listener(ring, &tablet_pad_ring_listener, + tablet_ring); + + group->group.rings = realloc(group->group.rings, + ++group->group.ring_count * sizeof(unsigned int)); + group->group.rings[group->group.ring_count - 1] = + tablet_ring->index; +} + +static void handle_tablet_pad_group_strip(void *data, + struct zwp_tablet_pad_group_v2 *pad_group, + struct zwp_tablet_pad_strip_v2 *strip) { + struct tablet_pad_group *group = data; + struct tablet_pad_strip *tablet_strip = calloc(1, sizeof(*tablet_strip)); + if (!tablet_strip) { + zwp_tablet_pad_strip_v2_destroy(strip); + return; + } + tablet_strip->index = group->pad->strip_count++; + tablet_strip->group = group; + zwp_tablet_pad_strip_v2_add_listener(strip, &tablet_pad_strip_listener, + tablet_strip); + + group->group.strips = realloc(group->group.strips, + ++group->group.strip_count * sizeof(unsigned int)); + group->group.strips[group->group.strip_count - 1] = + tablet_strip->index; +} + +static void handle_tablet_pad_group_done(void *data, + struct zwp_tablet_pad_group_v2 *pad_group) { + /* Empty for now */ +} + +static void handle_tablet_pad_group_mode_switch(void *data, + struct zwp_tablet_pad_group_v2 *pad_group, + uint32_t time, uint32_t serial, uint32_t mode) { + struct tablet_pad_group *group = data; + group->mode = mode; +} + +static void destroy_tablet_pad_group(struct tablet_pad_group *group) { + /* No need to remove the link on strips rings as long as we do *not* + * wl_list_remove on the wl_groups ring/strip attributes here */ + struct tablet_pad_ring *ring, *tmp_ring; + wl_list_for_each_safe(ring, tmp_ring, &group->rings, link) { + zwp_tablet_pad_ring_v2_destroy(ring->ring); + free(ring); + } + + struct tablet_pad_strip *strip, *tmp_strip; + wl_list_for_each_safe(strip, tmp_strip, &group->strips, link) { + zwp_tablet_pad_strip_v2_destroy(strip->strip); + free(strip); + } + + zwp_tablet_pad_group_v2_destroy(group->pad_group); + + free(group->group.buttons); + free(group->group.strips); + free(group->group.rings); + wl_list_remove(&group->group.link); + + free(group); +} + +static const struct zwp_tablet_pad_group_v2_listener tablet_pad_group_listener = { + .buttons = handle_tablet_pad_group_buttons, + .modes = handle_tablet_pad_group_modes, + .ring = handle_tablet_pad_group_ring, + .strip = handle_tablet_pad_group_strip, + .done = handle_tablet_pad_group_done, + .mode_switch = handle_tablet_pad_group_mode_switch, +}; + +static void handle_tablet_pad_group(void *data, + struct zwp_tablet_pad_v2 *zwp_tablet_pad, + struct zwp_tablet_pad_group_v2 *pad_group) { + struct wlr_wl_seat *seat = data; + struct wlr_tablet_pad *pad = &seat->wlr_tablet_pad; + + struct tablet_pad_group *group = calloc(1, sizeof(*group)); + if (!group) { + wlr_log_errno(WLR_ERROR, "failed to allocate tablet_pad_group"); + zwp_tablet_pad_group_v2_destroy(pad_group); + return; + } + group->pad_group = pad_group; + group->pad = pad; + + wl_list_init(&group->rings); + wl_list_init(&group->strips); + + zwp_tablet_pad_group_v2_add_listener(pad_group, + &tablet_pad_group_listener, group); + + wl_list_insert(&pad->groups, &group->group.link); +} + +static void handle_tablet_pad_path(void *data, + struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2, const char *path) { + struct wlr_wl_seat *seat = data; + struct wlr_tablet_pad *tablet_pad = &seat->wlr_tablet_pad; + + char **dst = wl_array_add(&tablet_pad->paths, sizeof(char *)); + *dst = strdup(path); +} + +static void handle_tablet_pad_buttons(void *data, + struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2, uint32_t buttons) { + struct wlr_wl_seat *seat = data; + struct wlr_tablet_pad *tablet_pad = &seat->wlr_tablet_pad; + + tablet_pad->button_count = buttons; +} + +static void handle_tablet_pad_button(void *data, + struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2, + uint32_t time, uint32_t button, uint32_t state) { + struct wlr_wl_seat *seat = data; + struct wlr_tablet_pad_button_event evt = { + .time_msec = time, + .button = button, + .state = state, + .mode = 0, + .group = 0, + }; + + wl_signal_emit_mutable(&seat->wlr_tablet_pad.events.button, &evt); +} + +static void handle_tablet_pad_done(void *data, + struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2) { + struct wlr_wl_seat *seat = data; + wl_signal_emit_mutable(&seat->backend->backend.events.new_input, + &seat->wlr_tablet_pad.base); +} + +static void handle_tablet_pad_enter(void *data, + struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2, + uint32_t serial, struct zwp_tablet_v2 *tablet_p, + struct wl_surface *surface) { + struct wlr_wl_seat *seat = data; + assert(seat->zwp_tablet_v2 == tablet_p); + + wl_signal_emit_mutable(&seat->wlr_tablet_pad.events.attach_tablet, + &seat->wlr_tablet_tool); +} + +static void handle_tablet_pad_leave(void *data, + struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2, + uint32_t serial, struct wl_surface *surface) { + /* Empty. Probably staying that way, unless we want to create/destroy + * tablet on enter/leave events (ehh) */ +} + +static void handle_tablet_pad_removed(void *data, + struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2) { + struct wlr_wl_seat *seat = data; + + struct wlr_tablet_pad *tablet_pad = &seat->wlr_tablet_pad; + struct tablet_pad_group *group, *it; + wl_list_for_each_safe(group, it, &tablet_pad->groups, group.link) { + destroy_tablet_pad_group(group); + } + + wlr_tablet_pad_finish(tablet_pad); + zwp_tablet_pad_v2_destroy(seat->zwp_tablet_pad_v2); + seat->zwp_tablet_pad_v2 = NULL; +} + +static const struct zwp_tablet_pad_v2_listener tablet_pad_listener = { + .group = handle_tablet_pad_group, + .path = handle_tablet_pad_path, + .buttons = handle_tablet_pad_buttons, + .button = handle_tablet_pad_button, + .done = handle_tablet_pad_done, + .enter = handle_tablet_pad_enter, + .leave = handle_tablet_pad_leave, + .removed = handle_tablet_pad_removed, +}; + +const struct wlr_tablet_pad_impl wl_tablet_pad_impl = { + .name = "wl-tablet-pad", +}; + +static void handle_pad_added(void *data, + struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, + struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2) { + struct wlr_wl_seat *seat = data; + if (seat->zwp_tablet_pad_v2 != NULL) { + wlr_log(WLR_ERROR, "zwp_tablet_pad_v2 is already present"); + return; + } + + seat->zwp_tablet_pad_v2 = zwp_tablet_pad_v2; + zwp_tablet_pad_v2_add_listener(zwp_tablet_pad_v2, &tablet_pad_listener, + seat); + + wlr_tablet_pad_init(&seat->wlr_tablet_pad, &wl_tablet_pad_impl, + "wlr_tablet_v2"); +} + +static void handle_tablet_tool_done(void *data, + struct zwp_tablet_tool_v2 *id) { + /* empty */ +} + +static enum wlr_tablet_tool_type tablet_type_to_wlr_type( + enum zwp_tablet_tool_v2_type type) { + switch (type) { + case ZWP_TABLET_TOOL_V2_TYPE_PEN: + return WLR_TABLET_TOOL_TYPE_PEN; + case ZWP_TABLET_TOOL_V2_TYPE_ERASER: + return WLR_TABLET_TOOL_TYPE_ERASER; + case ZWP_TABLET_TOOL_V2_TYPE_BRUSH: + return WLR_TABLET_TOOL_TYPE_BRUSH; + case ZWP_TABLET_TOOL_V2_TYPE_PENCIL: + return WLR_TABLET_TOOL_TYPE_PENCIL; + case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH: + return WLR_TABLET_TOOL_TYPE_AIRBRUSH; + case ZWP_TABLET_TOOL_V2_TYPE_MOUSE: + return WLR_TABLET_TOOL_TYPE_MOUSE; + case ZWP_TABLET_TOOL_V2_TYPE_LENS: + return WLR_TABLET_TOOL_TYPE_LENS; + case ZWP_TABLET_TOOL_V2_TYPE_FINGER: + // unused, see: + // https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/18 + abort(); + } + abort(); // unreachable +} + +static void handle_tablet_tool_type(void *data, + struct zwp_tablet_tool_v2 *id, uint32_t tool_type) { + struct tablet_tool *tool = data; + struct wlr_tablet_tool *wlr_tool = &tool->seat->wlr_tablet_tool; + wlr_tool->type = tablet_type_to_wlr_type(tool_type); +} + +static void handle_tablet_tool_serial(void *data, + struct zwp_tablet_tool_v2 *id, uint32_t high, uint32_t low) { + struct tablet_tool *tool = data; + struct wlr_tablet_tool *wlr_tool = &tool->seat->wlr_tablet_tool; + wlr_tool->hardware_serial = ((uint64_t) high) << 32 | (uint64_t) low; +} + +static void handle_tablet_tool_id_wacom(void *data, + struct zwp_tablet_tool_v2 *id, uint32_t high, uint32_t low) { + struct tablet_tool *tool = data; + struct wlr_tablet_tool *wlr_tool = &tool->seat->wlr_tablet_tool; + wlr_tool->hardware_wacom = ((uint64_t) high) << 32 | (uint64_t) low; +} + +static void handle_tablet_tool_capability(void *data, + struct zwp_tablet_tool_v2 *id, uint32_t capability) { + struct tablet_tool *tool = data; + struct wlr_tablet_tool *wlr_tool = &tool->seat->wlr_tablet_tool; + + /* One event is sent for each capability */ + switch (capability) { + case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT: + wlr_tool->tilt = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE: + wlr_tool->pressure = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE: + wlr_tool->distance = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION: + wlr_tool->rotation = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER: + wlr_tool->slider = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL: + wlr_tool->wheel = true; + break; + } +} + +static void handle_tablet_tool_proximity_in(void *data, + struct zwp_tablet_tool_v2 *id, uint32_t serial, + struct zwp_tablet_v2 *tablet_id, struct wl_surface *surface) { + struct tablet_tool *tool = data; + assert(tablet_id == tool->seat->zwp_tablet_v2); + + struct wlr_wl_output *output = get_wl_output_from_surface(tool->seat->backend, surface); + if (output == NULL) { + return; + } + + tool->is_in = true; + tool->output = output; +} + +static void handle_tablet_tool_proximity_out(void *data, + struct zwp_tablet_tool_v2 *id) { + struct tablet_tool *tool = data; + tool->is_out = true; + tool->output = NULL; +} + +static void handle_tablet_tool_down(void *data, struct zwp_tablet_tool_v2 *id, + unsigned int serial) { + struct tablet_tool *tool = data; + tool->is_down = true; +} + +static void handle_tablet_tool_up(void *data, struct zwp_tablet_tool_v2 *id) { + struct tablet_tool *tool = data; + tool->is_up = true; +} + +static void handle_tablet_tool_motion(void *data, struct zwp_tablet_tool_v2 *id, + wl_fixed_t x, wl_fixed_t y) { + struct tablet_tool *tool = data; + struct wlr_wl_output *output = tool->output; + assert(output); + + tool->x = wl_fixed_to_double(x) / output->wlr_output.width; + tool->y = wl_fixed_to_double(y) / output->wlr_output.height; +} + +static void handle_tablet_tool_pressure(void *data, + struct zwp_tablet_tool_v2 *id, uint32_t pressure) { + struct tablet_tool *tool = data; + tool->pressure = (double) pressure / 65535.0; +} + +static void handle_tablet_tool_distance(void *data, + struct zwp_tablet_tool_v2 *id, uint32_t distance) { + struct tablet_tool *tool = data; + tool->distance = (double) distance / 65535.0; +} + +static void handle_tablet_tool_tilt(void *data, struct zwp_tablet_tool_v2 *id, + wl_fixed_t x, wl_fixed_t y) { + struct tablet_tool *tool = data; + tool->tilt_x = wl_fixed_to_double(x); + tool->tilt_y = wl_fixed_to_double(y); +} + +static void handle_tablet_tool_rotation(void *data, + struct zwp_tablet_tool_v2 *id, wl_fixed_t rotation) { + struct tablet_tool *tool = data; + tool->rotation = wl_fixed_to_double(rotation); +} + +static void handle_tablet_tool_slider(void *data, struct zwp_tablet_tool_v2 *id, + int slider) { + struct tablet_tool *tool = data; + tool->slider = (double) slider / 65535.0;; +} + +// TODO: This looks wrong :/ +static void handle_tablet_tool_wheel(void *data, struct zwp_tablet_tool_v2 *id, + wl_fixed_t degree, int clicks) { + struct tablet_tool *tool = data; + tool->wheel_delta = wl_fixed_to_double(degree); +} + +static void handle_tablet_tool_button(void *data, + struct zwp_tablet_tool_v2 *id, + uint32_t serial, uint32_t button, uint32_t state) { + struct tablet_tool *tool = data; + struct wlr_wl_seat *seat = tool->seat; + struct wlr_tablet *tablet = &seat->wlr_tablet; + + struct wlr_tablet_tool_button_event evt = { + .tablet = tablet, + .tool = &seat->wlr_tablet_tool, + .time_msec = get_current_time_msec(), + .button = button, + .state = state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_RELEASED ? + WLR_BUTTON_RELEASED : WLR_BUTTON_PRESSED, + }; + + wl_signal_emit_mutable(&tablet->events.button, &evt); +} + +static void clear_tablet_tool_values(struct tablet_tool *tool) { + tool->is_out = tool->is_in = false; + tool->is_up = tool->is_down = false; + tool->x = tool->y = NAN; + tool->pressure = NAN; + tool->distance = NAN; + tool->tilt_x = tool->tilt_y = NAN; + tool->rotation = NAN; + tool->slider = NAN; + tool->wheel_delta = NAN; +} + +static void handle_tablet_tool_frame(void *data, + struct zwp_tablet_tool_v2 *id, + uint32_t time) { + struct tablet_tool *tool = data; + struct wlr_wl_seat *seat = tool->seat; + + if (tool->is_out && tool->is_in) { + /* we got a tablet tool coming in and out of proximity before + * we could process it. Just ignore anything it did */ + goto clear_values; + } + struct wlr_tablet *tablet = &seat->wlr_tablet; + + if (tool->is_in) { + struct wlr_tablet_tool_proximity_event evt = { + .tablet = tablet, + .tool = &seat->wlr_tablet_tool, + .time_msec = time, + .x = tool->x, + .y = tool->y, + .state = WLR_TABLET_TOOL_PROXIMITY_IN, + }; + + wl_signal_emit_mutable(&tablet->events.proximity, &evt); + } + + { + struct wlr_tablet_tool_axis_event evt = { + .tablet = tablet, + .tool = &seat->wlr_tablet_tool, + .time_msec = time, + .updated_axes = 0, + }; + + if (!isnan(tool->x) && !tool->is_in) { + evt.updated_axes |= WLR_TABLET_TOOL_AXIS_X; + evt.x = tool->x; + } + + if (!isnan(tool->y) && !tool->is_in) { + evt.updated_axes |= WLR_TABLET_TOOL_AXIS_Y; + evt.y = tool->y; + } + + if (!isnan(tool->pressure)) { + evt.updated_axes |= WLR_TABLET_TOOL_AXIS_PRESSURE; + evt.pressure = tool->pressure; + } + + if (!isnan(tool->distance)) { + evt.updated_axes |= WLR_TABLET_TOOL_AXIS_DISTANCE; + evt.distance = tool->distance; + } + + if (!isnan(tool->tilt_x)) { + evt.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_X; + evt.tilt_x = tool->tilt_x; + } + + if (!isnan(tool->tilt_y)) { + evt.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_Y; + evt.tilt_y = tool->tilt_y; + } + + if (!isnan(tool->rotation)) { + evt.updated_axes |= WLR_TABLET_TOOL_AXIS_ROTATION; + evt.rotation = tool->rotation; + } + + if (!isnan(tool->slider)) { + evt.updated_axes |= WLR_TABLET_TOOL_AXIS_SLIDER; + evt.slider = tool->slider; + } + + if (!isnan(tool->wheel_delta)) { + evt.updated_axes |= WLR_TABLET_TOOL_AXIS_WHEEL; + evt.wheel_delta = tool->wheel_delta; + } + + if (evt.updated_axes) { + wl_signal_emit_mutable(&tablet->events.axis, &evt); + } + } + + /* This will always send down then up if we got both. + * Maybe we should send them right away, in case we get up then both in + * series? + * Downside: Here we have the frame time, if we sent right away, we + * need to generate the time */ + if (tool->is_down) { + struct wlr_tablet_tool_tip_event evt = { + .tablet = tablet, + .tool = &seat->wlr_tablet_tool, + .time_msec = time, + .x = tool->x, + .y = tool->y, + .state = WLR_TABLET_TOOL_TIP_DOWN, + }; + + wl_signal_emit_mutable(&tablet->events.tip, &evt); + } + + if (tool->is_up) { + struct wlr_tablet_tool_tip_event evt = { + .tablet = tablet, + .tool = &seat->wlr_tablet_tool, + .time_msec = time, + .x = tool->x, + .y = tool->y, + .state = WLR_TABLET_TOOL_TIP_UP, + }; + + wl_signal_emit_mutable(&tablet->events.tip, &evt); + } + + if (tool->is_out) { + struct wlr_tablet_tool_proximity_event evt = { + .tablet = tablet, + .tool = &seat->wlr_tablet_tool, + .time_msec = time, + .x = tool->x, + .y = tool->y, + .state = WLR_TABLET_TOOL_PROXIMITY_OUT, + }; + + wl_signal_emit_mutable(&tablet->events.proximity, &evt); + } + +clear_values: + clear_tablet_tool_values(tool); +} + +static void handle_tablet_tool_removed(void *data, + struct zwp_tablet_tool_v2 *id) { + struct tablet_tool *tool = data; + struct wlr_wl_seat *seat = tool->seat; + + zwp_tablet_tool_v2_destroy(seat->zwp_tablet_tool_v2); + seat->zwp_tablet_tool_v2 = NULL; + + free(tool); +} + +static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = { + .removed = handle_tablet_tool_removed, + .done = handle_tablet_tool_done, + .type = handle_tablet_tool_type, + .hardware_serial = handle_tablet_tool_serial, + .hardware_id_wacom = handle_tablet_tool_id_wacom, + .capability = handle_tablet_tool_capability, + + .proximity_in = handle_tablet_tool_proximity_in, + .proximity_out = handle_tablet_tool_proximity_out, + .down = handle_tablet_tool_down, + .up = handle_tablet_tool_up, + + .motion = handle_tablet_tool_motion, + .pressure = handle_tablet_tool_pressure, + .distance = handle_tablet_tool_distance, + .tilt = handle_tablet_tool_tilt, + .rotation = handle_tablet_tool_rotation, + .slider = handle_tablet_tool_slider, + .wheel = handle_tablet_tool_wheel, + .button = handle_tablet_tool_button, + .frame = handle_tablet_tool_frame, +}; + +static void handle_tool_added(void *data, + struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) { + struct wlr_wl_seat *seat = data; + if (seat->zwp_tablet_tool_v2 != NULL) { + wlr_log(WLR_ERROR, "zwp_tablet_tool_v2 already present"); + return; + } + + wl_signal_init(&seat->wlr_tablet_tool.events.destroy); + + struct tablet_tool *tool = calloc(1, sizeof(*tool)); + if (tool == NULL) { + wlr_log_errno(WLR_ERROR, "failed to allocate tablet_tool"); + zwp_tablet_tool_v2_destroy(zwp_tablet_tool_v2); + return; + } + + tool->seat = seat; + clear_tablet_tool_values(tool); + + seat->zwp_tablet_tool_v2 = zwp_tablet_tool_v2; + zwp_tablet_tool_v2_add_listener(seat->zwp_tablet_tool_v2, &tablet_tool_listener, + tool); +} + +static void handle_tablet_name(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, + const char *name) { + struct wlr_wl_seat *seat = data; + struct wlr_tablet *tablet = &seat->wlr_tablet; + + free(tablet->base.name); + tablet->base.name = strdup(name); +} + +static void handle_tablet_id(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, + uint32_t vid, uint32_t pid) { + struct wlr_wl_seat *seat = data; + struct wlr_tablet *tablet = &seat->wlr_tablet; + + tablet->usb_vendor_id = vid; + tablet->usb_product_id = pid; +} + +static void handle_tablet_path(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, + const char *path) { + struct wlr_wl_seat *seat = data; + struct wlr_tablet *tablet = &seat->wlr_tablet; + + char **dst = wl_array_add(&tablet->paths, sizeof(char *)); + *dst = strdup(path); +} + +static void handle_tablet_done(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) { + struct wlr_wl_seat *seat = data; + + wl_signal_emit_mutable(&seat->backend->backend.events.new_input, + &seat->wlr_tablet.base); +} + +static void handle_tablet_removed(void *data, + struct zwp_tablet_v2 *zwp_tablet_v2) { + struct wlr_wl_seat *seat = data; + + wlr_tablet_finish(&seat->wlr_tablet); + zwp_tablet_v2_destroy(seat->zwp_tablet_v2); + seat->zwp_tablet_v2 = NULL; +} + +static const struct zwp_tablet_v2_listener tablet_listener = { + .name = handle_tablet_name, + .id = handle_tablet_id, + .path = handle_tablet_path, + .done = handle_tablet_done, + .removed = handle_tablet_removed, +}; + +const struct wlr_tablet_impl wl_tablet_impl = { + .name = "wl-tablet-tool", +}; + +static void handle_tab_added(void *data, + struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, + struct zwp_tablet_v2 *zwp_tablet_v2) { + struct wlr_wl_seat *seat = data; + if (seat->zwp_tablet_v2 != NULL) { + wlr_log(WLR_ERROR, "zwp_tablet_v2 already present"); + return; + } + + seat->zwp_tablet_v2 = zwp_tablet_v2; + zwp_tablet_v2_add_listener(zwp_tablet_v2, &tablet_listener, seat); + + wlr_tablet_init(&seat->wlr_tablet, &wl_tablet_impl, "wlr_tablet_v2"); +} + +static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { + .tablet_added = handle_tab_added, + .tool_added = handle_tool_added, + .pad_added = handle_pad_added, +}; + +void init_seat_tablet(struct wlr_wl_seat *seat) { + struct zwp_tablet_manager_v2 *manager = seat->backend->tablet_manager; + assert(manager); + + /** + * TODO: multi tablet support + * The wlr_wl_seat should support multiple tablet_v2 devices, but for + * the sake of simplicity, it supports only one device of each. + * If this is a feature you want/need, please open an issue on the wlroots + * tracker here https://gitlab.freedesktop.org/wlroots/wlroots/-/issues + */ + + seat->zwp_tablet_seat_v2 = + zwp_tablet_manager_v2_get_tablet_seat(manager, seat->wl_seat); + if (seat->zwp_tablet_seat_v2 == NULL) { + wlr_log(WLR_ERROR, "failed to get zwp_tablet_manager_v2 from seat '%s'", + seat->name); + return; + } + + zwp_tablet_seat_v2_add_listener(seat->zwp_tablet_seat_v2, + &tablet_seat_listener, seat); +} + +void finish_seat_tablet(struct wlr_wl_seat *seat) { + if (seat->zwp_tablet_v2 != NULL) { + wlr_tablet_finish(&seat->wlr_tablet); + zwp_tablet_v2_destroy(seat->zwp_tablet_v2); + } + + if (seat->zwp_tablet_tool_v2 != NULL) { + struct tablet_tool *tool = + zwp_tablet_tool_v2_get_user_data(seat->zwp_tablet_tool_v2); + free(tool); + + zwp_tablet_tool_v2_destroy(seat->zwp_tablet_tool_v2); + } + + if (seat->zwp_tablet_pad_v2 != NULL) { + struct wlr_tablet_pad *tablet_pad = &seat->wlr_tablet_pad; + struct tablet_pad_group *group, *it; + wl_list_for_each_safe(group, it, &tablet_pad->groups, group.link) { + destroy_tablet_pad_group(group); + } + + wlr_tablet_pad_finish(tablet_pad); + zwp_tablet_pad_v2_destroy(seat->zwp_tablet_pad_v2); + } + + zwp_tablet_seat_v2_destroy(seat->zwp_tablet_seat_v2); + seat->zwp_tablet_seat_v2 = NULL; +} diff --git a/backend/x11/backend.c b/backend/x11/backend.c new file mode 100644 index 0000000..ae6d55e --- /dev/null +++ b/backend/x11/backend.c @@ -0,0 +1,736 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "backend/x11.h" +#include "render/drm_format_set.h" + +// See dri2_format_for_depth in mesa +const struct wlr_x11_format formats[] = { + { .drm = DRM_FORMAT_XRGB8888, .depth = 24, .bpp = 32 }, + { .drm = DRM_FORMAT_ARGB8888, .depth = 32, .bpp = 32 }, +}; + +static const struct wlr_x11_format *x11_format_from_depth(uint8_t depth) { + for (size_t i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) { + if (formats[i].depth == depth) { + return &formats[i]; + } + } + return NULL; +} + +struct wlr_x11_output *get_x11_output_from_window_id( + struct wlr_x11_backend *x11, xcb_window_t window) { + struct wlr_x11_output *output; + wl_list_for_each(output, &x11->outputs, link) { + if (output->win == window) { + return output; + } + } + return NULL; +} + +static void handle_x11_error(struct wlr_x11_backend *x11, xcb_value_error_t *ev); +static void handle_x11_unknown_event(struct wlr_x11_backend *x11, + xcb_generic_event_t *ev); + +static void handle_x11_event(struct wlr_x11_backend *x11, + xcb_generic_event_t *event) { + switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { + case XCB_EXPOSE: { + xcb_expose_event_t *ev = (xcb_expose_event_t *)event; + struct wlr_x11_output *output = + get_x11_output_from_window_id(x11, ev->window); + if (output != NULL) { + pixman_region32_union_rect( + &output->exposed, &output->exposed, + ev->x, ev->y, ev->width, ev->height); + wlr_output_update_needs_frame(&output->wlr_output); + } + break; + } + case XCB_CONFIGURE_NOTIFY: { + xcb_configure_notify_event_t *ev = + (xcb_configure_notify_event_t *)event; + struct wlr_x11_output *output = + get_x11_output_from_window_id(x11, ev->window); + if (output != NULL) { + handle_x11_configure_notify(output, ev); + } + break; + } + case XCB_CLIENT_MESSAGE: { + xcb_client_message_event_t *ev = (xcb_client_message_event_t *)event; + if (ev->data.data32[0] == x11->atoms.wm_delete_window) { + struct wlr_x11_output *output = + get_x11_output_from_window_id(x11, ev->window); + if (output != NULL) { + wlr_output_destroy(&output->wlr_output); + } + } else { + wlr_log(WLR_DEBUG, "Unhandled client message %"PRIu32, + ev->data.data32[0]); + } + break; + } + case XCB_GE_GENERIC: { + xcb_ge_generic_event_t *ev = (xcb_ge_generic_event_t *)event; + if (ev->extension == x11->xinput_opcode) { + handle_x11_xinput_event(x11, ev); + } else if (ev->extension == x11->present_opcode) { + handle_x11_present_event(x11, ev); + } else { + handle_x11_unknown_event(x11, event); + } + break; + } + case 0: { + xcb_value_error_t *ev = (xcb_value_error_t *)event; + handle_x11_error(x11, ev); + break; + } + case XCB_UNMAP_NOTIFY: + case XCB_MAP_NOTIFY: + break; + default: + handle_x11_unknown_event(x11, event); + break; + } +} + +static int x11_event(int fd, uint32_t mask, void *data) { + struct wlr_x11_backend *x11 = data; + + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { + if (mask & WL_EVENT_ERROR) { + wlr_log(WLR_ERROR, "Failed to read from X11 server"); + } + wlr_backend_destroy(&x11->backend); + return 0; + } + + xcb_generic_event_t *e; + while ((e = xcb_poll_for_event(x11->xcb))) { + handle_x11_event(x11, e); + free(e); + } + + int ret = xcb_connection_has_error(x11->xcb); + if (ret != 0) { + wlr_log(WLR_ERROR, "X11 connection error (%d)", ret); + wlr_backend_destroy(&x11->backend); + } + + return 0; +} + +struct wlr_x11_backend *get_x11_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_x11(wlr_backend)); + struct wlr_x11_backend *backend = wl_container_of(wlr_backend, backend, backend); + return backend; +} + +static bool backend_start(struct wlr_backend *backend) { + struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend); + x11->started = true; + + wlr_log(WLR_INFO, "Starting X11 backend"); + + wl_signal_emit_mutable(&x11->backend.events.new_input, &x11->keyboard.base); + + for (size_t i = 0; i < x11->requested_outputs; ++i) { + wlr_x11_output_create(&x11->backend); + } + + return true; +} + +static void backend_destroy(struct wlr_backend *backend) { + if (!backend) { + return; + } + + struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend); + + struct wlr_x11_output *output, *tmp; + wl_list_for_each_safe(output, tmp, &x11->outputs, link) { + wlr_output_destroy(&output->wlr_output); + } + + wlr_keyboard_finish(&x11->keyboard); + + wlr_backend_finish(backend); + + if (x11->event_source) { + wl_event_source_remove(x11->event_source); + } + wl_list_remove(&x11->event_loop_destroy.link); + + wlr_drm_format_set_finish(&x11->primary_dri3_formats); + wlr_drm_format_set_finish(&x11->primary_shm_formats); + wlr_drm_format_set_finish(&x11->dri3_formats); + wlr_drm_format_set_finish(&x11->shm_formats); + +#if HAVE_XCB_ERRORS + xcb_errors_context_free(x11->errors_context); +#endif + + close(x11->drm_fd); + xcb_disconnect(x11->xcb); + free(x11); +} + +static int backend_get_drm_fd(struct wlr_backend *backend) { + struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend); + return x11->drm_fd; +} + +static uint32_t get_buffer_caps(struct wlr_backend *backend) { + struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend); + return (x11->have_dri3 ? WLR_BUFFER_CAP_DMABUF : 0) + | (x11->have_shm ? WLR_BUFFER_CAP_SHM : 0); +} + +static const struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_drm_fd = backend_get_drm_fd, + .get_buffer_caps = get_buffer_caps, +}; + +bool wlr_backend_is_x11(struct wlr_backend *backend) { + return backend->impl == &backend_impl; +} + +static void handle_event_loop_destroy(struct wl_listener *listener, void *data) { + struct wlr_x11_backend *x11 = wl_container_of(listener, x11, event_loop_destroy); + backend_destroy(&x11->backend); +} + +static xcb_depth_t *get_depth(xcb_screen_t *screen, uint8_t depth) { + xcb_depth_iterator_t iter = xcb_screen_allowed_depths_iterator(screen); + while (iter.rem > 0) { + if (iter.data->depth == depth) { + return iter.data; + } + xcb_depth_next(&iter); + } + return NULL; +} + +static xcb_visualid_t pick_visualid(xcb_depth_t *depth) { + xcb_visualtype_t *visuals = xcb_depth_visuals(depth); + for (int i = 0; i < xcb_depth_visuals_length(depth); i++) { + if (visuals[i]._class == XCB_VISUAL_CLASS_TRUE_COLOR) { + return visuals[i].visual_id; + } + } + return 0; +} + +static int query_dri3_drm_fd(struct wlr_x11_backend *x11) { + xcb_dri3_open_cookie_t open_cookie = + xcb_dri3_open(x11->xcb, x11->screen->root, 0); + xcb_dri3_open_reply_t *open_reply = + xcb_dri3_open_reply(x11->xcb, open_cookie, NULL); + if (open_reply == NULL) { + wlr_log(WLR_ERROR, "Failed to open DRI3"); + return -1; + } + + int *open_fds = xcb_dri3_open_reply_fds(x11->xcb, open_reply); + if (open_fds == NULL) { + wlr_log(WLR_ERROR, "xcb_dri3_open_reply_fds() failed"); + free(open_reply); + return -1; + } + + assert(open_reply->nfd == 1); + int drm_fd = open_fds[0]; + + free(open_reply); + + int flags = fcntl(drm_fd, F_GETFD); + if (flags < 0) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM FD flags"); + close(drm_fd); + return -1; + } + if (fcntl(drm_fd, F_SETFD, flags | FD_CLOEXEC) < 0) { + wlr_log_errno(WLR_ERROR, "Failed to set DRM FD flags"); + close(drm_fd); + return -1; + } + + if (drmGetNodeTypeFromFd(drm_fd) != DRM_NODE_RENDER) { + char *render_name = drmGetRenderDeviceNameFromFd(drm_fd); + if (render_name == NULL) { + wlr_log(WLR_ERROR, "Failed to get DRM render node name from DRM FD"); + close(drm_fd); + return -1; + } + + close(drm_fd); + drm_fd = open(render_name, O_RDWR | O_CLOEXEC); + if (drm_fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open DRM render node '%s'", render_name); + free(render_name); + return -1; + } + + free(render_name); + } + + return drm_fd; +} + +static bool query_dri3_modifiers(struct wlr_x11_backend *x11, + const struct wlr_x11_format *format) { + if (x11->dri3_major_version == 1 && x11->dri3_minor_version < 2) { + return true; // GetSupportedModifiers requires DRI3 1.2 + } + + // Query the root window's supported modifiers, because we only care about + // screen_modifiers for now + xcb_dri3_get_supported_modifiers_cookie_t modifiers_cookie = + xcb_dri3_get_supported_modifiers(x11->xcb, x11->screen->root, + format->depth, format->bpp); + xcb_dri3_get_supported_modifiers_reply_t *modifiers_reply = + xcb_dri3_get_supported_modifiers_reply(x11->xcb, modifiers_cookie, + NULL); + if (!modifiers_reply) { + wlr_log(WLR_ERROR, "Failed to get DMA-BUF modifiers supported by " + "the X11 server for the format 0x%"PRIX32, format->drm); + return false; + } + + // If modifiers aren't supported, DRI3 will return an empty list + const uint64_t *modifiers = + xcb_dri3_get_supported_modifiers_screen_modifiers(modifiers_reply); + int modifiers_len = + xcb_dri3_get_supported_modifiers_screen_modifiers_length(modifiers_reply); + for (int i = 0; i < modifiers_len; i++) { + wlr_drm_format_set_add(&x11->dri3_formats, format->drm, modifiers[i]); + } + + free(modifiers_reply); + return true; +} + +static bool query_formats(struct wlr_x11_backend *x11) { + xcb_depth_iterator_t iter = xcb_screen_allowed_depths_iterator(x11->screen); + while (iter.rem > 0) { + uint8_t depth = iter.data->depth; + + const struct wlr_x11_format *format = x11_format_from_depth(depth); + if (format != NULL) { + if (x11->have_shm) { + wlr_drm_format_set_add(&x11->shm_formats, format->drm, + DRM_FORMAT_MOD_INVALID); + } + + if (x11->have_dri3) { + // X11 always supports implicit modifiers + wlr_drm_format_set_add(&x11->dri3_formats, format->drm, + DRM_FORMAT_MOD_INVALID); + if (!query_dri3_modifiers(x11, format)) { + return false; + } + } + } + + xcb_depth_next(&iter); + } + + return true; +} + +static void x11_get_argb32(struct wlr_x11_backend *x11) { + xcb_render_query_pict_formats_cookie_t cookie = + xcb_render_query_pict_formats(x11->xcb); + xcb_render_query_pict_formats_reply_t *reply = + xcb_render_query_pict_formats_reply(x11->xcb, cookie, NULL); + if (!reply) { + wlr_log(WLR_ERROR, "Did not get any reply from xcb_render_query_pict_formats"); + return; + } + + xcb_render_pictforminfo_t *format = + xcb_render_util_find_standard_format(reply, XCB_PICT_STANDARD_ARGB_32); + + if (format == NULL) { + wlr_log(WLR_DEBUG, "No ARGB_32 render format"); + free(reply); + return; + } + + x11->argb32 = format->id; + free(reply); +} + +struct wlr_backend *wlr_x11_backend_create(struct wl_event_loop *loop, + const char *x11_display) { + wlr_log(WLR_INFO, "Creating X11 backend"); + + struct wlr_x11_backend *x11 = calloc(1, sizeof(*x11)); + if (!x11) { + return NULL; + } + + wlr_backend_init(&x11->backend, &backend_impl); + x11->event_loop = loop; + wl_list_init(&x11->outputs); + + x11->xcb = xcb_connect(x11_display, NULL); + if (!x11->xcb || xcb_connection_has_error(x11->xcb)) { + wlr_log(WLR_ERROR, "Failed to open xcb connection"); + goto error_x11; + } + + struct { + const char *name; + xcb_intern_atom_cookie_t cookie; + xcb_atom_t *atom; + } atom[] = { + { .name = "WM_PROTOCOLS", .atom = &x11->atoms.wm_protocols }, + { .name = "WM_DELETE_WINDOW", .atom = &x11->atoms.wm_delete_window }, + { .name = "_NET_WM_NAME", .atom = &x11->atoms.net_wm_name }, + { .name = "UTF8_STRING", .atom = &x11->atoms.utf8_string }, + { .name = "_VARIABLE_REFRESH", .atom = &x11->atoms.variable_refresh }, + }; + + for (size_t i = 0; i < sizeof(atom) / sizeof(atom[0]); ++i) { + atom[i].cookie = xcb_intern_atom(x11->xcb, + true, strlen(atom[i].name), atom[i].name); + } + + for (size_t i = 0; i < sizeof(atom) / sizeof(atom[0]); ++i) { + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply( + x11->xcb, atom[i].cookie, NULL); + + if (reply) { + *atom[i].atom = reply->atom; + free(reply); + } else { + *atom[i].atom = XCB_ATOM_NONE; + } + } + + const xcb_query_extension_reply_t *ext; + + // DRI3 extension + + ext = xcb_get_extension_data(x11->xcb, &xcb_dri3_id); + if (ext && ext->present) { + xcb_dri3_query_version_cookie_t dri3_cookie = + xcb_dri3_query_version(x11->xcb, 1, 2); + xcb_dri3_query_version_reply_t *dri3_reply = + xcb_dri3_query_version_reply(x11->xcb, dri3_cookie, NULL); + if (dri3_reply) { + if (dri3_reply->major_version >= 1) { + x11->have_dri3 = true; + x11->dri3_major_version = dri3_reply->major_version; + x11->dri3_minor_version = dri3_reply->minor_version; + } else { + wlr_log(WLR_INFO, "X11 does not support required DRI3 version " + "(has %"PRIu32".%"PRIu32", want 1.0)", + dri3_reply->major_version, dri3_reply->minor_version); + } + free(dri3_reply); + } else { + wlr_log(WLR_INFO, "X11 does not support required DRi3 version"); + } + } else { + wlr_log(WLR_INFO, "X11 does not support DRI3 extension"); + } + + // SHM extension + + ext = xcb_get_extension_data(x11->xcb, &xcb_shm_id); + if (ext && ext->present) { + xcb_shm_query_version_cookie_t shm_cookie = + xcb_shm_query_version(x11->xcb); + xcb_shm_query_version_reply_t *shm_reply = + xcb_shm_query_version_reply(x11->xcb, shm_cookie, NULL); + if (shm_reply) { + if (shm_reply->major_version >= 1 || shm_reply->minor_version >= 2) { + if (shm_reply->shared_pixmaps) { + x11->have_shm = true; + } else { + wlr_log(WLR_INFO, "X11 does not support shared pixmaps"); + } + } else { + wlr_log(WLR_INFO, "X11 does not support required SHM version " + "(has %"PRIu32".%"PRIu32", want 1.2)", + shm_reply->major_version, shm_reply->minor_version); + } + } else { + wlr_log(WLR_INFO, "X11 does not support required SHM version"); + } + free(shm_reply); + } else { + wlr_log(WLR_INFO, "X11 does not support SHM extension"); + } + + // Present extension + + ext = xcb_get_extension_data(x11->xcb, &xcb_present_id); + if (!ext || !ext->present) { + wlr_log(WLR_ERROR, "X11 does not support Present extension"); + goto error_display; + } + x11->present_opcode = ext->major_opcode; + + xcb_present_query_version_cookie_t present_cookie = + xcb_present_query_version(x11->xcb, 1, 2); + xcb_present_query_version_reply_t *present_reply = + xcb_present_query_version_reply(x11->xcb, present_cookie, NULL); + if (!present_reply || present_reply->major_version < 1) { + wlr_log(WLR_ERROR, "X11 does not support required Present version " + "(has %"PRIu32".%"PRIu32", want 1.0)", + present_reply->major_version, present_reply->minor_version); + free(present_reply); + goto error_display; + } + free(present_reply); + + // Xfixes extension + + ext = xcb_get_extension_data(x11->xcb, &xcb_xfixes_id); + if (!ext || !ext->present) { + wlr_log(WLR_ERROR, "X11 does not support Xfixes extension"); + goto error_display; + } + + xcb_xfixes_query_version_cookie_t fixes_cookie = + xcb_xfixes_query_version(x11->xcb, 4, 0); + xcb_xfixes_query_version_reply_t *fixes_reply = + xcb_xfixes_query_version_reply(x11->xcb, fixes_cookie, NULL); + if (!fixes_reply || fixes_reply->major_version < 4) { + wlr_log(WLR_ERROR, "X11 does not support required Xfixes version " + "(has %"PRIu32".%"PRIu32", want 4.0)", + fixes_reply->major_version, fixes_reply->minor_version); + free(fixes_reply); + goto error_display; + } + free(fixes_reply); + + // Xinput extension + + ext = xcb_get_extension_data(x11->xcb, &xcb_input_id); + if (!ext || !ext->present) { + wlr_log(WLR_ERROR, "X11 does not support Xinput extension"); + goto error_display; + } + x11->xinput_opcode = ext->major_opcode; + + xcb_input_xi_query_version_cookie_t xi_cookie = + xcb_input_xi_query_version(x11->xcb, 2, 0); + xcb_input_xi_query_version_reply_t *xi_reply = + xcb_input_xi_query_version_reply(x11->xcb, xi_cookie, NULL); + if (!xi_reply || xi_reply->major_version < 2) { + wlr_log(WLR_ERROR, "X11 does not support required Xinput version " + "(has %"PRIu32".%"PRIu32", want 2.0)", + xi_reply->major_version, xi_reply->minor_version); + free(xi_reply); + goto error_display; + } + free(xi_reply); + + int fd = xcb_get_file_descriptor(x11->xcb); + uint32_t events = WL_EVENT_READABLE | WL_EVENT_ERROR | WL_EVENT_HANGUP; + x11->event_source = wl_event_loop_add_fd(loop, fd, events, x11_event, x11); + if (!x11->event_source) { + wlr_log(WLR_ERROR, "Could not create event source"); + goto error_display; + } + wl_event_source_check(x11->event_source); + + x11->screen = xcb_setup_roots_iterator(xcb_get_setup(x11->xcb)).data; + if (!x11->screen) { + wlr_log(WLR_ERROR, "Failed to get X11 screen"); + goto error_event; + } + + x11->depth = get_depth(x11->screen, 24); + if (!x11->depth) { + wlr_log(WLR_ERROR, "Failed to get 24-bit depth for X11 screen"); + goto error_event; + } + + x11->visualid = pick_visualid(x11->depth); + if (!x11->visualid) { + wlr_log(WLR_ERROR, "Failed to pick X11 visual"); + goto error_event; + } + + x11->x11_format = x11_format_from_depth(x11->depth->depth); + if (!x11->x11_format) { + wlr_log(WLR_ERROR, "Unsupported depth %"PRIu8, x11->depth->depth); + goto error_event; + } + + x11->colormap = xcb_generate_id(x11->xcb); + xcb_create_colormap(x11->xcb, XCB_COLORMAP_ALLOC_NONE, x11->colormap, + x11->screen->root, x11->visualid); + + if (!query_formats(x11)) { + wlr_log(WLR_ERROR, "Failed to query supported DRM formats"); + goto error_event; + } + + x11->drm_fd = -1; + if (x11->have_dri3) { + // DRI3 may return a render node (Xwayland) or an authenticated primary + // node (plain Glamor). + x11->drm_fd = query_dri3_drm_fd(x11); + if (x11->drm_fd < 0) { + wlr_log(WLR_ERROR, "Failed to query DRI3 DRM FD"); + goto error_event; + } + } + + // Windows can only display buffers with the depth they were created with + // TODO: look into changing the window's depth at runtime + const struct wlr_drm_format *dri3_format = + wlr_drm_format_set_get(&x11->dri3_formats, x11->x11_format->drm); + if (x11->have_dri3 && dri3_format != NULL) { + wlr_drm_format_set_add(&x11->primary_dri3_formats, + dri3_format->format, DRM_FORMAT_MOD_INVALID); + for (size_t i = 0; i < dri3_format->len; i++) { + wlr_drm_format_set_add(&x11->primary_dri3_formats, + dri3_format->format, dri3_format->modifiers[i]); + } + } + + const struct wlr_drm_format *shm_format = + wlr_drm_format_set_get(&x11->shm_formats, x11->x11_format->drm); + if (x11->have_shm && shm_format != NULL) { + wlr_drm_format_set_add(&x11->primary_shm_formats, + shm_format->format, DRM_FORMAT_MOD_INVALID); + } + +#if HAVE_XCB_ERRORS + if (xcb_errors_context_new(x11->xcb, &x11->errors_context) != 0) { + wlr_log(WLR_ERROR, "Failed to create error context"); + goto error_event; + } +#endif + + wlr_keyboard_init(&x11->keyboard, &x11_keyboard_impl, + x11_keyboard_impl.name); + + x11->event_loop_destroy.notify = handle_event_loop_destroy; + wl_event_loop_add_destroy_listener(loop, &x11->event_loop_destroy); + + // Create an empty pixmap to be used as the cursor. The + // default GC foreground is 0, and that is what it will be + // filled with. + xcb_pixmap_t blank = xcb_generate_id(x11->xcb); + xcb_create_pixmap(x11->xcb, 1, blank, x11->screen->root, 1, 1); + xcb_gcontext_t gc = xcb_generate_id(x11->xcb); + xcb_create_gc(x11->xcb, gc, blank, 0, NULL); + xcb_rectangle_t rect = { .x = 0, .y = 0, .width = 1, .height = 1 }; + xcb_poly_fill_rectangle(x11->xcb, blank, gc, 1, &rect); + + x11->transparent_cursor = xcb_generate_id(x11->xcb); + xcb_create_cursor(x11->xcb, x11->transparent_cursor, blank, blank, + 0, 0, 0, 0, 0, 0, 0, 0); + + xcb_free_gc(x11->xcb, gc); + xcb_free_pixmap(x11->xcb, blank); + + x11_get_argb32(x11); + + return &x11->backend; + +error_event: + wl_event_source_remove(x11->event_source); +error_display: + xcb_disconnect(x11->xcb); +error_x11: + free(x11); + return NULL; +} + +static void handle_x11_error(struct wlr_x11_backend *x11, xcb_value_error_t *ev) { +#if HAVE_XCB_ERRORS + const char *major_name = xcb_errors_get_name_for_major_code( + x11->errors_context, ev->major_opcode); + if (!major_name) { + wlr_log(WLR_DEBUG, "X11 error happened, but could not get major name"); + goto log_raw; + } + + const char *minor_name = xcb_errors_get_name_for_minor_code( + x11->errors_context, ev->major_opcode, ev->minor_opcode); + + const char *extension; + const char *error_name = xcb_errors_get_name_for_error(x11->errors_context, + ev->error_code, &extension); + if (!error_name) { + wlr_log(WLR_DEBUG, "X11 error happened, but could not get error name"); + goto log_raw; + } + + wlr_log(WLR_ERROR, "X11 error: op %s (%s), code %s (%s), " + "sequence %"PRIu16", value %"PRIu32, + major_name, minor_name ? minor_name : "no minor", + error_name, extension ? extension : "no extension", + ev->sequence, ev->bad_value); + + return; + +log_raw: +#endif + + wlr_log(WLR_ERROR, "X11 error: op %"PRIu8":%"PRIu16", code %"PRIu8", " + "sequence %"PRIu16", value %"PRIu32, + ev->major_opcode, ev->minor_opcode, ev->error_code, + ev->sequence, ev->bad_value); +} + +static void handle_x11_unknown_event(struct wlr_x11_backend *x11, + xcb_generic_event_t *ev) { +#if HAVE_XCB_ERRORS + const char *extension; + const char *event_name = xcb_errors_get_name_for_xcb_event( + x11->errors_context, ev, &extension); + if (!event_name) { + wlr_log(WLR_DEBUG, "No name for unhandled event: %u", + ev->response_type); + return; + } + + wlr_log(WLR_DEBUG, "Unhandled X11 event: %s (%u)", event_name, ev->response_type); +#else + wlr_log(WLR_DEBUG, "Unhandled X11 event: %u", ev->response_type); +#endif +} diff --git a/backend/x11/input_device.c b/backend/x11/input_device.c new file mode 100644 index 0000000..c353653 --- /dev/null +++ b/backend/x11/input_device.c @@ -0,0 +1,331 @@ +#include + +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "backend/x11.h" + +static void send_key_event(struct wlr_x11_backend *x11, uint32_t key, + enum wl_keyboard_key_state st, xcb_timestamp_t time) { + struct wlr_keyboard_key_event ev = { + .time_msec = time, + .keycode = key, + .state = st, + .update_state = true, + }; + wlr_keyboard_notify_key(&x11->keyboard, &ev); +} + +static void send_button_event(struct wlr_x11_output *output, uint32_t key, + enum wl_pointer_button_state st, xcb_timestamp_t time) { + struct wlr_pointer_button_event ev = { + .pointer = &output->pointer, + .time_msec = time, + .button = key, + .state = st, + }; + wl_signal_emit_mutable(&output->pointer.events.button, &ev); + wl_signal_emit_mutable(&output->pointer.events.frame, &output->pointer); +} + +static void send_axis_event(struct wlr_x11_output *output, int32_t delta, + xcb_timestamp_t time) { + struct wlr_pointer_axis_event ev = { + .pointer = &output->pointer, + .time_msec = time, + .source = WL_POINTER_AXIS_SOURCE_WHEEL, + .orientation = WL_POINTER_AXIS_VERTICAL_SCROLL, + // Most mice use a 15 degree angle per scroll click + .delta = delta * 15, + .delta_discrete = delta * WLR_POINTER_AXIS_DISCRETE_STEP, + }; + wl_signal_emit_mutable(&output->pointer.events.axis, &ev); + wl_signal_emit_mutable(&output->pointer.events.frame, &output->pointer); +} + +static void send_pointer_position_event(struct wlr_x11_output *output, + int16_t x, int16_t y, xcb_timestamp_t time) { + struct wlr_pointer_motion_absolute_event ev = { + .pointer = &output->pointer, + .time_msec = time, + .x = (double)x / output->wlr_output.width, + .y = (double)y / output->wlr_output.height, + }; + wl_signal_emit_mutable(&output->pointer.events.motion_absolute, &ev); + wl_signal_emit_mutable(&output->pointer.events.frame, &output->pointer); +} + +static void send_touch_down_event(struct wlr_x11_output *output, + int16_t x, int16_t y, int32_t touch_id, xcb_timestamp_t time) { + struct wlr_touch_down_event ev = { + .touch = &output->touch, + .time_msec = time, + .x = (double)x / output->wlr_output.width, + .y = (double)y / output->wlr_output.height, + .touch_id = touch_id, + }; + wl_signal_emit_mutable(&output->touch.events.down, &ev); + wl_signal_emit_mutable(&output->touch.events.frame, NULL); +} + +static void send_touch_motion_event(struct wlr_x11_output *output, + int16_t x, int16_t y, int32_t touch_id, xcb_timestamp_t time) { + struct wlr_touch_motion_event ev = { + .touch = &output->touch, + .time_msec = time, + .x = (double)x / output->wlr_output.width, + .y = (double)y / output->wlr_output.height, + .touch_id = touch_id, + }; + wl_signal_emit_mutable(&output->touch.events.motion, &ev); + wl_signal_emit_mutable(&output->touch.events.frame, NULL); +} + +static void send_touch_up_event(struct wlr_x11_output *output, + int32_t touch_id, xcb_timestamp_t time) { + struct wlr_touch_up_event ev = { + .touch = &output->touch, + .time_msec = time, + .touch_id = touch_id, + }; + wl_signal_emit_mutable(&output->touch.events.up, &ev); + wl_signal_emit_mutable(&output->touch.events.frame, NULL); +} + +static struct wlr_x11_touchpoint *get_touchpoint_from_x11_touch_id( + struct wlr_x11_output *output, uint32_t id) { + struct wlr_x11_touchpoint *touchpoint; + wl_list_for_each(touchpoint, &output->touchpoints, link) { + if (touchpoint->x11_id == id) { + return touchpoint; + } + } + return NULL; +} + +void handle_x11_xinput_event(struct wlr_x11_backend *x11, + xcb_ge_generic_event_t *event) { + struct wlr_x11_output *output; + + switch (event->event_type) { + case XCB_INPUT_KEY_PRESS: { + xcb_input_key_press_event_t *ev = + (xcb_input_key_press_event_t *)event; + + if (ev->flags & XCB_INPUT_KEY_EVENT_FLAGS_KEY_REPEAT) { + return; + } + + wlr_keyboard_notify_modifiers(&x11->keyboard, ev->mods.base, + ev->mods.latched, ev->mods.locked, ev->mods.effective); + send_key_event(x11, ev->detail - 8, WL_KEYBOARD_KEY_STATE_PRESSED, ev->time); + x11->time = ev->time; + break; + } + case XCB_INPUT_KEY_RELEASE: { + xcb_input_key_release_event_t *ev = + (xcb_input_key_release_event_t *)event; + + wlr_keyboard_notify_modifiers(&x11->keyboard, ev->mods.base, + ev->mods.latched, ev->mods.locked, ev->mods.effective); + send_key_event(x11, ev->detail - 8, WL_KEYBOARD_KEY_STATE_RELEASED, ev->time); + x11->time = ev->time; + break; + } + case XCB_INPUT_BUTTON_PRESS: { + xcb_input_button_press_event_t *ev = + (xcb_input_button_press_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + switch (ev->detail) { + case XCB_BUTTON_INDEX_1: + send_button_event(output, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED, + ev->time); + break; + case XCB_BUTTON_INDEX_2: + send_button_event(output, BTN_MIDDLE, WL_POINTER_BUTTON_STATE_PRESSED, + ev->time); + break; + case XCB_BUTTON_INDEX_3: + send_button_event(output, BTN_RIGHT, WL_POINTER_BUTTON_STATE_PRESSED, + ev->time); + break; + case XCB_BUTTON_INDEX_4: + send_axis_event(output, -1, ev->time); + break; + case XCB_BUTTON_INDEX_5: + send_axis_event(output, 1, ev->time); + break; + } + + x11->time = ev->time; + break; + } + case XCB_INPUT_BUTTON_RELEASE: { + xcb_input_button_release_event_t *ev = + (xcb_input_button_release_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + switch (ev->detail) { + case XCB_BUTTON_INDEX_1: + send_button_event(output, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED, + ev->time); + break; + case XCB_BUTTON_INDEX_2: + send_button_event(output, BTN_MIDDLE, WL_POINTER_BUTTON_STATE_RELEASED, + ev->time); + break; + case XCB_BUTTON_INDEX_3: + send_button_event(output, BTN_RIGHT, WL_POINTER_BUTTON_STATE_RELEASED, + ev->time); + break; + } + + x11->time = ev->time; + break; + } + case XCB_INPUT_MOTION: { + xcb_input_motion_event_t *ev = (xcb_input_motion_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + send_pointer_position_event(output, ev->event_x >> 16, + ev->event_y >> 16, ev->time); + x11->time = ev->time; + break; + } + case XCB_INPUT_TOUCH_BEGIN: { + xcb_input_touch_begin_event_t *ev = (xcb_input_touch_begin_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + int32_t id = 0; + if (!wl_list_empty(&output->touchpoints)) { + struct wlr_x11_touchpoint *last_touchpoint = wl_container_of( + output->touchpoints.next, last_touchpoint, link); + id = last_touchpoint->wayland_id + 1; + } + + struct wlr_x11_touchpoint *touchpoint = calloc(1, sizeof(*touchpoint)); + if (!touchpoint) { + return; + } + + touchpoint->x11_id = ev->detail; + touchpoint->wayland_id = id; + wl_list_init(&touchpoint->link); + wl_list_insert(&output->touchpoints, &touchpoint->link); + + send_touch_down_event(output, ev->event_x >> 16, + ev->event_y >> 16, touchpoint->wayland_id, ev->time); + x11->time = ev->time; + break; + } + case XCB_INPUT_TOUCH_END: { + xcb_input_touch_end_event_t *ev = (xcb_input_touch_end_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + struct wlr_x11_touchpoint *touchpoint = get_touchpoint_from_x11_touch_id(output, ev->detail); + if (!touchpoint) { + return; + } + + send_touch_up_event(output, touchpoint->wayland_id, ev->time); + x11->time = ev->time; + + wl_list_remove(&touchpoint->link); + free(touchpoint); + break; + } + case XCB_INPUT_TOUCH_UPDATE: { + xcb_input_touch_update_event_t *ev = (xcb_input_touch_update_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + struct wlr_x11_touchpoint *touchpoint = get_touchpoint_from_x11_touch_id(output, ev->detail); + if (!touchpoint) { + return; + } + + send_touch_motion_event(output, ev->event_x >> 16, + ev->event_y >> 16, touchpoint->wayland_id, ev->time); + x11->time = ev->time; + break; + } + } +} + +const struct wlr_keyboard_impl x11_keyboard_impl = { + .name = "x11-keyboard", +}; + +const struct wlr_pointer_impl x11_pointer_impl = { + .name = "x11-pointer", +}; + +const struct wlr_touch_impl x11_touch_impl = { + .name = "x11-touch", +}; + +void update_x11_pointer_position(struct wlr_x11_output *output, + xcb_timestamp_t time) { + struct wlr_x11_backend *x11 = output->x11; + + xcb_query_pointer_cookie_t cookie = + xcb_query_pointer(x11->xcb, output->win); + xcb_query_pointer_reply_t *reply = + xcb_query_pointer_reply(x11->xcb, cookie, NULL); + if (!reply) { + return; + } + + send_pointer_position_event(output, reply->win_x, reply->win_y, time); + + free(reply); +} + +bool wlr_input_device_is_x11(struct wlr_input_device *wlr_dev) { + switch (wlr_dev->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + return wlr_keyboard_from_input_device(wlr_dev)->impl == &x11_keyboard_impl; + case WLR_INPUT_DEVICE_POINTER: + return wlr_pointer_from_input_device(wlr_dev)->impl == &x11_pointer_impl; + case WLR_INPUT_DEVICE_TOUCH: + return wlr_touch_from_input_device(wlr_dev)->impl == &x11_touch_impl; + default: + return false; + } +} diff --git a/backend/x11/meson.build b/backend/x11/meson.build new file mode 100644 index 0000000..97b6d54 --- /dev/null +++ b/backend/x11/meson.build @@ -0,0 +1,36 @@ +x11_libs = [] +x11_required = [ + 'xcb', + 'xcb-dri3', + 'xcb-present', + 'xcb-render', + 'xcb-renderutil', + 'xcb-shm', + 'xcb-xfixes', + 'xcb-xinput', +] + +msg = ['Required for X11 backend support.'] +if 'x11' in backends + msg += 'Install "@0@" or disable the X11 backend.' +endif + +foreach lib : x11_required + dep = dependency(lib, + required: 'x11' in backends, + not_found_message: '\n'.join(msg).format(lib), + ) + if not dep.found() + subdir_done() + endif + + x11_libs += dep +endforeach + +wlr_files += files( + 'backend.c', + 'input_device.c', + 'output.c', +) +wlr_deps += x11_libs +features += { 'x11-backend': true } diff --git a/backend/x11/output.c b/backend/x11/output.c new file mode 100644 index 0000000..51a8459 --- /dev/null +++ b/backend/x11/output.c @@ -0,0 +1,774 @@ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "backend/x11.h" +#include "util/time.h" +#include "types/wlr_output.h" + +static const uint32_t SUPPORTED_OUTPUT_STATE = + WLR_OUTPUT_STATE_BACKEND_OPTIONAL | + WLR_OUTPUT_STATE_BUFFER | + WLR_OUTPUT_STATE_ENABLED | + WLR_OUTPUT_STATE_MODE | + WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED; + +static size_t last_output_num = 0; + +static void parse_xcb_setup(struct wlr_output *output, + xcb_connection_t *xcb) { + const xcb_setup_t *xcb_setup = xcb_get_setup(xcb); + + output->make = calloc(1, xcb_setup_vendor_length(xcb_setup) + 1); + if (output->make == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return; + } + memcpy(output->make, xcb_setup_vendor(xcb_setup), + xcb_setup_vendor_length(xcb_setup)); + + char model[64]; + snprintf(model, sizeof(model), "%"PRIu16".%"PRIu16, + xcb_setup->protocol_major_version, + xcb_setup->protocol_minor_version); + output->model = strdup(model); +} + +static struct wlr_x11_output *get_x11_output_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_x11(wlr_output)); + struct wlr_x11_output *output = wl_container_of(wlr_output, output, wlr_output); + return output; +} + +static bool output_set_custom_mode(struct wlr_output *wlr_output, + int32_t width, int32_t height, int32_t refresh) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + struct wlr_x11_backend *x11 = output->x11; + + if (width == output->win_width && height == output->win_height) { + return true; + } + + const uint32_t values[] = { width, height }; + xcb_void_cookie_t cookie = xcb_configure_window_checked( + x11->xcb, output->win, + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); + + xcb_generic_error_t *error; + if ((error = xcb_request_check(x11->xcb, cookie))) { + wlr_log(WLR_ERROR, "Could not set window size to %dx%d\n", + width, height); + free(error); + return false; + } + + output->win_width = width; + output->win_height = height; + + // Move the pointer to its new location + update_x11_pointer_position(output, output->x11->time); + + return true; +} + +static void destroy_x11_buffer(struct wlr_x11_buffer *buffer); + +static void output_destroy(struct wlr_output *wlr_output) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + struct wlr_x11_backend *x11 = output->x11; + + pixman_region32_fini(&output->exposed); + + wlr_pointer_finish(&output->pointer); + wlr_touch_finish(&output->touch); + + struct wlr_x11_buffer *buffer, *buffer_tmp; + wl_list_for_each_safe(buffer, buffer_tmp, &output->buffers, link) { + destroy_x11_buffer(buffer); + } + + wl_list_remove(&output->link); + + if (output->cursor.pic != XCB_NONE) { + xcb_render_free_picture(x11->xcb, output->cursor.pic); + } + + // A zero event mask deletes the event context + xcb_present_select_input(x11->xcb, output->present_event_id, output->win, 0); + xcb_destroy_window(x11->xcb, output->win); + xcb_flush(x11->xcb); + free(output); +} + +static bool output_test(struct wlr_output *wlr_output, + const struct wlr_output_state *state) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + struct wlr_x11_backend *x11 = output->x11; + + uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE; + if (unsupported != 0) { + wlr_log(WLR_DEBUG, "Unsupported output state fields: 0x%"PRIx32, + unsupported); + return false; + } + + // All we can do to influence adaptive sync on the X11 backend is set the + // _VARIABLE_REFRESH window property like mesa automatically does. We don't + // have any control beyond that, so we set the state to enabled on creating + // the output and never allow changing it (just like the Wayland backend). + assert(wlr_output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED); + if (state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) { + if (!state->adaptive_sync_enabled) { + wlr_log(WLR_DEBUG, "Disabling adaptive sync is not supported"); + return false; + } + } + + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + struct wlr_buffer *buffer = state->buffer; + struct wlr_dmabuf_attributes dmabuf_attrs; + struct wlr_shm_attributes shm_attrs; + uint32_t format = DRM_FORMAT_INVALID; + if (wlr_buffer_get_dmabuf(buffer, &dmabuf_attrs)) { + format = dmabuf_attrs.format; + } else if (wlr_buffer_get_shm(buffer, &shm_attrs)) { + format = shm_attrs.format; + } + if (format != x11->x11_format->drm) { + wlr_log(WLR_DEBUG, "Unsupported buffer format"); + return false; + } + } + + if (state->committed & WLR_OUTPUT_STATE_MODE) { + assert(state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM); + + if (state->custom_mode.refresh != 0) { + wlr_log(WLR_DEBUG, "Refresh rates are not supported"); + return false; + } + } + + return true; +} + +static void destroy_x11_buffer(struct wlr_x11_buffer *buffer) { + if (!buffer) { + return; + } + wl_list_remove(&buffer->buffer_destroy.link); + wl_list_remove(&buffer->link); + xcb_free_pixmap(buffer->x11->xcb, buffer->pixmap); + for (size_t i = 0; i < buffer->n_busy; i++) { + wlr_buffer_unlock(buffer->buffer); + } + free(buffer); +} + +static void buffer_handle_buffer_destroy(struct wl_listener *listener, + void *data) { + struct wlr_x11_buffer *buffer = + wl_container_of(listener, buffer, buffer_destroy); + destroy_x11_buffer(buffer); +} + +static xcb_pixmap_t import_dmabuf(struct wlr_x11_output *output, + struct wlr_dmabuf_attributes *dmabuf) { + struct wlr_x11_backend *x11 = output->x11; + + if (dmabuf->format != x11->x11_format->drm) { + // The pixmap's depth must match the window's depth, otherwise Present + // will throw a Match error + return XCB_PIXMAP_NONE; + } + + // xcb closes the FDs after sending them, so we need to dup them here + struct wlr_dmabuf_attributes dup_attrs = {0}; + if (!wlr_dmabuf_attributes_copy(&dup_attrs, dmabuf)) { + return XCB_PIXMAP_NONE; + } + + const struct wlr_x11_format *x11_fmt = x11->x11_format; + xcb_pixmap_t pixmap = xcb_generate_id(x11->xcb); + + if (x11->dri3_major_version > 1 || x11->dri3_minor_version >= 2) { + if (dmabuf->n_planes > 4) { + wlr_dmabuf_attributes_finish(&dup_attrs); + return XCB_PIXMAP_NONE; + } + xcb_dri3_pixmap_from_buffers(x11->xcb, pixmap, output->win, + dmabuf->n_planes, dmabuf->width, dmabuf->height, dmabuf->stride[0], + dmabuf->offset[0], dmabuf->stride[1], dmabuf->offset[1], + dmabuf->stride[2], dmabuf->offset[2], dmabuf->stride[3], + dmabuf->offset[3], x11_fmt->depth, x11_fmt->bpp, dmabuf->modifier, + dup_attrs.fd); + } else { + // PixmapFromBuffers requires DRI3 1.2 + if (dmabuf->n_planes != 1 + || dmabuf->modifier != DRM_FORMAT_MOD_INVALID) { + wlr_dmabuf_attributes_finish(&dup_attrs); + return XCB_PIXMAP_NONE; + } + xcb_dri3_pixmap_from_buffer(x11->xcb, pixmap, output->win, + dmabuf->height * dmabuf->stride[0], dmabuf->width, dmabuf->height, + dmabuf->stride[0], x11_fmt->depth, x11_fmt->bpp, dup_attrs.fd[0]); + } + + return pixmap; +} + +static xcb_pixmap_t import_shm(struct wlr_x11_output *output, + struct wlr_shm_attributes *shm) { + struct wlr_x11_backend *x11 = output->x11; + + if (shm->format != x11->x11_format->drm) { + // The pixmap's depth must match the window's depth, otherwise Present + // will throw a Match error + return XCB_PIXMAP_NONE; + } + + // xcb closes the FD after sending it + int fd = fcntl(shm->fd, F_DUPFD_CLOEXEC, 0); + if (fd < 0) { + wlr_log_errno(WLR_ERROR, "fcntl(F_DUPFD_CLOEXEC) failed"); + return XCB_PIXMAP_NONE; + } + + xcb_shm_seg_t seg = xcb_generate_id(x11->xcb); + xcb_shm_attach_fd(x11->xcb, seg, fd, false); + + xcb_pixmap_t pixmap = xcb_generate_id(x11->xcb); + xcb_shm_create_pixmap(x11->xcb, pixmap, output->win, shm->width, + shm->height, x11->x11_format->depth, seg, shm->offset); + + xcb_shm_detach(x11->xcb, seg); + + return pixmap; +} + +static struct wlr_x11_buffer *create_x11_buffer(struct wlr_x11_output *output, + struct wlr_buffer *wlr_buffer) { + struct wlr_x11_backend *x11 = output->x11; + xcb_pixmap_t pixmap = XCB_PIXMAP_NONE; + + struct wlr_dmabuf_attributes dmabuf_attrs; + struct wlr_shm_attributes shm_attrs; + if (wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf_attrs)) { + pixmap = import_dmabuf(output, &dmabuf_attrs); + } else if (wlr_buffer_get_shm(wlr_buffer, &shm_attrs)) { + pixmap = import_shm(output, &shm_attrs); + } + + if (pixmap == XCB_PIXMAP_NONE) { + return NULL; + } + + struct wlr_x11_buffer *buffer = calloc(1, sizeof(*buffer)); + if (!buffer) { + xcb_free_pixmap(x11->xcb, pixmap); + return NULL; + } + buffer->buffer = wlr_buffer_lock(wlr_buffer); + buffer->n_busy = 1; + buffer->pixmap = pixmap; + buffer->x11 = x11; + wl_list_insert(&output->buffers, &buffer->link); + + buffer->buffer_destroy.notify = buffer_handle_buffer_destroy; + wl_signal_add(&wlr_buffer->events.destroy, &buffer->buffer_destroy); + + return buffer; +} + +static struct wlr_x11_buffer *get_or_create_x11_buffer( + struct wlr_x11_output *output, struct wlr_buffer *wlr_buffer) { + struct wlr_x11_buffer *buffer; + wl_list_for_each(buffer, &output->buffers, link) { + if (buffer->buffer == wlr_buffer) { + wlr_buffer_lock(buffer->buffer); + buffer->n_busy++; + return buffer; + } + } + + return create_x11_buffer(output, wlr_buffer); +} + +static bool output_commit_buffer(struct wlr_x11_output *output, + const struct wlr_output_state *state) { + struct wlr_x11_backend *x11 = output->x11; + + struct wlr_buffer *buffer = state->buffer; + struct wlr_x11_buffer *x11_buffer = + get_or_create_x11_buffer(output, buffer); + if (!x11_buffer) { + goto error; + } + + xcb_xfixes_region_t region = XCB_NONE; + if (state->committed & WLR_OUTPUT_STATE_DAMAGE) { + pixman_region32_union(&output->exposed, &output->exposed, &state->damage); + + int rects_len = 0; + const pixman_box32_t *rects = pixman_region32_rectangles(&output->exposed, &rects_len); + + xcb_rectangle_t *xcb_rects = calloc(rects_len, sizeof(xcb_rectangle_t)); + if (!xcb_rects) { + goto error; + } + + for (int i = 0; i < rects_len; i++) { + const pixman_box32_t *box = &rects[i]; + xcb_rects[i] = (struct xcb_rectangle_t){ + .x = box->x1, + .y = box->y1, + .width = box->x2 - box->x1, + .height = box->y2 - box->y1, + }; + } + + region = xcb_generate_id(x11->xcb); + xcb_xfixes_create_region(x11->xcb, region, rects_len, xcb_rects); + + free(xcb_rects); + } + + pixman_region32_clear(&output->exposed); + + uint32_t serial = output->wlr_output.commit_seq; + uint32_t options = 0; + uint64_t target_msc = output->last_msc ? output->last_msc + 1 : 0; + xcb_present_pixmap(x11->xcb, output->win, x11_buffer->pixmap, serial, + 0, region, 0, 0, XCB_NONE, XCB_NONE, XCB_NONE, options, target_msc, + 0, 0, 0, NULL); + + if (region != XCB_NONE) { + xcb_xfixes_destroy_region(x11->xcb, region); + } + + return true; + +error: + destroy_x11_buffer(x11_buffer); + return false; +} + +static bool output_commit(struct wlr_output *wlr_output, + const struct wlr_output_state *state) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + struct wlr_x11_backend *x11 = output->x11; + + if (!output_test(wlr_output, state)) { + return false; + } + + if (state->committed & WLR_OUTPUT_STATE_ENABLED) { + if (state->enabled) { + xcb_map_window(x11->xcb, output->win); + } else { + xcb_unmap_window(x11->xcb, output->win); + } + } + + if (state->committed & WLR_OUTPUT_STATE_MODE) { + if (!output_set_custom_mode(wlr_output, + state->custom_mode.width, + state->custom_mode.height, + state->custom_mode.refresh)) { + return false; + } + } + + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + if (!output_commit_buffer(output, state)) { + return false; + } + } else if (output_pending_enabled(wlr_output, state)) { + uint32_t serial = output->wlr_output.commit_seq; + uint64_t target_msc = output->last_msc ? output->last_msc + 1 : 0; + xcb_present_notify_msc(x11->xcb, output->win, serial, target_msc, 0, 0); + } + + xcb_flush(x11->xcb); + + return true; +} + +static void update_x11_output_cursor(struct wlr_x11_output *output, + int32_t hotspot_x, int32_t hotspot_y) { + struct wlr_x11_backend *x11 = output->x11; + + xcb_cursor_t cursor = x11->transparent_cursor; + + if (output->cursor.pic != XCB_NONE) { + cursor = xcb_generate_id(x11->xcb); + xcb_render_create_cursor(x11->xcb, cursor, output->cursor.pic, + hotspot_x, hotspot_y); + } + + uint32_t values[] = {cursor}; + xcb_change_window_attributes(x11->xcb, output->win, + XCB_CW_CURSOR, values); + xcb_flush(x11->xcb); + + if (cursor != x11->transparent_cursor) { + xcb_free_cursor(x11->xcb, cursor); + } +} + +static bool output_cursor_to_picture(struct wlr_x11_output *output, + struct wlr_buffer *buffer) { + struct wlr_x11_backend *x11 = output->x11; + struct wlr_renderer *renderer = output->wlr_output.renderer; + + if (output->cursor.pic != XCB_NONE) { + xcb_render_free_picture(x11->xcb, output->cursor.pic); + } + output->cursor.pic = XCB_NONE; + + if (buffer == NULL) { + return true; + } + + struct wlr_texture *texture = wlr_texture_from_buffer(renderer, buffer); + if (!texture) { + return false; + } + + int depth = 32; + int stride = texture->width * 4; + uint8_t *data = malloc(texture->height * stride); + if (data == NULL) { + wlr_texture_destroy(texture); + return false; + } + + bool result = wlr_texture_read_pixels(texture, &(struct wlr_texture_read_pixels_options) { + .format = DRM_FORMAT_ARGB8888, + .stride = stride, + .data = data, + }); + + wlr_texture_destroy(texture); + + if (!result) { + free(data); + return false; + } + + xcb_pixmap_t pix = xcb_generate_id(x11->xcb); + xcb_create_pixmap(x11->xcb, depth, pix, output->win, + buffer->width, buffer->height); + + output->cursor.pic = xcb_generate_id(x11->xcb); + xcb_render_create_picture(x11->xcb, output->cursor.pic, + pix, x11->argb32, 0, 0); + + xcb_gcontext_t gc = xcb_generate_id(x11->xcb); + xcb_create_gc(x11->xcb, gc, pix, 0, NULL); + + xcb_put_image(x11->xcb, XCB_IMAGE_FORMAT_Z_PIXMAP, + pix, gc, buffer->width, buffer->height, 0, 0, 0, depth, + stride * buffer->height * sizeof(uint8_t), data); + free(data); + xcb_free_gc(x11->xcb, gc); + xcb_free_pixmap(x11->xcb, pix); + + return true; +} + +static bool output_set_cursor(struct wlr_output *wlr_output, + struct wlr_buffer *buffer, int32_t hotspot_x, int32_t hotspot_y) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + struct wlr_x11_backend *x11 = output->x11; + + if (x11->argb32 == XCB_NONE) { + return false; + } + + if (buffer != NULL) { + if (hotspot_x < 0) { + hotspot_x = 0; + } + if (hotspot_x > buffer->width) { + hotspot_x = buffer->width; + } + if (hotspot_y < 0) { + hotspot_y = 0; + } + if (hotspot_y > buffer->height) { + hotspot_y = buffer->height; + } + } + + bool success = output_cursor_to_picture(output, buffer); + + update_x11_output_cursor(output, hotspot_x, hotspot_y); + + return success; +} + +static bool output_move_cursor(struct wlr_output *_output, int x, int y) { + // TODO: only return true if x == current x and y == current y + return true; +} + +static const struct wlr_drm_format_set *output_get_primary_formats( + struct wlr_output *wlr_output, uint32_t buffer_caps) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + struct wlr_x11_backend *x11 = output->x11; + + if (x11->have_dri3 && (buffer_caps & WLR_BUFFER_CAP_DMABUF)) { + return &output->x11->primary_dri3_formats; + } else if (x11->have_shm && (buffer_caps & WLR_BUFFER_CAP_SHM)) { + return &output->x11->primary_shm_formats; + } + return NULL; +} + +static const struct wlr_output_impl output_impl = { + .destroy = output_destroy, + .test = output_test, + .commit = output_commit, + .set_cursor = output_set_cursor, + .move_cursor = output_move_cursor, + .get_primary_formats = output_get_primary_formats, +}; + +struct wlr_output *wlr_x11_output_create(struct wlr_backend *backend) { + struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend); + + if (!x11->started) { + ++x11->requested_outputs; + return NULL; + } + + struct wlr_x11_output *output = calloc(1, sizeof(*output)); + if (output == NULL) { + return NULL; + } + output->x11 = x11; + wl_list_init(&output->buffers); + pixman_region32_init(&output->exposed); + + struct wlr_output *wlr_output = &output->wlr_output; + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_custom_mode(&state, 1024, 768, 0); + + wlr_output_init(wlr_output, &x11->backend, &output_impl, x11->event_loop, &state); + wlr_output_state_finish(&state); + + size_t output_num = ++last_output_num; + + char name[64]; + snprintf(name, sizeof(name), "X11-%zu", output_num); + wlr_output_set_name(wlr_output, name); + + parse_xcb_setup(wlr_output, x11->xcb); + + char description[128]; + snprintf(description, sizeof(description), "X11 output %zu", output_num); + wlr_output_set_description(wlr_output, description); + + // The X11 protocol requires us to set a colormap and border pixel if the + // depth doesn't match the root window's + uint32_t mask = XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | + XCB_CW_COLORMAP | XCB_CW_CURSOR; + uint32_t values[] = { + 0, + XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY, + x11->colormap, + x11->transparent_cursor, + }; + output->win = xcb_generate_id(x11->xcb); + xcb_create_window(x11->xcb, x11->depth->depth, output->win, + x11->screen->root, 0, 0, wlr_output->width, wlr_output->height, 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, x11->visualid, mask, values); + + output->win_width = wlr_output->width; + output->win_height = wlr_output->height; + + struct { + xcb_input_event_mask_t head; + xcb_input_xi_event_mask_t mask; + } xinput_mask = { + .head = { .deviceid = XCB_INPUT_DEVICE_ALL_MASTER, .mask_len = 1 }, + .mask = XCB_INPUT_XI_EVENT_MASK_KEY_PRESS | + XCB_INPUT_XI_EVENT_MASK_KEY_RELEASE | + XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS | + XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE | + XCB_INPUT_XI_EVENT_MASK_MOTION | + XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN | + XCB_INPUT_XI_EVENT_MASK_TOUCH_END | + XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE, + }; + xcb_input_xi_select_events(x11->xcb, output->win, 1, &xinput_mask.head); + + uint32_t present_mask = XCB_PRESENT_EVENT_MASK_IDLE_NOTIFY | + XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY; + output->present_event_id = xcb_generate_id(x11->xcb); + xcb_present_select_input(x11->xcb, output->present_event_id, output->win, + present_mask); + + xcb_change_property(x11->xcb, XCB_PROP_MODE_REPLACE, output->win, + x11->atoms.wm_protocols, XCB_ATOM_ATOM, 32, 1, + &x11->atoms.wm_delete_window); + + uint32_t enabled = 1; + xcb_change_property(x11->xcb, XCB_PROP_MODE_REPLACE, output->win, + x11->atoms.variable_refresh, XCB_ATOM_CARDINAL, 32, 1, + &enabled); + wlr_output->adaptive_sync_status = WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED; + + wlr_x11_output_set_title(wlr_output, NULL); + + xcb_flush(x11->xcb); + + wl_list_insert(&x11->outputs, &output->link); + + wlr_pointer_init(&output->pointer, &x11_pointer_impl, "x11-pointer"); + output->pointer.output_name = strdup(wlr_output->name); + + wlr_touch_init(&output->touch, &x11_touch_impl, "x11-touch"); + output->touch.output_name = strdup(wlr_output->name); + wl_list_init(&output->touchpoints); + + wl_signal_emit_mutable(&x11->backend.events.new_output, wlr_output); + wl_signal_emit_mutable(&x11->backend.events.new_input, &output->pointer.base); + wl_signal_emit_mutable(&x11->backend.events.new_input, &output->touch.base); + + return wlr_output; +} + +void handle_x11_configure_notify(struct wlr_x11_output *output, + xcb_configure_notify_event_t *ev) { + if (ev->width == 0 || ev->height == 0) { + wlr_log(WLR_DEBUG, + "Ignoring X11 configure event for height=%d, width=%d", + ev->width, ev->height); + return; + } + + output->win_width = ev->width; + output->win_height = ev->height; + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_custom_mode(&state, ev->width, ev->height, 0); + wlr_output_send_request_state(&output->wlr_output, &state); + wlr_output_state_finish(&state); +} + +bool wlr_output_is_x11(struct wlr_output *wlr_output) { + return wlr_output->impl == &output_impl; +} + +void wlr_x11_output_set_title(struct wlr_output *output, const char *title) { + struct wlr_x11_output *x11_output = get_x11_output_from_output(output); + + char wl_title[32]; + if (title == NULL) { + if (snprintf(wl_title, sizeof(wl_title), "wlroots - %s", output->name) <= 0) { + return; + } + title = wl_title; + } + + xcb_change_property(x11_output->x11->xcb, XCB_PROP_MODE_REPLACE, x11_output->win, + x11_output->x11->atoms.net_wm_name, x11_output->x11->atoms.utf8_string, 8, + strlen(title), title); +} + +static struct wlr_x11_buffer *get_x11_buffer(struct wlr_x11_output *output, + xcb_pixmap_t pixmap) { + struct wlr_x11_buffer *buffer; + wl_list_for_each(buffer, &output->buffers, link) { + if (buffer->pixmap == pixmap) { + return buffer; + } + } + return NULL; +} + +void handle_x11_present_event(struct wlr_x11_backend *x11, + xcb_ge_generic_event_t *event) { + struct wlr_x11_output *output; + + switch (event->event_type) { + case XCB_PRESENT_EVENT_IDLE_NOTIFY:; + xcb_present_idle_notify_event_t *idle_notify = + (xcb_present_idle_notify_event_t *)event; + + output = get_x11_output_from_window_id(x11, idle_notify->window); + if (!output) { + wlr_log(WLR_DEBUG, "Got PresentIdleNotify event for unknown window"); + return; + } + + struct wlr_x11_buffer *buffer = + get_x11_buffer(output, idle_notify->pixmap); + if (!buffer) { + wlr_log(WLR_DEBUG, "Got PresentIdleNotify event for unknown buffer"); + return; + } + + assert(buffer->n_busy > 0); + buffer->n_busy--; + wlr_buffer_unlock(buffer->buffer); // may destroy buffer + break; + case XCB_PRESENT_COMPLETE_NOTIFY:; + xcb_present_complete_notify_event_t *complete_notify = + (xcb_present_complete_notify_event_t *)event; + + output = get_x11_output_from_window_id(x11, complete_notify->window); + if (!output) { + wlr_log(WLR_DEBUG, "Got PresentCompleteNotify event for unknown window"); + return; + } + + output->last_msc = complete_notify->msc; + + struct timespec t; + timespec_from_nsec(&t, complete_notify->ust * 1000); + + uint32_t flags = 0; + if (complete_notify->mode == XCB_PRESENT_COMPLETE_MODE_FLIP) { + flags |= WLR_OUTPUT_PRESENT_ZERO_COPY; + } + + bool presented = complete_notify->mode != XCB_PRESENT_COMPLETE_MODE_SKIP; + struct wlr_output_event_present present_event = { + .output = &output->wlr_output, + .commit_seq = complete_notify->serial, + .presented = presented, + .when = &t, + .seq = complete_notify->msc, + .flags = flags, + }; + wlr_output_send_present(&output->wlr_output, &present_event); + + wlr_output_send_frame(&output->wlr_output); + break; + default: + wlr_log(WLR_DEBUG, "Unhandled Present event %"PRIu16, event->event_type); + } +} diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..7ab9212 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,86 @@ +# Architecture + +This document describes the high-level design of wlroots. wlroots is modular: +each module can be used mostly independently from the rest of wlroots. For +instance, some wlroots-based compositors only use its backends, some only use +its protocol implementations. + +## Backends + +Backends are responsible for exposing input devices and output devices. +wlroots provides DRM and libinput backends to directly drive physical +devices, Wayland and X11 backends to run nested inside another compositor, +and a headless backend. A special "multi" backend is used to combine together +multiple backends, for instance DRM and libinput. Compositors can also +implement their own custom backends if they have special needs. + +Input devices such as pointers, keyboards, touch screens, tablets, switches +are supported. They emit input events (e.g. a keyboard key is pressed) which +compositors can handle and forward to Wayland clients. + +Output devices are tasked with presenting buffers to the user. They also +provide feedback, for instance presentation timestamps. Some backends support +more advanced functionality, such as displaying multiple buffers (e.g. for the +cursor image) or basic 2D transformations (e.g. rotation, clipping, scaling). + +## Renderers + +Renderers provide utilities to import buffers submitted by Wayland clients, +and a basic 2D drawing API suitable for simple compositors. wlroots provides +renderer implementations based on OpenGL ES 2, Vulkan and Pixman. Just like +backends, compositors can implement their own renderers, or use the graphics +APIs directly. + +To draw an image onto a buffer, compositors will first need to create a +texture, representing a source of pixels the renderer can sample from. This can +be done either by uploading pixels from CPU memory, or by importing already +existing GPU memory via DMA-BUFs. Compositors can then create a render pass +and submit drawing operations. Once they are done drawing, compositors can +submit the rendered buffer to an output. + +## Protocol implementations + +A number of Wayland interface implementations are provided. + +### Plumbing protocols + +wlroots ships unopinionated implementations of core plumbing interfaces, for +instance: + +- `wl_compositor` and `wl_surface` +- `wl_seat` and all input-related interfaces +- Buffer factories such as `wl_shm` and linux-dmabuf +- Additional protocols such as viewporter and presentation-time + +### Shells + +Shells give a meaning to surfaces. There are many kinds of surfaces: +application windows, tooltips, right-click menus, desktop panels, wallpapers, +lock screens, on-screen keyboards, and so on. Each of these use-cases is +fulfilled with a shell. wlroots supports xdg-shell for regular windows and +popups, Xwayland for interoperability with X11 applications, layer-shell for +desktop UI elements, and more. + +### Other protocols + +Many other protocol implementations are included, for instance: + +- xdg-activation for raising application windows +- idle-inhibit for preventing the screen from blanking when the user is + watching a video +- ext-idle-notify for notifying when the user is idle + +## Helpers + +wlroots provides additional helpers which can make it easier for compositors to +tie everything together: + +- `wlr_output_layout` organises output devices in the physical space +- `wlr_cursor` stores the current position and image of the cursor +- `wlr_scene` provides a declarative way to display surfaces + +## tinywl + +tinywl is a minimal wlroots compositor. It implements basic stacking window +management and only supports xdg-shell. It's extensively commented and is a +good learning resource for developers new to wlroots. diff --git a/docs/env_vars.md b/docs/env_vars.md new file mode 100644 index 0000000..e36cdc7 --- /dev/null +++ b/docs/env_vars.md @@ -0,0 +1,68 @@ +wlroots reads these environment variables + +# wlroots specific + +* *WLR_BACKENDS*: comma-separated list of backends to use (available backends: + libinput, drm, wayland, x11, headless) +* *WLR_NO_HARDWARE_CURSORS*: set to 1 to use software cursors instead of + hardware cursors +* *WLR_XWAYLAND*: specifies the path to an Xwayland binary to be used (instead + of following shell search semantics for "Xwayland") +* *WLR_RENDERER*: forces the creation of a specified renderer (available + renderers: gles2, pixman, vulkan) +* *WLR_RENDER_DRM_DEVICE*: specifies the DRM node to use for + hardware-accelerated renderers. +* *WLR_EGL_NO_MODIFIERS*: set to 1 to disable format modifiers in EGL, this can + be used to understand and work around driver bugs. + +## DRM backend + +* *WLR_DRM_DEVICES*: specifies the DRM devices (as a colon separated list) + instead of auto probing them. The first existing device in this list is + considered the primary DRM device. +* *WLR_DRM_NO_ATOMIC*: set to 1 to use legacy DRM interface instead of atomic + mode setting +* *WLR_DRM_NO_MODIFIERS*: set to 1 to always allocate planes without modifiers, + this can fix certain modeset failures because of bandwidth restrictions. +* *WLR_DRM_FORCE_LIBLIFTOFF*: set to 1 to force libliftoff (by default, + libliftoff is never used) + +## Headless backend + +* *WLR_HEADLESS_OUTPUTS*: when using the headless backend specifies the number + of outputs + +## libinput backend + +* *WLR_LIBINPUT_NO_DEVICES*: set to 1 to not fail without any input devices + +## Wayland backend + +* *WLR_WL_OUTPUTS*: when using the wayland backend specifies the number of outputs + +## X11 backend + +* *WLR_X11_OUTPUTS*: when using the X11 backend specifies the number of outputs + +## gles2 renderer + +* *WLR_RENDERER_ALLOW_SOFTWARE*: allows the gles2 renderer to use software + rendering + +## scenes + +* *WLR_SCENE_DEBUG_DAMAGE*: specifies debug options for screen damage related + tasks for compositors that use scenes (available options: none, rerender, + highlight) +* *WLR_SCENE_DISABLE_DIRECT_SCANOUT*: disables direct scan-out for debugging. +* *WLR_SCENE_DISABLE_VISIBILITY*: If set to 1, the visibility of all scene nodes + will be considered to be the full node. Intelligent visibility canculations will + be disabled. + +# Generic + +* *DISPLAY*: if set probe X11 backend in `wlr_backend_autocreate` +* *WAYLAND_DISPLAY*, *WAYLAND_SOCKET*: if set probe Wayland backend in + `wlr_backend_autocreate` +* *XCURSOR_PATH*: directory where xcursors are located +* *XDG_SESSION_ID*: if set, session ID used by the logind session diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..3858298 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +/compositor/protocols diff --git a/examples/cairo-buffer.c b/examples/cairo-buffer.c new file mode 100644 index 0000000..7de4a05 --- /dev/null +++ b/examples/cairo-buffer.c @@ -0,0 +1,193 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Simple scene-graph example with a custom buffer drawn by Cairo. + * + * Input is unimplemented. Surfaces are unimplemented. */ + +struct cairo_buffer { + struct wlr_buffer base; + cairo_surface_t *surface; +}; + +static void cairo_buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + cairo_surface_destroy(buffer->surface); + free(buffer); +} + +static bool cairo_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, + uint32_t flags, void **data, uint32_t *format, size_t *stride) { + struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + + if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) { + return false; + } + + *format = DRM_FORMAT_ARGB8888; + *data = cairo_image_surface_get_data(buffer->surface); + *stride = cairo_image_surface_get_stride(buffer->surface); + return true; +} + +static void cairo_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { +} + +static const struct wlr_buffer_impl cairo_buffer_impl = { + .destroy = cairo_buffer_destroy, + .begin_data_ptr_access = cairo_buffer_begin_data_ptr_access, + .end_data_ptr_access = cairo_buffer_end_data_ptr_access +}; + +static struct cairo_buffer *create_cairo_buffer(int width, int height) { + struct cairo_buffer *buffer = calloc(1, sizeof(*buffer)); + if (!buffer) { + return NULL; + } + + wlr_buffer_init(&buffer->base, &cairo_buffer_impl, width, height); + + buffer->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + width, height); + if (cairo_surface_status(buffer->surface) != CAIRO_STATUS_SUCCESS) { + free(buffer); + return NULL; + } + + return buffer; +} + +struct server { + struct wl_display *display; + struct wlr_backend *backend; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + struct wlr_scene *scene; + + struct wl_listener new_output; +}; + +struct output { + struct wl_list link; + struct server *server; + struct wlr_output *wlr; + struct wlr_scene_output *scene_output; + + struct wl_listener frame; +}; + +static void output_handle_frame(struct wl_listener *listener, void *data) { + struct output *output = wl_container_of(listener, output, frame); + + wlr_scene_output_commit(output->scene_output, NULL); +} + +static void server_handle_new_output(struct wl_listener *listener, void *data) { + struct server *server = wl_container_of(listener, server, new_output); + struct wlr_output *wlr_output = data; + + wlr_output_init_render(wlr_output, server->allocator, server->renderer); + + struct output *output = calloc(1, sizeof(*output)); + output->wlr = wlr_output; + output->server = server; + output->frame.notify = output_handle_frame; + wl_signal_add(&wlr_output->events.frame, &output->frame); + + output->scene_output = wlr_scene_output_create(server->scene, wlr_output); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); +} + +int main(void) { + wlr_log_init(WLR_DEBUG, NULL); + + struct server server = {0}; + server.display = wl_display_create(); + server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.display), NULL); + server.scene = wlr_scene_create(); + + server.renderer = wlr_renderer_autocreate(server.backend); + wlr_renderer_init_wl_display(server.renderer, server.display); + + server.allocator = wlr_allocator_autocreate(server.backend, + server.renderer); + + server.new_output.notify = server_handle_new_output; + wl_signal_add(&server.backend->events.new_output, &server.new_output); + + if (!wlr_backend_start(server.backend)) { + wl_display_destroy(server.display); + return EXIT_FAILURE; + } + + struct cairo_buffer *buffer = create_cairo_buffer(256, 256); + if (!buffer) { + wl_display_destroy(server.display); + return EXIT_FAILURE; + } + + /* Begin drawing + * From cairo samples at https://www.cairographics.org/samples/ */ + cairo_t *cr = cairo_create(buffer->surface); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_paint(cr); + cairo_set_source_rgb(cr, 0, 0, 0); + + double x = 25.6, y = 128.0; + double x1 = 102.4, y1 = 230.4, + x2 = 153.6, y2 = 25.6, + x3 = 230.4, y3 = 128.0; + + cairo_move_to(cr, x, y); + cairo_curve_to(cr, x1, y1, x2, y2, x3, y3); + + cairo_set_line_width(cr, 10.0); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.6); + cairo_set_line_width(cr, 6.0); + cairo_move_to(cr, x, y); + cairo_line_to(cr, x1, y1); + cairo_move_to(cr, x2, y2); + cairo_line_to(cr, x3, y3); + cairo_stroke(cr); + + cairo_destroy(cr); + /* End drawing */ + + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_create( + &server.scene->tree, &buffer->base); + if (!scene_buffer) { + wl_display_destroy(server.display); + return EXIT_FAILURE; + } + + wlr_scene_node_set_position(&scene_buffer->node, 50, 50); + wlr_buffer_drop(&buffer->base); + + wl_display_run(server.display); + + wl_display_destroy(server.display); + return EXIT_SUCCESS; +} diff --git a/examples/cat.c b/examples/cat.c new file mode 100644 index 0000000..cc1403d --- /dev/null +++ b/examples/cat.c @@ -0,0 +1,2641 @@ +/* GIMP RGBA C-Source image dump (cat.c) */ + +#include "cat.h" + +const struct gimp_texture cat_tex = { + 128, 128, 4, + "[\227\017\377L\206\001\377M\212\002\377T\227\011\377V\231\010\377W\224\001\377[\222" + "\001\377T\212\001\377P\211\001\377M\203\001\377P\212\001\377Q\217\001\377K\210\001\377" + "K\210\001\377N\214\001\377Q\217\002\377W\226\001\377X\224\001\377U\214\001\377U\216\001" + "\377[\225\003\377Y\221\002\377W\217\001\377^\230\005\377^\230\006\377`\231\011\377Z" + "\225\003\377X\221\002\377Y\221\003\377X\220\003\377^\227\007\377Z\225\004\377Z\226\002" + "\377W\217\001\377Y\224\002\377X\222\002\377W\220\001\377[\227\001\377c\241\004\377a\237" + "\003\377Z\230\001\377Y\222\001\377Z\220\001\377X\216\001\377[\231\005\377j\246\025\377" + "_\232\010\377U\214\000\377o\242\026\377k\234\013\377g\231\001\377c\222\001\377X\213" + "\005\377_\221\023\377V\214\010\377T\216\002\377U\222\002\377Q\211\001\377J|\001\377W" + "\214\003\377j\235\006\377o\243\004\377g\235\002\377X\217\002\377X\226\002\377U\221\001" + "\377T\210\002\377V\212\001\377V\216\002\377Z\226\002\377Y\221\001\377[\221\002\377g\234" + "\014\377d\232\016\377g\240\032\377[\226\022\377\\\224\020\377g\235\026\377\\\225" + "\005\377V\221\002\377U\212\002\377Y\217\001\377Z\220\001\377Y\221\001\377Y\226\002\377" + "W\227\001\377P\217\001\377O\217\002\377V\231\004\377X\230\003\377[\231\004\377X\217\001" + "\377Z\215\002\377Y\212\002\377[\214\001\377\\\215\002\377]\220\001\377]\227\002\377]" + "\233\001\377X\220\001\377X\217\001\377X\215\001\377^\234\001\377]\237\002\377U\221\001" + "\377R\214\001\377S\217\001\377T\220\001\377P\217\001\377P\224\001\377O\216\001\377M\210" + "\001\377V\225\001\377S\216\001\377S\216\001\377S\224\001\377N\220\001\377Q\226\004\377" + "S\227\013\377Y\235\023\377a\241\025\377`\240\015\377]\234\005\377V\227\002\377Y\233" + "\010\377W\231\011\377P\222\002\377R\217\001\377Y\237\021\377S\232\010\377K\224\002" + "\377L\222\001\377P\214\002\377]\222\001\377]\224\002\377U\217\001\377L\212\001\377K\206" + "\001\377Q\213\001\377T\222\001\377U\220\001\377V\224\001\377V\223\001\377Y\223\001\377" + "[\223\001\377Z\221\001\377Y\222\001\377X\225\001\377V\222\002\377U\210\002\377^\225\001" + "\377i\240\013\377g\236\020\377h\240\023\377T\223\002\377R\216\001\377V\215\001\377" + "[\225\001\377Y\226\001\377[\227\001\377Z\227\001\377Z\226\002\377^\226\005\377e\232\014" + "\377\\\225\004\377a\233\005\377_\231\002\377m\246\013\377a\232\003\377_\226\002\377" + "\\\223\001\377X\224\001\377]\235\012\377c\241\016\377^\231\002\377_\222\002\377f\235" + "\006\377s\252\012\377j\242\001\377b\233\002\377a\235\006\377_\232\011\377V\220\002\377" + "T\210\002\377T\216\001\377S\216\001\377U\220\002\377e\234\005\377k\241\004\377n\244\004" + "\377s\252\014\377a\234\003\377c\235\010\377W\217\001\377U\204\001\377X\212\001\377" + "[\217\001\377]\225\001\377[\224\001\377W\216\002\377V\216\003\377T\214\002\377R\212\002" + "\377T\214\002\377U\213\001\377\\\223\005\377e\235\014\377W\221\002\377W\220\001\377" + "Y\220\001\377W\213\001\377V\214\001\377V\217\001\377W\227\002\377V\227\001\377U\227\001" + "\377V\231\003\377N\211\001\377S\215\001\377W\216\001\377T\210\002\377Q\204\002\377W\213" + "\001\377Z\217\001\377Z\216\000\377[\225\002\377b\243\013\377V\220\001\377T\207\001\377" + "U\214\001\377[\231\002\377\\\236\002\377U\220\001\377M\207\002\377N\207\001\377Q\215" + "\001\377S\216\001\377V\226\002\377U\216\000\377V\212\002\377c\233\001\377]\221\002\377" + "V\215\001\377X\230\001\377S\226\001\377L\215\001\377I\211\001\377L\217\001\377T\225\004" + "\377j\244\014\377l\246\013\377X\226\001\377T\225\004\377\\\235\016\377c\243\025\377" + "^\236\012\377K\223\002\377M\230\007\377S\235\016\377N\227\007\377P\215\001\377^\224" + "\001\377`\226\001\377Z\225\001\377Q\216\001\377M\210\001\377V\216\002\377]\227\001\377" + "_\232\001\377]\226\001\377[\223\001\377Z\222\001\377Y\216\001\377Y\216\001\377X\225\001" + "\377W\226\001\377V\216\001\377T\210\001\377]\225\001\377o\246\014\377e\235\012\377" + "b\236\014\377S\222\001\377T\213\001\377W\220\001\377W\223\001\377W\216\002\377\\\231" + "\002\377c\235\011\377l\242\027\377e\233\022\377d\234\020\377Y\223\003\377b\235\007" + "\377b\233\003\377f\237\004\377b\233\001\377b\232\002\377`\232\001\377]\235\001\377^\237" + "\005\377Y\227\002\377U\211\001\377]\220\002\377_\225\001\377c\240\001\377l\250\005\377" + "b\237\002\377^\233\001\377W\223\002\377W\221\001\377T\214\001\377T\217\001\377Z\225\005" + "\377]\230\002\377a\233\002\377b\234\001\377k\245\011\377l\247\014\377\\\227\003\377" + "g\240\015\377V\213\001\377P\202\001\377T\212\001\377Z\224\001\377Z\226\003\377\\\231" + "\010\377T\220\014\377L\207\004\377L\200\002\377Q\203\001\377^\223\003\377Y\216\001\377" + "^\224\004\377p\247\026\377]\227\005\377Y\222\001\377X\222\001\377V\213\001\377W\213" + "\001\377X\220\001\377[\233\001\377X\231\001\377O\212\002\377S\220\001\377S\211\001\377" + "V\216\001\377W\214\002\377O\204\002\377U\213\001\377Z\217\002\377\\\225\001\377Z\223" + "\001\377U\221\003\377g\247\027\377S\224\002\377O\213\001\377U\223\001\377Y\230\001\377" + "\\\234\001\377Y\226\001\377Q\215\001\377O\207\001\377S\214\001\377V\217\001\377Y\225" + "\001\377X\214\001\377_\223\001\377i\237\001\377_\223\001\377[\222\001\377Y\227\002\377" + "V\226\001\377K\206\001\377H\203\002\377J\211\001\377T\224\003\377j\244\014\377i\244" + "\010\377]\234\003\377T\222\001\377U\226\002\377\\\236\012\377a\242\012\377H\211\001" + "\377E\214\001\377I\225\010\377V\240\021\377Q\232\002\377Y\224\001\377\\\222\002\377" + "X\224\001\377Q\215\001\377P\212\001\377W\216\001\377_\226\001\377a\226\001\377^\224\001" + "\377Z\221\001\377X\217\002\377S\210\001\377U\214\001\377[\227\003\377X\227\001\377U\221" + "\001\377W\216\001\377^\227\001\377h\243\002\377`\231\001\377Z\226\002\377U\214\001\377" + "W\217\001\377Y\222\001\377X\221\001\377X\226\002\377W\227\002\377X\227\004\377U\223\005" + "\377U\220\002\377V\222\001\377V\222\001\377\\\235\004\377]\236\003\377X\226\002\377Z" + "\225\002\377`\237\001\377`\241\002\377]\235\001\377Y\232\002\377X\224\001\377U\212\001" + "\377W\214\002\377Z\221\002\377Y\216\001\377d\240\005\377`\233\003\377Z\226\001\377^\232" + "\003\377Z\227\002\377X\222\001\377c\234\010\377i\242\014\377\\\227\003\377]\235\003\377" + "[\234\002\377d\244\015\377e\244\017\377Z\230\004\377j\246\017\377W\215\001\377S\205" + "\002\377X\223\003\377j\244\037\377l\246/\377~\261P\377\224\301r\377j\236\063\377" + "M\201\001\377S\207\001\377[\222\002\377T\214\000\377`\233\013\377i\243\024\377`\233" + "\014\377h\241\027\377e\240\023\377U\217\002\377V\213\001\377[\230\002\377Z\231\001\377" + "N\210\002\377H\177\002\377W\225\001\377Y\220\001\377Z\217\001\377V\214\001\377R\210\001" + "\377]\234\001\377_\231\002\377Z\222\001\377Y\223\001\377V\223\001\377c\244\020\377Y" + "\233\007\377R\223\001\377U\224\001\377Z\232\001\377^\235\001\377]\234\002\377Y\232\002" + "\377R\222\001\377T\223\001\377W\230\001\377W\230\002\377W\213\001\377h\240\001\377h\236" + "\001\377_\222\002\377\\\221\002\377[\231\002\377X\231\001\377O\220\001\377K\213\001\377" + "O\217\002\377b\236\015\377_\233\007\377Z\227\002\377_\240\004\377U\217\002\377T\221" + "\001\377S\222\001\377U\221\001\377K\203\002\377F\202\001\377D\212\001\377K\225\004\377" + "O\232\003\377R\230\002\377U\225\001\377T\223\001\377P\211\001\377K\204\001\377R\211\001" + "\377X\223\001\377[\231\001\377[\233\001\377X\222\001\377S\212\001\377M\200\002\377U\214" + "\003\377^\226\012\377X\225\005\377Y\232\006\377V\222\001\377\\\225\001\377d\240\001\377" + "_\226\001\377Z\220\001\377X\221\001\377Y\222\001\377[\216\001\377\\\217\002\377^\233" + "\002\377V\225\001\377R\221\001\377T\215\001\377V\220\001\377Z\224\001\377X\222\002\377" + "W\225\001\377^\240\007\377V\224\001\377V\216\001\377^\236\002\377b\243\004\377[\233\003" + "\377X\230\004\377\\\234\011\377V\222\003\377U\216\001\377X\215\001\377[\217\001\377" + "h\240\005\377i\245\005\377]\227\001\377^\230\002\377Z\217\001\377[\221\001\377k\243\011" + "\377e\236\007\377W\223\001\377c\243\014\377[\234\005\377f\244\017\377f\241\015\377" + "b\236\012\377n\251\024\377W\217\001\377V\216\005\377e\237\040\377t\254B\377|\260" + "T\377\230\305y\377\250\317\222\377o\242<\377P\210\002\377^\224\006\377\\\226" + "\006\377Q\221\003\377b\241\024\377a\241\020\377S\224\005\377e\240\030\377l\245\033" + "\377Z\222\001\377]\223\002\377`\232\001\377[\226\002\377P\204\001\377T\211\001\377_\231" + "\001\377^\224\002\377^\224\001\377[\224\002\377Y\217\001\377Z\225\001\377Y\220\001\377" + "Z\221\001\377X\224\001\377R\215\001\377X\231\005\377`\241\012\377S\221\001\377R\214" + "\001\377W\226\001\377[\227\001\377_\237\002\377`\242\002\377W\226\001\377Y\232\002\377" + "g\246\011\377a\237\003\377Z\220\002\377e\236\002\377_\226\001\377X\220\001\377X\223" + "\001\377[\233\001\377Z\233\002\377V\230\002\377Q\220\001\377b\235\015\377m\247\025\377" + "Z\232\004\377U\224\001\377[\234\004\377R\220\001\377R\220\001\377R\225\001\377V\221\001" + "\377M\200\001\377M\205\002\377L\207\001\377N\217\002\377N\217\001\377J\217\002\377P\230" + "\004\377O\227\003\377O\215\001\377E\201\001\377N\205\002\377Q\223\002\377`\242\021\377" + "R\225\002\377T\223\001\377T\215\001\377T\205\001\377^\222\010\377b\226\015\377]\231" + "\014\377]\235\011\377V\223\001\377Z\221\001\377c\233\001\377b\231\002\377`\226\001\377" + "]\225\001\377Y\216\002\377[\213\002\377^\222\001\377b\235\003\377[\223\002\377Y\225\001" + "\377Z\223\002\377]\223\002\377]\222\002\377]\227\002\377[\230\002\377a\243\010\377W" + "\230\002\377R\217\001\377Z\234\003\377h\251\017\377e\245\022\377^\233\017\377`\234" + "\025\377]\230\017\377i\243\032\377d\235\016\377]\224\001\377i\241\002\377i\243\002" + "\377b\233\001\377_\233\002\377X\222\001\377W\223\001\377c\237\017\377h\243\026\377" + "\\\234\014\377`\240\015\377Y\233\003\377^\235\004\377]\231\004\377c\235\007\377f\242" + "\016\377]\232\016\377n\250/\377q\253<\377w\256H\377\177\263Q\377\234\307z\377" + "\244\315\211\377g\241+\377O\216\002\377U\222\003\377S\222\003\377N\217\001\377_\241" + "\020\377Z\235\011\377Q\220\002\377W\226\003\377\\\231\003\377d\236\003\377k\237\001\377" + "f\233\001\377_\225\001\377X\213\002\377]\225\001\377f\236\002\377c\231\001\377`\234\002" + "\377]\234\001\377W\217\001\377V\214\001\377V\223\001\377T\223\001\377T\224\002\377K\203" + "\002\377N\212\001\377_\240\004\377X\222\001\377W\222\001\377Y\225\002\377W\220\001\377" + "Z\227\001\377`\242\003\377^\241\003\377`\241\004\377h\247\007\377c\242\003\377]\231\001" + "\377^\234\001\377S\220\001\377R\216\001\377U\225\001\377Z\231\002\377`\233\002\377[\225" + "\003\377W\213\002\377h\235\016\377e\240\015\377[\233\003\377X\232\003\377\\\235\005\377" + "V\225\002\377S\222\002\377Z\234\001\377b\235\001\377L~\002\377P\205\001\377O\206\002\377" + "Z\223\001\377Y\226\001\377N\210\001\377I\212\001\377R\231\004\377J\213\001\377C}\001\377" + "L\211\001\377V\232\015\377a\242\040\377L\212\001\377N\212\001\377T\217\001\377^\232" + "\003\377a\233\003\377Y\223\001\377T\224\002\377W\232\003\377W\225\001\377\\\225\002\377" + "b\230\001\377e\234\002\377a\226\001\377\\\217\001\377X\214\002\377\\\220\001\377a\232" + "\003\377b\233\002\377[\222\001\377_\233\002\377_\225\001\377`\225\001\377a\225\001\377" + "b\231\001\377\\\227\001\377a\243\005\377`\244\007\377U\224\002\377]\237\003\377b\242" + "\007\377f\246\020\377Z\231\007\377X\225\006\377b\235\022\377w\253.\377\200\263:\377" + "m\244\033\377j\245\002\377g\236\001\377b\232\002\377]\230\001\377X\224\001\377T\220" + "\004\377f\241\034\377b\240\031\377i\250\035\377d\244\021\377Y\230\001\377^\231\001" + "\377]\223\002\377b\231\003\377]\231\005\377^\233\024\377r\253\067\377x\262A\377{" + "\261@\377\202\265I\377\230\305k\377\227\302t\377_\234\026\377P\213\002\377R" + "\213\001\377V\220\002\377W\224\002\377a\241\010\377W\230\003\377N\213\001\377R\221\001" + "\377[\231\001\377c\232\001\377i\240\002\377j\240\001\377[\221\001\377O\202\001\377]\225" + "\001\377c\235\001\377a\230\001\377b\237\003\377_\240\003\377W\216\002\377\\\232\002\377" + "Z\227\001\377U\222\001\377P\216\001\377M\210\002\377Q\207\002\377\\\232\001\377_\233" + "\002\377^\236\001\377Z\224\001\377W\213\002\377[\231\001\377h\246\010\377f\247\014\377" + "W\231\002\377]\237\003\377\\\234\001\377^\233\001\377Z\227\002\377V\225\001\377U\230" + "\001\377Z\233\001\377\\\233\002\377c\240\002\377d\232\004\377^\223\005\377^\226\007\377" + "[\231\004\377]\233\003\377`\237\005\377^\235\003\377^\236\002\377\\\235\002\377_\240" + "\001\377`\230\002\377Ey\002\377H\201\001\377N\210\001\377R\216\001\377Z\231\001\377U\221" + "\001\377N\211\001\377N\221\000\377J\212\001\377G\202\001\377G\212\001\377R\231\012\377" + "X\233\021\377I\202\001\377M\212\001\377O\221\001\377W\227\001\377_\240\001\377]\223" + "\001\377X\223\001\377W\227\001\377X\226\002\377^\231\001\377d\237\001\377i\247\002\377" + "\\\231\001\377W\216\001\377X\220\001\377Z\227\002\377f\246\011\377\\\231\002\377Y\224" + "\002\377]\227\001\377^\222\002\377c\225\001\377e\230\001\377b\227\001\377^\227\001\377" + "b\243\003\377h\252\012\377]\236\001\377^\240\001\377Z\233\001\377]\235\002\377c\244" + "\012\377g\244\020\377b\236\022\377i\243\037\377u\253\060\377}\262\060\377o\251" + "\013\377f\234\002\377`\223\001\377e\235\006\377e\233\021\377q\247\063\377y\261F\377" + "l\245\062\377o\253*\377f\246\024\377Z\231\002\377c\235\001\377a\221\003\377n\243" + "\013\377s\247\034\377g\237\037\377s\253\064\377~\264?\377x\254\065\377\177\263" + ";\377\204\265C\377q\246)\377V\224\000\377W\223\002\377T\216\001\377V\217\001\377" + "\\\227\001\377e\241\005\377W\221\001\377M\200\002\377S\220\002\377]\233\001\377b\226" + "\002\377j\234\002\377i\240\002\377\\\226\001\377R\207\001\377]\230\001\377b\233\002\377" + "_\224\002\377f\240\002\377`\233\002\377[\222\001\377c\232\002\377e\240\001\377V\213\002" + "\377R\212\001\377S\215\001\377[\226\001\377`\237\001\377^\235\001\377Y\233\001\377X\230" + "\001\377U\212\001\377[\227\001\377j\250\007\377b\242\004\377S\215\002\377Y\226\001\377" + "a\242\002\377a\240\001\377X\222\001\377Y\227\001\377^\242\001\377]\234\001\377_\234\002" + "\377c\237\001\377f\237\004\377b\227\005\377a\230\005\377_\232\003\377f\240\004\377m\246" + "\006\377g\242\001\377p\251\005\377l\246\003\377c\232\002\377a\232\001\377p\001\377M\207\001\377H\204\002\377D}\001\377I\210\001" + "\377S\226\001\377N\224\002\377K\222\002\377\\\241\025\377H\217\003\377D\210\001\377" + "U\233\004\377S\223\001\377X\223\001\377W\216\001\377S\216\001\377X\227\002\377\\\233" + "\002\377Z\222\001\377Y\227\002\377V\227\002\377^\237\010\377]\234\002\377l\245\002\377" + "u\256\003\377d\231\002\377\\\217\002\377Y\216\001\377c\237\004\377{\262\026\377j\240" + "\003\377f\225\001\377e\222\002\377i\234\002\377h\240\001\377a\231\002\377]\226\001\377" + "\\\221\001\377\\\223\001\377_\236\002\377c\237\001\377h\244\001\377d\235\002\377^\226" + "\001\377_\234\001\377Z\230\002\377c\243\016\377a\235\017\377o\251\027\377g\241\005\377" + "g\242\001\377l\246\002\377f\235\000\377j\242\013\377p\246'\377v\253A\377\217\300" + "d\377w\261;\377Y\233\001\377e\250\005\377]\236\001\377_\237\003\377p\254\026\377x" + "\263(\377p\255)\377b\236\036\377|\262<\377s\252-\377\\\233\010\377m\253\023" + "\377d\241\005\377\\\231\002\377[\230\001\377Y\231\001\377\\\227\001\377c\231\001\377" + "k\237\001\377p\247\002\377k\243\002\377e\241\002\377^\231\001\377i\243\001\377p\252\002" + "\377o\246\005\377a\233\002\377_\232\001\377b\236\002\377d\236\002\377b\233\001\377s\255" + "\013\377x\261\013\377j\241\002\377a\225\002\377Z\214\002\377_\225\002\377d\236\001\377" + "b\232\001\377b\240\001\377]\232\001\377X\225\002\377c\245\016\377S\223\002\377[\233" + "\002\377[\226\002\377c\237\004\377n\254\010\377a\242\001\377`\226\001\377h\241\001\377" + "v\256\002\377r\247\001\377j\241\001\377e\236\001\377c\234\002\377f\242\001\377f\236\001" + "\377f\234\002\377d\233\001\377a\225\002\377i\240\003\377k\242\003\377z\261\031\377~" + "\263\037\377h\240\003\377j\244\002\377d\235\002\377`\220\001\377i\243\001\377Cu\002\377" + "I\201\001\377F\201\001\377E{\002\377K\206\001\377U\225\001\377R\226\001\377J\221\001\377" + "R\231\011\377H\220\004\377E\212\002\377R\230\002\377V\226\001\377Z\221\001\377O|\001\377" + "P\203\001\377[\223\001\377`\232\001\377^\226\001\377Z\233\002\377R\226\001\377\\\235" + "\014\377c\243\014\377c\240\001\377l\251\001\377c\235\001\377Z\223\001\377X\223\001\377" + "i\246\010\377x\260\017\377j\235\001\377h\224\002\377h\230\001\377h\236\001\377c\231" + "\001\377Z\223\002\377Z\231\001\377[\226\001\377^\230\001\377a\242\003\377]\237\001\377" + "Y\230\001\377Z\225\001\377_\236\002\377c\240\001\377d\240\002\377d\242\007\377\\\231" + "\006\377d\237\012\377q\255\015\377i\243\002\377k\250\002\377f\243\003\377a\236\010\377" + "f\240\037\377s\254>\377\203\271Q\377s\256\062\377\\\235\003\377h\252\012\377a" + "\242\005\377^\236\003\377n\253\027\377\200\273\060\377j\251\027\377\\\235\006\377" + "]\235\010\377]\235\006\377_\231\002\377e\234\001\377i\240\002\377g\235\001\377a\230" + "\002\377\\\221\001\377d\233\001\377n\246\002\377o\243\001\377p\246\002\377h\241\002\377" + "a\232\002\377k\247\003\377r\255\002\377q\251\001\377j\235\002\377k\243\002\377k\242\001" + "\377j\240\001\377h\234\001\377d\234\002\377x\261\015\377w\261\007\377j\240\002\377_" + "\220\002\377Y\212\002\377V\210\002\377e\236\001\377m\250\002\377l\250\001\377f\242\002" + "\377_\236\005\377b\242\011\377\\\235\003\377]\232\002\377Y\220\001\377n\247\013\377" + "r\254\010\377i\245\001\377k\242\001\377l\241\001\377w\254\001\377x\252\001\377v\256" + "\001\377m\243\002\377d\234\002\377e\236\002\377e\234\002\377c\233\002\377`\227\002\377" + "g\236\002\377p\247\003\377h\234\001\377p\250\015\377z\261\032\377d\240\001\377d\236" + "\001\377e\236\002\377a\224\002\377h\242\001\377G|\002\377K\206\004\377K\211\006\377\071" + "m\001\377N\212\002\377]\237\001\377V\235\001\377M\222\001\377O\226\007\377N\226\007\377" + "I\217\001\377V\233\003\377V\221\001\377N\177\001\377Hu\001\377M}\001\377Y\216\001\377h" + "\241\001\377c\233\001\377[\231\002\377U\227\002\377M\215\002\377Y\230\003\377_\235\001" + "\377g\243\001\377b\235\002\377\\\230\001\377X\224\001\377l\251\012\377n\247\005\377" + "j\233\002\377i\227\002\377k\235\002\377g\236\001\377[\216\001\377U\211\001\377[\230\001" + "\377_\233\001\377`\232\002\377a\243\002\377\\\236\002\377Z\237\001\377Z\232\001\377c" + "\243\002\377o\255\004\377i\245\003\377_\235\001\377\\\233\002\377a\241\002\377i\247\004" + "\377d\241\001\377l\250\010\377e\242\011\377a\237\015\377o\251%\377x\261\066\377" + "v\260\066\377x\262.\377^\236\006\377\\\235\002\377c\245\006\377`\240\003\377q\256" + "\030\377w\263\036\377e\244\007\377b\243\002\377Z\226\001\377]\231\002\377c\236\001\377" + "j\242\002\377m\241\001\377k\241\002\377j\236\002\377f\231\001\377h\235\001\377i\240\001" + "\377h\240\002\377g\244\002\377e\244\002\377j\246\005\377n\251\004\377q\251\001\377o\241" + "\002\377p\241\001\377r\253\002\377p\244\001\377p\243\001\377j\235\002\377h\236\002\377" + "t\257\002\377p\250\001\377j\235\001\377d\223\001\377h\227\003\377]\213\002\377Z\212\001" + "\377i\241\002\377m\247\001\377n\246\003\377o\252\011\377`\241\004\377Z\231\001\377^" + "\231\002\377^\231\001\377p\254\015\377g\245\004\377j\244\001\377p\252\001\377r\245\001" + "\377w\254\001\377{\255\001\377y\262\001\377o\256\001\377a\235\002\377b\234\002\377b\236" + "\001\377`\231\001\377`\227\001\377i\240\002\377m\244\002\377h\236\001\377p\254\016\377" + "r\257\021\377`\237\001\377a\231\002\377c\232\002\377d\227\001\377g\240\001\377J\210" + "\001\377]\233\026\377h\243)\377:j\000\377R\207\002\377b\243\002\377_\242\004\377M\222" + "\001\377N\226\004\377Q\230\005\377Q\227\003\377U\232\003\377R\215\001\377K~\002\377M\200" + "\002\377T\213\001\377a\231\002\377g\236\002\377`\224\002\377]\231\001\377Z\232\001\377" + "Q\215\001\377Q\216\001\377\\\231\001\377f\242\001\377b\231\001\377X\217\002\377Y\225" + "\000\377o\252\012\377f\235\003\377d\227\001\377h\233\002\377h\236\001\377d\240\002\377" + "\\\227\001\377Y\225\001\377^\235\001\377a\234\001\377c\235\002\377c\240\002\377^\234" + "\001\377Z\231\001\377\\\237\001\377a\242\001\377n\252\010\377i\245\010\377\\\235\002" + "\377a\242\001\377d\242\001\377a\233\001\377]\232\001\377i\251\017\377a\241\014\377" + "[\234\010\377l\250\030\377{\264+\377v\260&\377t\261\035\377^\236\003\377\\\230" + "\002\377j\254\012\377c\245\006\377j\251\015\377k\253\014\377c\243\004\377g\251\003\377" + "[\230\001\377Z\227\001\377d\245\001\377h\243\001\377j\235\001\377j\240\001\377m\244\002" + "\377j\243\001\377f\237\002\377h\240\002\377k\245\002\377l\251\003\377f\247\001\377d\242" + "\002\377i\242\001\377n\247\001\377i\234\002\377o\243\002\377x\252\002\377r\241\002\377" + "t\247\001\377n\243\001\377l\240\001\377o\253\002\377j\243\001\377f\236\002\377b\231\002" + "\377\177\255\033\377b\222\006\377U\204\001\377d\233\001\377p\252\002\377j\244\001\377" + "l\253\002\377b\240\001\377a\234\002\377d\233\002\377g\241\003\377m\252\010\377_\237" + "\001\377b\237\001\377h\240\002\377r\246\002\377x\247\002\377\206\267\002\377\200\265" + "\001\377r\254\001\377k\251\002\377e\236\001\377a\232\001\377^\227\001\377a\231\001\377" + "j\241\003\377w\255\020\377z\262\025\377r\256\017\377l\254\014\377\\\236\001\377\\" + "\226\001\377b\233\001\377h\237\001\377l\245\001\377J\210\002\377S\225\016\377o\254-" + "\377K\204\004\377S\217\002\377Z\232\001\377^\241\002\377Y\234\001\377X\236\003\377[\242" + "\011\377\\\242\015\377P\230\003\377P\222\001\377P\212\001\377R\214\001\377[\223\001\377" + "g\236\002\377i\235\001\377^\222\003\377]\231\001\377Y\230\001\377U\226\002\377Q\217\002" + "\377U\224\001\377`\236\002\377c\233\001\377]\221\001\377b\241\003\377q\255\021\377_" + "\233\002\377a\230\001\377e\233\002\377d\234\002\377b\240\002\377c\246\002\377d\247\004" + "\377\\\234\001\377^\235\001\377`\235\002\377a\240\002\377^\233\002\377[\230\002\377a" + "\242\001\377^\237\000\377d\243\012\377j\247\025\377j\250\017\377d\244\003\377e\247" + "\001\377^\232\002\377W\223\001\377\\\240\006\377[\236\003\377Z\234\002\377a\241\006\377" + "s\260\026\377y\266\035\377f\245\006\377a\234\001\377a\235\002\377n\255\007\377j\250" + "\010\377j\247\011\377r\260\021\377f\246\005\377a\243\002\377Z\232\001\377f\246\012" + "\377n\253\011\377h\233\002\377l\241\001\377e\235\001\377d\240\001\377c\241\001\377b" + "\234\002\377j\245\001\377j\246\002\377g\243\002\377i\250\002\377k\245\002\377p\251\002" + "\377o\247\001\377m\241\001\377j\235\002\377q\244\002\377r\242\002\377s\246\002\377n\243" + "\001\377h\237\001\377h\243\001\377e\237\001\377`\233\002\377]\231\002\377j\246\016\377" + "`\230\003\377a\227\001\377g\240\001\377m\252\002\377i\246\001\377i\244\001\377e\235\002" + "\377a\227\002\377i\242\001\377q\253\003\377l\251\003\377b\237\001\377d\242\001\377g\240" + "\001\377m\242\001\377r\244\001\377}\257\001\377\207\271\001\377~\256\001\377t\251\001\377" + "k\242\001\377f\236\002\377b\232\001\377f\237\002\377m\245\003\377o\246\011\377z\263" + "\026\377q\256\020\377q\260\022\377_\241\002\377^\234\002\377e\235\002\377m\245\001\377" + "n\241\001\377V\230\010\377]\236\017\377w\261-\377f\244\026\377S\220\001\377V\222" + "\001\377Z\227\001\377`\234\001\377_\235\002\377[\235\002\377`\245\014\377V\232\003\377" + "W\233\002\377\\\237\001\377Y\233\001\377]\230\001\377d\232\002\377f\232\001\377b\230" + "\001\377^\230\001\377[\231\001\377W\226\002\377X\227\001\377Y\221\001\377e\240\002\377" + "g\236\002\377d\237\001\377m\253\014\377l\253\021\377[\233\001\377b\231\001\377l\237" + "\001\377h\236\002\377c\237\001\377g\250\007\377c\245\005\377\\\236\002\377^\241\001\377" + "\\\234\001\377[\232\001\377[\231\001\377^\232\001\377a\243\002\377Z\235\001\377\\\240" + "\011\377U\231\006\377`\241\012\377f\250\007\377d\247\002\377`\242\002\377V\227\002\377" + "U\232\003\377b\246\011\377\\\236\001\377^\236\001\377i\252\005\377i\251\003\377c\243" + "\002\377b\243\001\377d\245\001\377g\250\002\377a\243\002\377`\241\002\377h\251\005\377" + "e\247\002\377_\235\002\377_\237\002\377d\245\005\377e\236\003\377s\251\001\377w\254\002" + "\377j\241\001\377h\247\002\377a\235\001\377c\234\001\377j\246\002\377n\252\004\377j\247" + "\002\377k\245\001\377l\243\002\377s\255\001\377o\241\002\377k\237\002\377l\236\001\377" + "p\245\002\377m\244\001\377l\244\001\377l\243\001\377i\241\001\377h\237\001\377a\225\002" + "\377_\226\002\377]\224\001\377c\234\002\377j\237\001\377l\235\001\377l\240\001\377p\253" + "\001\377k\242\001\377e\227\002\377f\234\001\377b\226\002\377n\247\002\377s\256\002\377" + "m\241\001\377j\240\001\377j\243\002\377i\240\001\377l\242\001\377q\252\001\377t\253\001" + "\377\210\267\001\377\216\274\001\377{\257\002\377l\245\002\377j\246\001\377j\243\002" + "\377n\244\001\377t\251\005\377u\252\005\377l\245\002\377m\252\006\377r\257\021\377h" + "\246\010\377d\241\002\377f\235\001\377o\245\002\377s\244\001\377O\212\003\377W\227\004" + "\377f\243\022\377r\254\036\377V\223\001\377X\223\001\377Z\230\002\377^\223\001\377" + "a\230\001\377\\\232\001\377Z\237\004\377M\221\001\377P\223\001\377^\243\003\377\\\237" + "\001\377\\\233\001\377b\233\001\377d\241\001\377a\237\001\377\\\234\001\377\\\234\001\377" + "[\224\002\377_\233\001\377_\230\001\377k\246\003\377d\242\004\377]\236\002\377g\250\015" + "\377e\247\015\377[\233\002\377c\237\002\377f\233\002\377f\234\002\377d\242\001\377o" + "\253\017\377f\246\012\377]\236\001\377_\240\002\377Y\225\001\377[\231\001\377`\234" + "\001\377c\237\001\377c\240\001\377[\232\001\377X\232\002\377\\\237\003\377`\242\003\377" + "b\243\004\377d\245\005\377f\250\012\377Z\237\003\377W\231\002\377Y\235\002\377[\235" + "\001\377`\240\002\377b\243\001\377c\242\001\377d\244\001\377c\240\001\377g\246\002\377" + "f\247\001\377`\240\001\377^\241\002\377c\245\001\377h\251\002\377n\253\012\377j\252" + "\012\377b\241\002\377g\242\001\377t\261\002\377q\253\002\377l\247\002\377k\251\002\377" + "b\236\002\377b\236\002\377l\253\012\377}\270\034\377k\250\004\377k\246\001\377m\247" + "\002\377q\256\001\377m\241\001\377n\242\001\377o\242\001\377r\245\001\377q\247\002\377" + "n\246\001\377p\252\001\377l\242\001\377g\235\002\377c\234\001\377`\226\001\377c\227\002" + "\377l\234\001\377m\236\001\377i\231\001\377i\236\001\377i\242\001\377i\241\001\377d\230" + "\001\377h\226\002\377l\233\002\377q\244\001\377t\253\001\377o\241\001\377k\241\002\377" + "j\242\001\377j\243\001\377m\251\002\377m\247\002\377n\244\001\377{\256\001\377\203\266" + "\001\377w\254\001\377m\246\002\377o\246\001\377t\247\002\377y\250\001\377\225\266\023" + "\377\235\274\032\377u\252\003\377j\243\001\377w\264\017\377s\261\013\377g\237\002" + "\377l\242\001\377s\251\001\377s\246\001\377R\212\001\377[\227\002\377c\241\010\377l" + "\247\020\377`\235\002\377Z\225\002\377\\\234\001\377[\222\001\377\\\220\001\377`\235" + "\002\377Z\240\002\377J\223\002\377R\227\003\377V\233\002\377X\226\001\377^\223\002\377" + "a\232\001\377`\231\002\377X\222\002\377X\227\001\377[\233\001\377]\235\001\377\\\233" + "\001\377`\235\001\377k\252\003\377b\234\003\377\\\231\001\377_\243\002\377[\234\001\377" + "Y\224\002\377b\234\002\377b\232\001\377a\231\001\377a\236\002\377_\240\004\377d\247\010" + "\377\\\236\001\377_\236\002\377a\237\001\377c\244\001\377c\240\001\377_\233\001\377\\" + "\234\002\377[\236\001\377a\244\005\377b\244\004\377`\232\002\377d\237\000\377p\254\015" + "\377r\255\031\377[\234\004\377[\234\001\377]\235\001\377`\236\001\377`\235\001\377c" + "\240\001\377c\231\001\377f\246\001\377e\243\001\377g\243\001\377j\245\001\377e\240\002" + "\377_\237\002\377d\245\006\377r\262\022\377w\267\032\377c\244\007\377`\240\002\377" + "d\242\002\377p\257\001\377m\247\001\377h\246\001\377h\247\001\377d\242\002\377`\235\001" + "\377m\255\021\377z\272#\377e\247\003\377i\247\001\377j\237\001\377n\250\002\377m\241" + "\001\377l\237\002\377q\243\002\377s\246\001\377s\246\001\377p\242\001\377n\244\001\377" + "g\236\001\377g\243\001\377e\237\001\377b\227\001\377h\234\001\377n\243\001\377m\244\002" + "\377h\234\001\377g\240\002\377g\245\001\377n\251\002\377o\245\001\377o\236\002\377q\240" + "\001\377r\243\002\377u\254\002\377n\241\002\377m\246\001\377r\255\002\377o\252\002\377" + "j\237\001\377j\237\001\377m\242\001\377t\254\001\377z\257\001\377y\256\002\377t\251\001" + "\377o\241\001\377q\241\001\377v\246\001\377~\255\005\377\206\264\014\377k\242\002\377" + "i\243\001\377w\264\013\377p\256\006\377h\235\002\377n\244\002\377s\250\002\377r\244" + "\002\377e\241\002\377\\\232\002\377\\\235\002\377_\240\003\377]\235\001\377]\226\001\377" + "_\234\002\377\\\222\002\377X\215\001\377a\233\002\377^\240\002\377W\230\001\377Y\233" + "\002\377Y\232\001\377\\\227\001\377^\222\002\377d\233\002\377^\225\001\377X\224\002\377" + "X\224\001\377^\241\004\377i\252\014\377Z\233\002\377^\237\001\377k\254\006\377b\236" + "\002\377^\227\001\377`\242\002\377Y\226\001\377\\\221\001\377d\237\001\377f\241\001\377" + "b\231\002\377^\227\001\377Y\227\001\377Y\233\001\377X\225\001\377\\\233\001\377`\236" + "\001\377c\242\001\377a\237\002\377`\235\001\377^\240\001\377Z\233\001\377\\\237\001\377" + "[\231\001\377_\225\002\377a\234\001\377a\236\003\377a\237\005\377Y\232\001\377\\\234" + "\001\377b\240\001\377a\237\001\377f\244\010\377|\265\034\377i\242\002\377g\242\001\377" + "d\234\001\377i\244\001\377g\237\002\377b\240\001\377\\\236\001\377t\262\033\377\206" + "\301*\377l\254\020\377Z\236\002\377Z\235\001\377a\233\002\377k\243\002\377j\241\001" + "\377g\241\001\377c\241\001\377f\247\001\377a\241\002\377j\254\006\377f\251\010\377i" + "\254\004\377k\247\002\377i\235\001\377l\243\002\377h\234\002\377f\232\002\377g\232\002" + "\377g\232\002\377o\244\001\377n\242\001\377k\245\001\377d\237\001\377c\241\002\377b\231" + "\001\377`\224\002\377b\227\001\377k\244\002\377p\253\002\377j\237\002\377j\243\002\377" + "j\241\001\377p\250\001\377u\254\001\377s\246\002\377j\234\002\377h\233\001\377p\250\002" + "\377o\251\001\377m\244\001\377z\265\004\377r\251\001\377n\242\001\377l\241\001\377i\240" + "\001\377l\243\001\377l\237\001\377w\253\001\377w\253\001\377m\237\001\377o\241\001\377" + "o\241\002\377o\246\001\377n\252\001\377j\240\001\377l\244\001\377y\264\005\377y\262\005" + "\377l\242\001\377k\240\001\377n\245\002\377j\235\001\377a\240\001\377`\233\002\377_\237" + "\001\377[\235\001\377X\227\001\377X\222\001\377[\231\001\377[\225\002\377W\213\001\377" + "\\\223\001\377`\236\002\377\\\230\002\377\\\227\001\377Y\227\001\377\\\231\001\377[\226" + "\001\377a\237\001\377a\237\002\377Z\231\001\377X\226\001\377_\236\002\377m\254\015\377" + "^\240\002\377\\\235\001\377k\254\007\377c\240\003\377_\230\000\377b\243\001\377_\237" + "\001\377Z\222\002\377`\227\002\377d\236\002\377e\241\001\377c\235\002\377a\237\001\377" + "_\234\001\377\\\224\001\377_\231\001\377c\241\001\377b\236\002\377`\235\002\377_\237" + "\003\377d\244\017\377_\236\016\377X\231\002\377X\223\001\377\\\231\002\377a\240\001\377" + "[\230\001\377Y\230\001\377\\\234\001\377a\242\001\377b\242\001\377\\\233\002\377j\251" + "\016\377y\261\035\377j\235\000\377o\243\002\377]\221\002\377f\236\002\377g\244\001\377" + "_\237\001\377e\247\012\377\177\275)\377w\266\040\377b\244\011\377Z\236\004\377X" + "\232\002\377^\231\001\377f\236\002\377f\234\001\377f\241\001\377c\242\001\377j\254\004" + "\377h\250\003\377f\247\002\377c\241\001\377g\241\001\377n\250\001\377o\244\002\377r\246" + "\002\377m\237\002\377c\226\001\377c\226\001\377g\236\001\377o\246\001\377o\243\001\377" + "o\241\002\377m\237\002\377t\252\002\377f\230\001\377b\231\001\377a\225\001\377h\240\001" + "\377j\244\002\377q\254\001\377k\244\001\377i\235\002\377q\250\001\377u\257\002\377o\246" + "\001\377c\230\001\377e\233\002\377p\252\001\377n\247\002\377l\244\001\377t\261\002\377" + "p\253\001\377n\251\002\377l\251\003\377e\236\002\377k\244\001\377j\240\001\377u\254\002" + "\377z\256\001\377t\243\002\377m\235\001\377m\241\002\377s\256\001\377o\255\001\377h\235" + "\002\377n\244\001\377u\252\001\377\177\265\003\377q\245\001\377j\235\002\377i\241\001\377" + "f\236\002\377a\240\001\377`\231\001\377b\241\002\377`\240\001\377X\222\002\377W\220\001" + "\377\\\232\001\377[\225\001\377Y\220\001\377]\226\001\377a\232\001\377c\230\001\377a" + "\226\001\377^\222\002\377b\232\002\377`\234\004\377b\241\006\377e\245\007\377Z\226\001" + "\377[\223\001\377_\237\002\377g\247\003\377f\245\001\377a\235\001\377e\246\002\377a\240" + "\002\377a\231\001\377c\245\001\377a\243\002\377R\214\001\377Y\223\002\377`\234\002\377" + "c\244\001\377b\243\003\377_\240\002\377Z\226\002\377`\230\001\377d\231\001\377n\253\003" + "\377m\255\004\377a\243\004\377V\222\007\377Z\203)\377\213\256]\377g\235\"\377O" + "\220\000\377X\224\001\377\\\233\001\377Z\231\001\377Y\226\001\377Z\226\001\377^\234\002" + "\377a\243\001\377j\253\014\377h\247\007\377`\227\001\377m\243\001\377p\243\001\377Y" + "\217\002\377h\236\001\377h\247\002\377]\235\002\377o\257\032\377x\266\040\377j\253" + "\017\377Z\237\004\377X\236\004\377W\231\002\377\\\234\002\377j\250\007\377c\240\001\377" + "g\246\002\377j\252\004\377n\256\007\377l\254\007\377`\237\002\377c\241\001\377i\240\001" + "\377r\252\001\377w\253\001\377t\243\002\377l\234\001\377e\227\002\377a\225\001\377h\246" + "\001\377k\250\001\377l\241\002\377q\243\002\377t\251\002\377o\242\002\377h\234\001\377" + "c\232\002\377^\226\001\377f\245\001\377e\236\001\377s\260\006\377h\243\003\377g\235\002" + "\377o\247\001\377r\254\001\377j\236\002\377a\226\002\377g\236\001\377n\251\001\377l\246" + "\002\377j\247\001\377o\260\003\377i\251\001\377m\254\005\377z\267\020\377g\246\001\377" + "g\243\001\377j\246\001\377n\244\001\377t\247\002\377{\262\001\377u\245\001\377r\245\002" + "\377r\251\001\377m\243\001\377i\240\001\377n\250\001\377r\253\001\377|\264\002\377u\256" + "\002\377m\241\001\377g\235\002\377i\245\001\377X\227\001\377Y\221\002\377`\237\001\377" + "b\240\002\377\\\222\001\377^\230\000\377`\235\001\377^\225\001\377Y\217\002\377_\226" + "\002\377_\230\002\377`\234\003\377a\230\001\377d\233\002\377g\237\004\377`\233\004\377" + "g\245\020\377b\243\011\377[\232\002\377[\221\001\377_\232\001\377j\250\004\377k\253" + "\004\377b\240\001\377b\241\001\377c\241\002\377d\234\002\377m\256\003\377e\244\002\377" + "W\215\001\377[\222\001\377b\235\002\377c\241\001\377f\251\004\377f\246\005\377`\233\001" + "\377f\235\002\377m\243\001\377w\263\001\377n\251\002\377Z\223\001\377a\220\032\377\335" + "\317\305\377\377\362\365\377\373\370\361\377\225\273W\377S\220\000\377V\222" + "\001\377W\225\002\377Y\225\002\377X\217\002\377Z\232\002\377\\\235\007\377{\272(\377" + "d\240\010\377`\226\002\377l\245\002\377p\251\001\377j\236\002\377n\246\001\377j\251" + "\001\377f\245\007\377i\251\022\377h\250\014\377[\233\001\377^\240\001\377`\242\002\377" + "_\236\002\377i\251\010\377m\254\015\377Z\232\001\377]\235\002\377^\236\001\377_\240" + "\001\377i\254\004\377`\241\001\377a\234\002\377g\234\002\377r\250\002\377o\240\002\377" + "o\240\002\377k\234\001\377g\235\002\377h\246\002\377i\253\003\377h\243\001\377n\245\001" + "\377p\246\002\377q\253\001\377j\235\002\377f\236\002\377c\227\002\377b\231\001\377i\245" + "\001\377g\241\001\377n\256\005\377d\244\004\377f\240\002\377n\243\001\377s\246\001\377" + "m\242\001\377f\230\002\377h\233\002\377m\246\001\377m\245\001\377p\256\001\377m\251\002" + "\377m\247\001\377l\250\003\377v\267\014\377e\247\002\377a\243\001\377`\241\002\377c" + "\233\002\377l\244\001\377s\253\001\377r\252\001\377v\254\002\377o\244\001\377j\234\001" + "\377h\241\002\377o\256\001\377t\264\003\377t\261\002\377q\253\002\377l\246\001\377f\235" + "\002\377q\254\010\377[\230\001\377X\220\001\377[\227\001\377^\230\001\377_\225\001\377" + "a\231\001\377c\242\001\377_\232\002\377]\224\001\377\\\225\000\377e\243\010\377g\250" + "\010\377^\224\001\377e\236\002\377a\232\001\377e\237\004\377l\247\012\377c\236\002\377" + "e\235\001\377f\235\002\377i\242\002\377j\246\002\377k\253\003\377f\246\001\377k\252\002" + "\377j\244\001\377m\243\001\377r\255\002\377d\233\002\377Z\217\003\377W\214\001\377n\242" + "\001\377o\251\001\377g\245\001\377d\240\001\377g\234\002\377o\252\002\377t\255\002\377" + "v\256\001\377n\243\001\377Z\224\000\377\243\264}\377\344\326\333\377\366\352\356" + "\377\373\363\366\377\373\362\363\377\257\311\177\377N\210\000\377Y\225\002\377" + "[\227\001\377Y\226\001\377Z\235\001\377f\250\014\377x\264\034\377r\257\017\377d\237" + "\002\377q\254\002\377s\255\002\377k\236\002\377f\233\002\377d\240\001\377f\250\005\377" + "\\\235\002\377`\241\006\377Y\226\001\377_\237\001\377c\240\002\377d\236\001\377h\250" + "\003\377a\241\002\377]\237\003\377\\\236\002\377W\225\001\377_\233\002\377g\247\002\377" + "d\243\001\377[\222\001\377b\227\002\377i\236\002\377c\227\001\377e\232\000\377f\237\000" + "\377p\253\010\377g\250\003\377g\244\001\377l\243\001\377l\243\002\377o\254\001\377k" + "\251\001\377e\235\002\377d\241\001\377b\240\002\377a\234\002\377j\246\001\377i\237\001" + "\377m\250\001\377h\243\001\377j\243\002\377n\242\001\377p\244\001\377s\251\001\377l\237" + "\001\377m\241\001\377p\245\002\377r\247\001\377w\263\001\377s\250\001\377q\247\002\377" + "o\246\001\377v\267\006\377f\247\003\377]\240\001\377[\241\002\377\\\233\001\377l\246" + "\001\377s\253\002\377s\255\002\377q\254\003\377j\246\003\377c\235\002\377d\242\001\377" + "g\250\002\377k\251\003\377k\245\003\377t\255\011\377s\253\014\377l\247\013\377u\260" + "\021\377b\234\001\377`\234\001\377^\227\002\377a\231\002\377f\241\001\377f\237\001\377" + "i\243\001\377a\227\001\377X\217\000\377e\243\021\377~\271/\377a\233\001\377^\222\002" + "\377a\231\001\377g\235\003\377q\244\007\377m\241\002\377k\236\002\377m\232\001\377n\236" + "\001\377i\233\002\377_\222\001\377_\232\002\377d\246\002\377o\255\004\377r\252\001\377" + "}\261\001\377y\254\002\377^\223\002\377X\216\002\377T\212\002\377u\246\002\377w\255\002" + "\377e\237\001\377e\240\002\377i\237\002\377p\252\001\377h\234\002\377o\244\002\377l\242" + "\001\377V\222\000\377\331\320\317\377\324\303\313\377\341\317\331\377\363\345" + "\354\377\353\332\332\377\364\355\351\377y\251\061\377V\231\000\377W\221\001\377" + "_\236\004\377n\257\023\377p\257\023\377l\254\014\377p\260\013\377j\251\002\377p\252" + "\002\377s\245\001\377l\237\001\377b\226\001\377f\243\001\377j\254\003\377\\\236\001\377" + "Z\234\002\377Y\232\001\377]\234\001\377f\242\001\377e\234\001\377d\244\002\377`\243\002" + "\377_\243\005\377V\234\001\377T\220\002\377f\243\001\377k\250\001\377g\247\001\377]\224" + "\001\377_\227\000\377t\246\027\377\223\272G\377\242\277b\377u\215\071\377^\234" + "\011\377^\237\001\377f\240\002\377m\250\001\377m\252\001\377h\247\002\377d\245\001\377" + "c\243\001\377b\244\001\377]\234\001\377b\244\001\377e\244\002\377g\235\002\377p\251\001" + "\377m\244\001\377l\246\001\377j\237\002\377o\244\001\377t\255\001\377q\250\001\377q\252" + "\001\377p\244\002\377t\253\002\377y\263\002\377y\263\001\377u\253\001\377r\245\002\377" + "r\255\001\377h\240\001\377c\242\001\377e\247\004\377a\234\002\377q\252\002\377v\245\002" + "\377z\260\001\377n\254\004\377m\257\011\377d\243\001\377g\247\001\377g\245\001\377i" + "\245\001\377j\242\004\377~\263\032\377\203\263\040\377\202\264\037\377s\254\013\377" + "g\232\002\377n\245\007\377`\226\002\377`\224\002\377f\242\001\377f\240\001\377g\243\002" + "\377f\236\001\377f\240\003\377\205\274\071\377\200\270\066\377d\233\002\377b\230" + "\002\377`\226\002\377d\233\001\377z\260\015\377m\241\003\377g\226\001\377k\231\002\377" + "n\234\001\377j\234\001\377e\231\001\377`\225\001\377c\240\002\377e\243\002\377h\237\002" + "\377\210\267\001\377y\251\001\377_\225\002\377^\225\001\377X\214\001\377v\247\002\377" + "y\256\001\377e\232\002\377f\233\002\377m\240\002\377l\237\001\377]\221\002\377t\251\001" + "\377t\257\000\377k\233\034\377\326\301\315\377\313\267\301\377\347\331\343\377" + "\367\356\364\377\362\345\352\377\354\332\332\377\357\354\326\377W\221\011" + "\377Q\221\000\377c\244\011\377q\260\030\377e\246\006\377_\240\002\377[\225\002\377" + "d\244\001\377n\247\002\377u\251\001\377u\247\002\377k\236\002\377n\252\002\377l\251\002" + "\377Z\231\002\377T\225\002\377[\234\004\377a\236\003\377f\244\001\377f\240\001\377e\247" + "\002\377^\237\002\377Y\230\001\377U\221\001\377Y\225\002\377d\243\002\377d\241\002\377" + "b\240\002\377X\225\000\377\253\306s\377\363\357\336\377\353\335\332\377\345\326" + "\322\377\311\273\261\377e\212\"\377^\237\000\377d\240\001\377m\250\001\377l\246" + "\001\377b\232\002\377^\226\001\377^\236\001\377k\255\010\377o\257\007\377d\245\000\377" + "c\240\002\377g\234\002\377r\254\002\377k\243\002\377i\243\001\377e\234\001\377l\244\001" + "\377t\260\001\377v\257\001\377v\253\002\377o\240\002\377u\247\002\377z\262\001\377|\262" + "\001\377z\255\002\377r\243\003\377t\250\001\377n\243\002\377j\243\002\377p\254\003\377" + "l\245\001\377v\257\004\377q\245\001\377u\255\002\377t\263\007\377i\252\002\377e\245\002" + "\377o\254\001\377q\252\001\377o\247\002\377l\241\002\377{\257\014\377\204\263\027\377" + "\201\261\022\377r\252\004\377m\245\001\377f\236\002\377b\236\002\377d\236\002\377j\246" + "\002\377i\242\001\377i\242\001\377e\237\002\377t\255\030\377\220\304E\377u\254\032" + "\377g\233\001\377f\226\001\377f\225\001\377f\226\001\377p\252\001\377m\247\002\377c\230" + "\002\377d\232\001\377j\241\002\377g\233\002\377e\236\002\377d\236\001\377e\241\001\377" + "f\241\001\377d\227\002\377\201\261\002\377y\252\002\377g\233\001\377b\232\002\377c\230" + "\001\377u\250\002\377|\260\001\377j\236\002\377g\233\002\377i\235\002\377k\236\001\377" + "e\231\002\377{\256\001\377\203\273\014\377\230\262`\377\317\266\304\377\271\236" + "\252\377\350\332\344\377\360\342\353\377\341\316\325\377\335\307\307\377" + "\334\304\303\377\326\340\261\377`\236\020\377n\255\035\377x\267$\377e\244\004" + "\377a\233\001\377[\220\002\377e\235\001\377n\244\001\377q\245\002\377r\243\001\377t\254" + "\002\377s\253\001\377s\247\001\377d\234\001\377P\221\002\377m\251\003\377l\244\002\377" + "d\234\001\377e\244\003\377f\247\003\377`\240\002\377\\\222\002\377\\\225\002\377^\234" + "\002\377_\241\003\377X\227\000\377s\242\026\377\336\343\246\377\352\326\322\377" + "\335\306\276\377\330\303\273\377\314\271\260\377\330\311\306\377\236\245" + "r\377[\234\000\377g\250\002\377e\243\001\377d\234\001\377b\231\001\377_\230\001\377^" + "\224\001\377b\232\001\377l\251\004\377n\255\003\377h\242\001\377n\243\001\377w\261\001" + "\377m\241\002\377k\242\002\377e\233\001\377m\251\002\377u\260\002\377v\254\002\377u\250" + "\001\377q\243\002\377w\250\002\377}\264\002\377~\262\001\377z\255\001\377n\240\001\377" + "o\247\002\377n\245\002\377j\242\002\377n\252\002\377s\257\007\377n\250\003\377l\244\002" + "\377p\253\001\377s\260\003\377n\256\001\377l\246\001\377n\251\002\377t\256\001\377p\251" + "\001\377o\246\002\377|\262\014\377w\255\010\377m\246\001\377k\245\001\377h\243\002\377" + "e\243\001\377c\235\001\377f\236\001\377x\263\006\377n\242\002\377l\240\001\377d\234\001" + "\377d\231\014\377\212\301B\377l\253\020\377h\244\004\377i\240\002\377l\241\002\377" + "g\230\001\377r\253\001\377l\246\001\377h\247\002\377e\241\001\377o\250\004\377e\234\002" + "\377o\251\011\377s\256\017\377k\251\003\377h\240\001\377k\234\001\377\202\262\002\377" + "s\242\002\377h\234\001\377g\245\002\377j\250\002\377p\252\001\377x\260\002\377r\246\001" + "\377]\225\002\377g\235\003\377f\232\003\377q\244\002\377q\246\001\377i\245\000\377\245" + "\263z\377\321\275\307\377\323\277\313\377\360\345\357\377\362\347\360\377" + "\357\345\352\377\342\314\320\377\341\314\314\377\346\315\303\377\231\272" + "T\377i\246\014\377g\245\005\377_\224\002\377`\227\001\377d\230\001\377i\234\002\377" + "p\243\002\377o\241\002\377o\243\002\377s\255\001\377r\251\001\377q\245\002\377j\236\002" + "\377T\223\002\377v\255\002\377j\242\001\377e\241\002\377i\246\004\377f\242\001\377f\236" + "\002\377d\227\001\377g\235\001\377d\241\002\377]\234\002\377{\254\060\377\365\362\337" + "\377\332\305\277\377\317\265\260\377\330\302\275\377\322\274\272\377\311" + "\262\255\377\301\257\251\377\276\264\242\377U\223\002\377]\236\002\377a\237\002" + "\377c\234\002\377a\234\002\377a\236\002\377`\225\002\377c\237\001\377c\236\001\377g\244" + "\001\377l\243\002\377s\251\001\377{\263\001\377n\240\002\377m\241\001\377j\241\001\377" + "k\242\002\377o\251\001\377o\247\001\377k\243\002\377j\240\001\377p\251\001\377y\262\001" + "\377x\262\001\377x\262\001\377k\237\002\377j\242\001\377j\240\001\377j\243\002\377j\243" + "\002\377m\247\001\377k\242\002\377o\242\001\377s\252\002\377t\244\002\377t\255\002\377" + "u\257\001\377k\242\001\377m\247\001\377i\242\001\377l\245\001\377s\255\005\377f\241\001" + "\377e\243\001\377i\247\001\377g\243\001\377e\243\002\377d\232\001\377o\247\005\377u\261" + "\011\377n\250\001\377n\244\001\377l\242\001\377a\226\002\377u\260\021\377d\245\005\377" + "_\241\003\377k\252\022\377n\252\021\377k\243\005\377o\246\001\377t\252\002\377p\251" + "\001\377k\243\001\377m\244\002\377k\233\002\377j\235\002\377i\235\002\377q\253\003\377" + "y\257\001\377\177\255\001\377\207\264\001\377o\235\002\377e\231\002\377_\222\001\377" + "b\234\001\377m\252\001\377u\257\002\377j\234\002\377_\223\001\377n\242\001\377j\237\001" + "\377k\242\001\377c\227\002\377_\234\000\377\262\272\213\377\303\254\266\377\324" + "\277\312\377\336\316\333\377\372\365\371\377\355\341\351\377\344\321\331" + "\377\335\304\310\377\327\277\277\377\347\324\276\377t\247\025\377[\220\001\377" + "W\216\000\377a\233\000\377e\234\000\377k\241\000\377p\246\000\377n\243\001\377n\250\001" + "\377l\246\002\377n\246\002\377h\235\002\377d\236\001\377_\233\002\377z\260\001\377h\241" + "\002\377k\251\006\377x\261\031\377i\245\006\377i\246\003\377b\231\001\377c\235\002\377" + "^\233\000\377\217\265?\377\356\344\327\377\323\273\265\377\313\257\246\377" + "\341\315\315\377\334\313\315\377\307\263\265\377\266\240\235\377\272\244" + "\236\377\313\273\261\377Y\226\011\377_\237\003\377a\232\002\377_\226\001\377]\225" + "\002\377b\241\002\377c\237\001\377e\243\001\377c\234\002\377i\240\001\377k\236\002\377" + "{\255\002\377\201\263\001\377q\243\002\377q\250\002\377m\245\001\377l\240\001\377n\245" + "\001\377p\250\001\377h\235\002\377i\240\002\377q\254\002\377|\264\002\377}\263\002\377" + "z\263\001\377n\246\001\377m\245\001\377l\250\000\377h\245\000\377o\247\011\377\203\266" + "\035\377|\260\023\377n\241\001\377v\254\001\377x\247\002\377}\256\001\377|\257\001\377" + "s\246\002\377t\256\001\377n\245\001\377m\243\001\377j\243\001\377e\234\002\377e\237\002" + "\377j\243\001\377q\251\002\377c\236\001\377c\237\001\377k\251\004\377d\242\002\377o\255" + "\005\377o\250\002\377p\245\001\377o\243\003\377x\263\012\377g\241\000\377e\246\006\377" + "_\240\023\377d\243\024\377g\241\007\377w\255\005\377{\256\001\377z\261\001\377v\257" + "\003\377s\254\006\377o\243\005\377k\237\002\377h\234\001\377t\253\003\377\200\265\002\377" + "\211\264\001\377\214\263\002\377w\245\001\377k\242\002\377c\227\001\377_\227\001\377" + "i\247\001\377p\254\001\377d\225\002\377f\230\002\377o\241\002\377p\247\001\377h\235\002" + "\377a\230\001\377U\216\000\377\275\301\232\377\305\247\252\377\303\242\243\377" + "\347\325\335\377\364\352\362\377\364\347\357\377\346\323\334\377\312\257" + "\264\377\340\313\315\377\316\265\261\377\316\316\230\377Z\214\022\377a\225" + "\035\377`\222\"\377`\217&\377m\235\060\377p\242%\377e\235\016\377f\240\012\377" + "h\243\010\377c\234\004\377a\223\001\377d\237\001\377e\237\003\377x\261\005\377f\234" + "\000\377y\261\040\377r\257\040\377o\257\025\377f\247\005\377e\242\001\377e\235\001\377" + "\226\273G\377\354\336\316\377\304\251\245\377\342\311\310\377\327\301\277" + "\377\333\310\312\377\327\310\316\377\313\275\302\377\277\256\257\377\271" + "\246\242\377\320\276\270\377c\232\013\377d\241\001\377c\227\001\377c\226\002\377" + "h\241\001\377e\236\001\377o\256\010\377c\240\001\377a\231\001\377g\242\002\377j\235" + "\002\377}\257\001\377\201\263\001\377w\252\002\377q\246\001\377n\246\002\377l\240\002\377" + "r\247\001\377r\252\001\377f\232\002\377g\241\002\377v\260\001\377~\261\002\377{\252\001" + "\377|\257\001\377u\252\001\377v\263\010\377t\256\014\377x\263\031\377\211\300\063" + "\377\205\273+\377~\266\032\377r\254\002\377w\261\001\377|\262\002\377{\255\002\377" + "v\250\002\377p\237\001\377y\253\002\377z\252\002\377n\236\001\377l\237\001\377j\241\001" + "\377l\243\001\377n\241\002\377p\250\002\377g\235\002\377j\244\004\377m\250\006\377`\232" + "\001\377l\251\006\377r\251\001\377{\256\001\377p\241\001\377t\253\004\377o\241\001\377" + "m\246\003\377g\245\030\377Y\230\013\377l\246\027\377\211\273%\377\204\267\015\377" + "}\262\002\377}\266\012\377z\264\024\377v\260\031\377t\255\026\377\205\274'\377" + "\205\275\035\377t\246\001\377\201\255\001\377\221\271\001\377o\237\002\377f\231\002" + "\377i\242\001\377d\240\002\377d\243\002\377n\254\002\377j\235\001\377j\231\002\377r\241" + "\002\377s\245\001\377i\235\001\377a\230\002\377S\214\000\377\321\316\265\377\316\260" + "\263\377\332\277\306\377\366\352\362\377\373\367\372\377\365\355\362\377" + "\354\337\345\377\337\311\315\377\317\266\267\377\302\252\244\377\324\271" + "\253\377\270\266\232\377emd\377aff\377^bg\377_ej\377gpo\377o\200l\377s\216" + "b\377n\217M\377w\240G\377f\226(\377`\225\030\377b\225\021\377v\254\017\377k" + "\240\012\377o\246\031\377k\247\030\377h\244\017\377a\233\001\377i\244\000\377\272" + "\321u\377\331\304\264\377\301\241\227\377\302\242\232\377\337\313\310\377" + "\336\314\316\377\333\313\321\377\341\324\335\377\312\275\310\377\273\254" + "\265\377\274\251\253\377\310\266\262\377^\227\014\377c\236\002\377b\226\001\377" + "l\242\002\377r\253\001\377l\246\001\377j\252\004\377c\246\002\377`\230\001\377i\243\002" + "\377m\237\001\377\200\262\001\377w\247\002\377r\246\002\377p\247\001\377o\244\001\377" + "o\241\002\377v\256\002\377x\261\001\377e\233\002\377g\236\001\377u\251\002\377\200\263" + "\002\377z\252\001\377\206\272\016\377\203\271\031\377\211\301-\377\200\274-\377" + "o\256\027\377k\250\012\377p\255\004\377o\246\003\377m\241\001\377p\245\001\377q\246" + "\002\377r\253\002\377p\253\002\377m\236\001\377~\260\001\377|\251\001\377p\234\001\377" + "u\250\001\377s\250\001\377q\245\001\377u\252\000\377f\232\001\377r\251\006\377o\247\004" + "\377j\241\002\377g\237\001\377i\242\003\377q\250\002\377\205\265\001\377|\256\001\377" + "o\246\002\377i\237\001\377i\243\002\377m\252\035\377d\242\040\377o\254*\377l\250" + "\027\377\203\265\017\377\204\265\005\377z\255\002\377s\253\006\377v\261\027\377|\266" + "!\377~\267\040\377|\267\025\377s\250\000\377x\246\001\377\206\262\002\377z\252\002" + "\377i\234\001\377q\253\001\377l\246\001\377k\245\002\377o\252\001\377r\253\001\377o\240" + "\001\377u\250\001\377n\240\001\377j\234\002\377e\235\002\377Y\212\000\377\321\311\261" + "\377\321\266\271\377\327\277\307\377\347\325\337\377\345\323\336\377\336" + "\314\326\377\341\317\327\377\314\271\274\377\271\241\240\377\301\251\250" + "\377\212gf\377\306\250\226\377xei\377~ow\377XI\\\377XKa\377[Ne\377XLd\377" + "i`r\377tn}\377}{\206\377\201\206\210\377z\205x\377x\212d\377p\222C\377n\226" + "?\377f\225,\377l\236.\377d\227\034\377d\226\016\377\271\314q\377\301\254\237" + "\377\251\215\213\377\313\261\254\377\330\304\300\377\341\320\320\377\362" + "\350\353\377\324\306\316\377\346\332\343\377\311\274\306\377\275\251\262" + "\377\271\243\243\377\330\302\274\377i\237\022\377i\240\001\377o\241\002\377y\257" + "\001\377n\241\002\377a\227\001\377c\244\001\377g\251\003\377e\235\002\377p\246\001\377" + "t\245\002\377\202\263\001\377c\227\002\377f\235\001\377j\245\001\377m\245\002\377o\244" + "\001\377z\265\006\377x\262\003\377p\247\002\377p\243\001\377w\252\001\377|\264\000\377" + "\202\273\015\377\202\271\027\377v\261\016\377w\261\015\377]\225\000\377c\240\001" + "\377i\240\001\377q\244\001\377r\242\002\377x\251\002\377q\240\002\377p\241\001\377l\242" + "\002\377n\244\001\377y\253\001\377\201\262\001\377r\240\001\377s\241\002\377v\247\001\377" + "q\244\002\377q\246\002\377\201\271\012\377_\222\002\377l\246\004\377l\245\001\377m\241" + "\001\377q\251\001\377h\232\001\377s\250\002\377\177\260\001\377\201\263\004\377z\256" + "\020\377n\244\014\377g\237\003\377l\246\022\377p\254$\377i\247\034\377c\242\005\377" + "n\247\002\377z\253\002\377\200\260\002\377z\255\002\377u\257\006\377y\262\021\377\202" + "\273\036\377t\257\013\377u\254\002\377z\252\001\377z\247\002\377\202\261\001\377y\251" + "\002\377v\247\001\377t\250\001\377v\243\002\377\177\257\001\377v\251\001\377u\257\001\377" + "l\245\001\377h\235\002\377c\226\002\377d\231\001\377[\216\000\377\332\325\272\377\342" + "\307\311\377\335\302\307\377\361\345\354\377\375\375\374\377\367\354\363" + "\377\357\336\347\377\333\306\312\377\312\260\261\377\262\225\223\377\312" + "\260\252\377\317\264\254\377\307\256\254\377\332\306\306\377\321\277\304" + "\377\324\312\321\377\260\237\257\377wf\177\377\200m\211\377\233\215\242\377" + "\212|\222\377\207v\215\377\201r\206\377\226\207\220\377zoz\377e`h\377ggh" + "\377t|r\377\216\227\200\377\313\320\253\377\330\300\271\377\254\214\210\377" + "\312\260\254\377\330\303\277\377\323\272\272\377\357\337\343\377\332\311" + "\321\377\360\344\355\377\343\330\345\377\311\274\311\377\302\257\267\377" + "\305\260\257\377\320\277\264\377^\232\002\377m\243\001\377x\253\002\377z\252\002" + "\377m\235\002\377h\240\001\377f\246\001\377d\241\002\377j\250\001\377s\253\001\377x\252" + "\001\377~\255\002\377`\223\002\377a\225\001\377d\244\001\377l\252\001\377p\252\001\377" + "t\257\002\377q\247\001\377v\256\002\377v\250\001\377\200\267\002\377\201\270\002\377" + "\201\271\006\377s\251\002\377q\251\002\377\200\271\013\377e\232\001\377f\233\001\377" + "l\243\001\377i\231\002\377o\240\002\377|\255\002\377y\246\002\377r\237\002\377n\236\001" + "\377u\250\001\377{\260\002\377z\250\001\377w\241\002\377{\251\001\377w\250\001\377s\247" + "\001\377y\260\001\377\202\271\005\377_\223\002\377i\243\002\377p\251\003\377n\237\001\377" + "p\247\001\377l\236\001\377r\244\002\377x\252\001\377\177\261\003\377\210\267\027\377" + "\221\276\060\377m\243\006\377j\243\003\377w\260\030\377d\243\015\377l\253\017\377" + "s\255\006\377r\243\002\377v\253\001\377}\266\014\377k\247\005\377s\255\023\377\204" + "\274(\377l\240\012\377q\240\002\377~\255\001\377{\250\002\377x\245\001\377\177\261" + "\001\377y\250\002\377x\245\002\377|\247\002\377\204\260\001\377t\243\002\377p\247\002\377" + "l\247\001\377d\236\002\377Y\215\001\377]\216\001\377X\217\000\377\326\320\262\377\343" + "\303\302\377\354\321\325\377\341\321\333\377\363\353\360\377\353\335\344" + "\377\360\335\337\377\340\312\307\377\337\310\304\377\347\317\312\377\353" + "\326\323\377\345\323\321\377\360\341\346\377\361\347\354\377\365\360\363" + "\377\366\356\363\377\344\327\341\377\340\322\334\377\346\332\343\377\370" + "\361\365\377\360\351\355\377\336\322\333\377\303\256\267\377\322\276\275" + "\377\312\262\255\377\271\233\220\377\260\223\214\377\232\202\177\377\263" + "\235\226\377\301\241\220\377\215oh\377\272\233\226\377\307\256\252\377\333" + "\310\310\377\347\327\333\377\364\352\360\377\327\307\321\377\337\320\332" + "\377\325\310\326\377\303\263\300\377\277\251\260\377\305\253\252\377\310" + "\274\253\377_\240\000\377h\241\001\377k\232\002\377z\251\002\377t\245\001\377n\244" + "\001\377g\241\001\377f\236\001\377m\246\001\377u\254\002\377|\254\002\377w\247\001\377" + "[\216\002\377^\223\001\377k\250\003\377u\264\004\377r\250\001\377t\252\001\377t\251\001" + "\377u\256\001\377u\252\001\377{\260\001\377~\257\001\377\204\264\001\377\203\263\002" + "\377\200\263\002\377|\264\003\377a\223\002\377j\242\002\377k\245\002\377m\244\001\377" + "w\255\001\377t\245\002\377u\242\002\377w\246\002\377w\247\001\377v\254\001\377o\242\001" + "\377r\244\002\377\216\271\016\377\210\264\011\377{\255\002\377\177\267\002\377\201" + "\270\002\377~\261\002\377b\237\001\377c\233\002\377h\243\002\377o\252\004\377p\250\001" + "\377o\246\002\377q\251\003\377t\245\001\377~\256\001\377\207\265\020\377\226\277." + "\377l\240\003\377o\246\003\377t\255\007\377f\244\005\377r\257\022\377m\243\003\377|" + "\257\002\377w\255\001\377m\247\003\377\213\270\064\377p\251\033\377\200\271)\377" + "d\231\003\377k\235\002\377r\251\002\377r\251\002\377o\243\001\377u\253\000\377x\257\001" + "\377v\255\001\377w\256\001\377u\254\002\377m\241\001\377n\237\001\377r\255\002\377`\226" + "\002\377Y\214\002\377_\220\002\377[\220\000\377\305\307\223\377\344\277\273\377\352" + "\313\312\377\337\312\322\377\356\341\351\377\354\336\340\377\362\337\334" + "\377\362\343\337\377\367\356\355\377\342\316\317\377\277\245\247\377\344" + "\320\324\377\365\356\360\377\271\244\260\377\364\355\360\377\324\300\314" + "\377\342\322\334\377\360\346\354\377\357\345\352\377\305\263\272\377\352" + "\340\344\377\353\336\341\377\314\262\262\377\334\305\301\377\347\323\315" + "\377\342\313\302\377\334\300\263\377\343\303\257\377\333\274\243\377\303" + "\242\212\377\312\254\232\377\306\251\237\377\277\244\236\377\354\331\332" + "\377\332\305\314\377\356\342\352\377\332\311\323\377\320\276\311\377\306" + "\266\302\377\276\254\265\377\302\252\256\377\321\270\263\377\267\270\214" + "\377b\244\000\377i\243\001\377j\232\001\377|\255\002\377w\253\002\377l\235\001\377h\231" + "\002\377i\235\001\377l\237\001\377u\251\002\377{\256\001\377k\233\002\377Z\216\001\377" + "m\250\010\377x\266\014\377p\252\002\377r\246\002\377s\247\002\377v\260\002\377}\270" + "\013\377u\253\002\377{\256\001\377|\255\002\377z\246\002\377\203\261\002\377\216\275" + "\002\377u\260\002\377c\225\002\377l\245\001\377q\255\002\377o\247\001\377x\264\002\377" + "j\240\001\377k\235\002\377m\236\002\377t\251\001\377s\254\002\377n\245\001\377o\243\001" + "\377\221\302\036\377\203\266\011\377\202\267\003\377\202\272\004\377|\260\001\377" + "|\254\002\377g\243\002\377h\241\002\377e\236\001\377\200\270\016\377w\257\004\377o\247" + "\002\377q\250\002\377r\246\001\377|\253\002\377\211\267\016\377\177\255\013\377p\242" + "\003\377k\242\001\377i\243\002\377k\251\005\377j\250\005\377k\243\003\377u\254\001\377" + "r\246\001\377\204\256\022\377\260\277]\377q\250\"\377\202\271/\377n\246\006\377" + "o\245\001\377o\245\001\377u\254\012\377z\261\025\377r\254\015\377l\251\006\377k\247" + "\002\377u\262\011\377o\255\006\377i\244\001\377l\245\001\377l\245\002\377g\240\001\377" + "c\227\001\377^\217\002\377V\215\000\377\255\271x\377\340\270\262\377\337\275\266" + "\377\360\334\334\377\360\340\342\377\361\342\341\377\340\313\315\377\355" + "\340\341\377\370\360\360\377\272\230\234\377\311\255\261\377\367\355\356" + "\377\334\310\312\377\344\323\331\377\345\330\336\377\320\276\311\377\352" + "\334\345\377\330\307\315\377\261\233\243\377\317\276\276\377\375\373\373" + "\377\336\314\316\377\221wx\377\317\272\264\377\347\333\327\377\353\333\327" + "\377\353\326\314\377\336\303\261\377\344\304\254\377\351\314\262\377\357" + "\327\302\377\325\273\254\377\332\304\274\377\346\324\325\377\326\302\312" + "\377\353\335\350\377\350\334\345\377\275\254\270\377\276\255\273\377\274" + "\250\263\377\317\267\271\377\340\304\303\377\235\261_\377a\237\000\377f\233" + "\002\377u\251\002\377\204\265\002\377o\237\001\377l\236\001\377i\232\001\377k\241\002\377" + "o\244\001\377p\246\001\377p\246\001\377i\231\001\377l\242\006\377y\264\022\377p\254" + "\003\377q\255\001\377o\245\001\377q\253\001\377u\262\004\377m\246\002\377n\245\001\377" + "z\257\001\377w\246\002\377i\223\002\377|\255\001\377\206\272\003\377\200\272\023\377" + "h\234\000\377k\242\002\377w\262\002\377\201\271\001\377o\252\002\377q\260\014\377i\241" + "\002\377o\243\001\377s\247\001\377t\254\001\377p\243\001\377v\246\001\377\212\275\004\377" + "\206\272\003\377\177\264\001\377|\257\001\377w\245\002\377u\247\001\377b\235\002\377" + "e\237\002\377d\231\002\377\200\267\022\377z\260\011\377i\237\001\377k\244\002\377s" + "\253\003\377u\251\001\377~\261\005\377q\247\001\377g\235\001\377h\235\002\377e\231\001" + "\377j\246\004\377g\245\005\377f\245\010\377g\244\005\377c\231\001\377\241\263,\377" + "z\244\036\377e\241\016\377u\260\027\377w\254\010\377w\244\001\377\204\264\021\377" + "\213\272%\377\203\265\032\377i\242\005\377Z\224\001\377b\233\001\377r\253\001\377" + "s\247\001\377l\240\001\377h\243\001\377d\234\002\377f\241\001\377f\236\005\377\\\216" + "\006\377W\205\023\377\224\243p\377\352\310\312\377\312\245\250\377\356\330\330" + "\377\355\334\332\377\332\312\307\377\307\263\261\377\370\362\362\377\320" + "\272\266\377\247zr\377\365\341\335\377\344\315\313\377\335\311\311\377\343" + "\326\334\377\345\327\340\377\351\333\343\377\360\346\354\377\355\343\350" + "\377\336\317\320\377\374\361\357\377\374\367\365\377\334\307\277\377\207" + "g[\377\303\246\233\377\367\360\355\377\333\312\305\377\311\262\254\377\331" + "\301\267\377\342\310\267\377\316\261\236\377\341\305\261\377\363\346\327" + "\377\356\340\327\377\356\341\336\377\345\325\330\377\342\323\334\377\325" + "\306\324\377\267\245\265\377\274\251\271\377\277\255\272\377\337\306\304" + "\377\347\315\306\377p\236\026\377e\234\000\377j\241\001\377{\262\001\377\200\264" + "\001\377t\245\002\377o\242\002\377k\243\001\377q\256\002\377p\256\001\377m\247\001\377" + "p\251\001\377q\246\002\377\202\264\011\377m\241\004\377m\246\001\377m\252\002\377g\242" + "\002\377k\247\002\377k\250\002\377d\234\002\377t\253\003\377\177\264\002\377w\247\001\377" + "p\240\001\377z\257\002\377y\256\001\377\177\270\010\377r\244\002\377|\256\001\377\206" + "\273\002\377\203\263\000\377q\246\002\377u\261\011\377k\247\001\377p\251\001\377t\247" + "\001\377u\254\001\377z\253\001\377\205\271\001\377\205\272\002\377~\263\001\377|\260" + "\001\377}\260\002\377r\243\001\377s\247\002\377_\236\004\377c\232\001\377l\243\001\377" + "u\254\006\377~\265\024\377b\232\002\377e\237\001\377m\253\007\377r\255\005\377y\263" + "\011\377i\242\002\377e\235\003\377d\232\002\377i\235\001\377q\252\006\377k\246\004\377" + "b\236\002\377k\245\013\377h\234\002\377\213\247\015\377e\227\000\377c\232\002\377h" + "\241\003\377v\255\007\377u\252\002\377x\256\011\377k\240\002\377~\261\001\377t\247\001" + "\377]\226\001\377b\232\001\377m\247\001\377u\251\001\377v\253\001\377n\245\001\377c\235" + "\002\377g\244\024\377i\234*\377b\220\063\377r\212b\377\200\203~\377\340\303\304" + "\377\347\325\324\377\371\363\363\377\366\356\357\377\352\331\336\377\365" + "\353\354\377\376\373\372\377\344\323\313\377\252\200i\377\354\322\310\377" + "\340\310\305\377\341\315\317\377\353\336\345\377\341\322\334\377\350\333" + "\343\377\346\330\340\377\332\311\322\377\323\277\301\377\332\302\277\377" + "\361\335\321\377\341\307\265\377\204V;\377\330\275\252\377\375\375\375\377" + "\354\340\334\377\270\234\233\377\302\246\243\377\317\270\261\377\351\323" + "\304\377\340\305\257\377\347\314\272\377\365\352\341\377\353\340\332\377" + "\373\364\363\377\360\350\353\377\303\255\274\377\263\235\255\377\267\242" + "\261\377\315\262\270\377\336\275\257\377\316\277\230\377h\237\005\377i\237" + "\001\377u\261\006\377s\252\002\377|\263\001\377x\253\002\377v\262\002\377q\254\002\377" + "u\256\004\377|\261\003\377v\252\001\377r\252\001\377r\253\002\377n\242\001\377i\232\002" + "\377n\245\002\377p\252\001\377l\247\002\377k\250\001\377l\251\001\377l\245\002\377u\254" + "\003\377\200\265\004\377s\242\001\377r\241\001\377v\256\001\377u\255\004\377\216\304" + "&\377x\255\016\377\224\303\034\377\204\265\000\377\177\260\001\377{\255\001\377y" + "\257\002\377n\247\002\377s\253\002\377x\254\002\377\177\263\001\377\207\273\001\377\207" + "\274\002\377\203\271\001\377}\260\001\377\203\272\003\377{\256\001\377o\242\001\377t" + "\253\001\377r\254!\377f\232\000\377q\246\001\377q\247\001\377\202\271\031\377m\252" + "\007\377c\241\003\377`\233\002\377i\247\003\377v\262\014\377n\246\002\377e\235\002\377" + "l\244\010\377o\247\007\377r\252\005\377i\241\002\377\\\225\001\377p\247\027\377p\241" + "\014\377u\243\011\377j\230\001\377k\241\002\377r\252\012\377o\251\007\377j\236\001\377" + "n\240\002\377x\250\001\377\200\255\002\377\203\262\001\377Y\224\001\377Y\225\001\377" + "d\241\001\377j\237\001\377o\241\001\377q\246\001\377i\242\002\377h\242\030\377r\222" + "g\377{\214\200\377\177\201\210\377\254\235\246\377\346\321\315\377\374\367" + "\365\377\370\361\361\377\277\250\250\377v[\\\377}db\377\272\241\225\377\363" + "\325\307\377\345\301\253\377\364\332\313\377\343\306\275\377\344\313\311" + "\377\336\311\316\377\334\313\323\377\344\322\332\377\316\271\301\377\326" + "\302\305\377\315\266\264\377\323\273\260\377\353\320\301\377\316\255\226" + "\377\216dD\377\372\357\346\377\365\354\347\377\344\330\326\377\331\307\307" + "\377\352\332\327\377\320\267\261\377\340\311\274\377\331\277\250\377\335" + "\301\255\377\361\341\323\377\346\322\311\377\361\341\337\377\346\324\327" + "\377\317\273\306\377\263\233\250\377\274\240\246\377\340\276\263\377\352" + "\306\275\377\231\261I\377m\247\002\377i\237\001\377n\250\001\377q\243\001\377\177" + "\265\001\377z\256\002\377r\253\001\377o\237\001\377t\243\002\377\201\257\004\377\205" + "\270\012\377y\261\010\377p\252\007\377c\234\002\377d\234\002\377s\255\001\377v\252" + "\002\377z\261\001\377s\253\001\377p\250\002\377r\251\002\377}\263\007\377~\263\002\377" + "|\255\003\377z\247\001\377\201\262\001\377z\257\002\377\200\271\037\377\210\276\063" + "\377\230\310>\377z\256\000\377{\254\001\377r\240\002\377r\246\001\377r\255\001\377" + "u\261\002\377v\256\003\377\205\271\001\377\213\276\001\377~\254\001\377\201\263\002\377" + "\202\265\002\377\207\275\006\377{\256\001\377s\252\001\377n\247\001\377|\265\061\377" + "m\250\013\377f\234\001\377o\250\006\377\204\272\040\377\177\266\033\377r\253\017" + "\377l\245\012\377q\251\014\377z\261\016\377r\255\010\377d\237\001\377j\245\010\377" + "{\260\024\377|\257\022\377s\246\020\377U\221\005\377h\235\020\377x\247\023\377r" + "\243\014\377f\234\002\377o\247\001\377p\251\001\377l\241\001\377q\245\001\377t\242\002" + "\377\200\262\003\377s\241\002\377x\252\003\377k\240\006\377c\230\001\377d\232\002\377" + "j\244\001\377j\241\002\377g\236\002\377j\244\010\377s\240\060\377|\204\205\377||" + "\207\377\212\177\220\377\326\307\313\377\370\353\351\377\372\361\354\377" + "\310\244\224\377O/$\377#\011\003\377#\014\005\377\024\000\000\377D%\035\377\330\261\233" + "\377\377\366\347\377\342\314\300\377\333\277\272\377\333\305\305\377\330" + "\276\302\377\276\247\255\377\276\250\251\377\275\243\240\377\337\307\274" + "\377\360\334\316\377\355\323\277\377\325\260\221\377\343\303\251\377\306" + "\257\237\377\251\221\210\377\266\241\235\377\274\250\251\377\343\326\327" + "\377\350\330\327\377\337\312\303\377\334\301\264\377\323\272\246\377\324" + "\271\247\377\372\362\354\377\357\337\340\377\315\266\303\377\266\233\254" + "\377\257\220\234\377\323\253\236\377\341\270\250\377\333\306\255\377f\235" + "\000\377m\247\002\377l\244\001\377q\254\001\377v\256\001\377x\262\002\377o\247\001\377" + "j\243\001\377o\242\001\377q\237\001\377u\243\001\377v\254\002\377\200\271\033\377~\267" + "-\377f\237\004\377e\233\001\377s\252\002\377|\257\001\377~\260\001\377|\256\001\377y" + "\254\001\377}\257\001\377\203\270\005\377}\260\001\377}\256\002\377\202\263\001\377\177" + "\256\002\377t\247\006\377\207\300/\377\205\276\066\377\210\302\062\377r\252\000\377" + "s\254\001\377s\255\003\377m\250\001\377p\255\004\377n\247\002\377\203\266\001\377\211" + "\275\002\377\205\267\001\377|\252\002\377\201\264\001\377\204\267\006\377|\257\001\377" + "{\260\002\377v\262\002\377q\251\001\377~\267!\377u\260\015\377`\225\001\377c\234\003" + "\377\201\266\031\377\214\276'\377\200\265\034\377\177\264\036\377\200\265\035" + "\377\200\266\030\377t\256\011\377c\235\002\377_\230\001\377d\241\002\377w\254\034" + "\377w\251)\377V\217\016\377p\241\030\377\205\255\034\377\217\265,\377o\245\020" + "\377t\256\012\377q\254\002\377m\244\002\377w\253\006\377~\257\004\377x\255\003\377m" + "\246\010\377m\245\015\377\202\263\"\377\203\262$\377d\226\002\377g\240\003\377" + "X\215\001\377f\235\024\377d\226'\377\177\217w\377\200|\207\377\210}\216\377" + "\241\225\237\377\362\342\333\377\350\321\313\377\355\323\306\377\236n[\377" + "\071\025\020\377bA+\377\320\307\317\377\244\241\267\377bLB\377M(\032\377\376" + "\354\330\377\355\317\271\377\373\352\337\377\326\254\225\377\347\315\277" + "\377\323\270\260\377\340\305\272\377\325\270\254\377\372\365\357\377\375" + "\372\364\377\365\336\307\377\300\241\207\377;&\035\377\034\006\014\377\"\012\017" + "\377\040\010\010\377<&#\377\234\204}\377\346\326\324\377\350\332\326\377\310" + "\256\241\377\352\322\302\377\334\303\256\377\363\344\327\377\371\363\360" + "\377\340\317\325\377\267\234\254\377\323\262\264\377\345\277\264\377\354" + "\317\304\377pdJ\377s\255\000\377q\246\001\377w\255\003\377z\266\005\377s\256\001\377" + "u\261\005\377i\243\002\377l\241\002\377x\253\001\377{\251\002\377~\253\002\377z\253\001" + "\377n\246\002\377n\252\034\377\207\275\071\377j\240\002\377{\257\001\377\207\267" + "\002\377\177\255\001\377z\255\002\377|\261\002\377~\260\001\377\212\276\010\377|\257" + "\002\377{\254\001\377\203\270\004\377y\257\007\377t\256\016\377\217\306?\377\207\300" + ";\377x\265\036\377r\261\001\377p\254\001\377r\260\007\377m\254\007\377i\243\001\377" + "~\260\001\377\230\303\002\377\177\256\001\377u\246\001\377t\252\004\377\212\301\036" + "\377\211\300\034\377y\260\003\377{\257\001\377z\261\001\377s\250\001\377x\257\013\377" + "|\262\005\377e\236\002\377c\235\003\377w\255\011\377\206\270\030\377}\262\017\377" + "s\253\011\377g\237\002\377z\260\022\377~\264\025\377e\240\006\377^\234\003\377b\237" + "\004\377h\240\011\377e\236\021\377R\217\006\377a\230\016\377\221\264\063\377\204" + "\252*\377{\253\"\377v\255\031\377c\240\006\377m\251\022\377\203\266\"\377\210" + "\267\026\377w\250\002\377m\242\002\377r\251\013\377\203\266)\377\206\265\063\377" + "Y\211\001\377h\236\025\377h\232\034\377w\243<\377x\222\\\377\201{\206\377\205" + "w\210\377\226\203\223\377\301\260\265\377\267\237\245\377\372\362\357\377" + "\371\361\355\377\241\200}\377nP:\377\273\253|\377\307\305\260\377\247\251" + "\276\377\322\322\324\377\204dN\377\374\342\313\377\366\327\271\377\366\323" + "\260\377\366\320\252\377\370\334\276\377\366\335\306\377\357\317\275\377" + "\367\342\321\377\375\377\375\377\372\352\330\377\225t\\\377\065\034\015\377" + "B/'\377\201u{\377ymy\377^H@\377/\023\014\377\067\033\025\377\251\210u\377\333" + "\311\300\377\350\327\314\377\315\260\242\377\332\301\263\377\354\336\321" + "\377\340\317\310\377\351\331\331\377\322\261\262\377\353\303\261\377\373" + "\343\326\377\254zc\377\201tC\377y\261\011\377w\250\001\377{\257\003\377z\263\004" + "\377v\260\002\377t\257\001\377q\253\001\377o\246\002\377w\253\001\377\177\260\000\377" + "\177\255\001\377w\247\001\377n\245\001\377b\233\001\377\202\271)\377w\256\007\377~" + "\261\001\377\204\265\001\377\177\260\001\377{\254\001\377\200\262\001\377\200\262\001" + "\377\211\300\010\377u\250\002\377t\245\001\377\202\271\001\377}\267\015\377\204\276" + "\071\377\244\325`\377\217\307C\377x\265\024\377t\262\003\377y\265\006\377|\266" + "\015\377p\247\002\377s\251\001\377\206\270\002\377\204\264\002\377x\253\000\377{\262" + "\023\377\222\306=\377~\272\025\377q\252\001\377u\246\001\377~\257\001\377}\263\001" + "\377v\253\001\377p\247\007\377\202\265\007\377g\242\012\377c\240\007\377r\253\007\377" + "y\260\012\377\202\265\022\377p\247\006\377d\235\003\377v\255\025\377\200\266\036" + "\377t\254\017\377q\251\016\377b\231\001\377c\231\002\377^\233\007\377J\214\003\377" + "X\225\017\377\204\257\061\377e\226\024\377l\237\035\377v\252(\377`\233\016\377" + "t\257'\377|\262\"\377}\260\025\377r\243\002\377m\237\002\377r\247\005\377\203\270" + "\040\377w\255\031\377Y\222\005\377k\240#\377l\235/\377v\225^\377\200\200\211" + "\377~v\203\377\213|\215\377\234\210\225\377\271\241\246\377\321\272\270\377" + "\344\316\314\377\363\354\354\377\336\322\321\377\231\210\201\377\334\330" + "\273\377\346\340\303\377\355\347\314\377\333\321\303\377\265\225\215\377" + "\362\320\274\377\366\331\276\377\364\321\262\377\363\310\250\377\362\313" + "\250\377\362\316\261\377\365\326\303\377\341\271\251\377\357\320\265\377" + "tTJ\377)\031\034\377kZ\070\377\275\265\237\377\315\316\341\377\265\266\322\377" + "\325\327\333\377\213mK\377\067\031\024\377tL?\377\313\252\231\377\307\251\224" + "\377\262\217{\377\320\263\242\377\343\315\301\377\346\330\322\377\332\302" + "\303\377\306\225\203\377\330\246\211\377\371\343\323\377\303\242\223\377" + "\225\224y\377x\246-\377u\246\012\377\177\263\013\377{\257\004\377z\255\002\377" + "z\263\002\377x\263\003\377p\251\001\377s\253\001\377z\254\001\377}\255\001\377x\247\001" + "\377q\242\001\377u\247\001\377{\260\003\377~\263\003\377\177\261\001\377\203\264\001" + "\377\202\263\001\377~\250\002\377\203\263\002\377\201\264\001\377\205\273\004\377u" + "\250\001\377z\253\002\377\202\265\002\377\202\270\007\377\214\304K\377\227\314W\377" + "{\272$\377r\261\006\377r\255\001\377u\260\003\377t\254\002\377\177\257\001\377\212" + "\275\002\377\177\262\001\377v\256\001\377w\262\012\377\224\310D\377\213\300\064\377" + "u\260\000\377u\254\001\377z\256\001\377~\255\001\377\201\266\001\377v\250\002\377v\244" + "\002\377\207\267\022\377o\250!\377W\225\003\377h\245\016\377l\247\014\377|\263\037" + "\377m\247\024\377l\247\033\377j\246\026\377\200\266%\377n\243\006\377\203\264" + "\037\377l\241\013\377b\233\007\377^\233\023\377P\220\004\377Z\231\005\377n\243\017" + "\377y\253\036\377n\237\025\377x\253,\377d\234\034\377\205\274;\377{\262\"\377" + "x\256\032\377`\226\003\377[\213\001\377V\210\001\377y\254\060\377\177\264!\377\177" + "\263(\377{\253=\377}\247V\377\177\213\177\377\203~\213\377\207{\214\377\214" + "|\215\377\305\257\260\377\256\221\221\377\301\243\231\377\275\235\220\377" + "\303\247\236\377\325\300\273\377\353\334\332\377\370\364\360\377\374\373" + "\365\377\364\346\330\377\326\254\235\377\310\241\242\377\332\262\261\377" + "\341\273\274\377\333\265\267\377\314\247\254\377\266\216\217\377\323\240" + "\177\377\341\301\262\377\277\232\225\377\220b_\377oCF\377\213cP\377\264\241" + "\202\377\333\324\262\377\330\327\312\377\341\342\331\377\343\345\333\377" + "\276\270\235\377\202hW\377\347\332\321\377\364\351\340\377\312\262\250\377" + "\245\215\203\377\317\265\247\377\324\274\261\377\344\322\313\377\347\324" + "\320\377\305\236\216\377\331\271\247\377\345\311\267\377\264\201l\377\223" + "\220\221\377\207\237|\377v\242\067\377t\251\017\377r\250\002\377p\246\001\377r" + "\257\002\377m\251\004\377q\256\002\377x\256\001\377y\245\002\377}\251\001\377|\252\001" + "\377{\247\002\377{\252\002\377{\253\001\377\177\266\003\377\207\301\015\377\207\273" + "\005\377\203\262\000\377\201\254\001\377\200\257\002\377~\261\001\377\177\270\003\377" + "t\247\001\377{\254\001\377\201\265\002\377\227\311/\377\203\273\063\377\210\301" + "E\377v\265\037\377q\255\003\377s\255\001\377u\261\002\377}\265\001\377\214\276\001\377" + "\203\264\001\377w\254\001\377y\267\016\377\233\320R\377\203\273)\377s\257\005\377" + "r\256\001\377w\261\001\377v\245\001\377~\256\001\377~\257\001\377{\252\002\377\221\263" + "\001\377\200\256\012\377\203\267D\377V\225\016\377Y\230\014\377_\236\016\377s\256" + "&\377y\256\032\377\206\270\040\377q\246\006\377}\263\033\377r\250\022\377~\262" + ".\377\210\272G\377\214\275U\377\204\270N\377Z\226\007\377Z\223\001\377a\231\001" + "\377t\247\030\377y\247\036\377y\252'\377m\242!\377\177\266.\377i\240\011\377" + "v\254#\377`\227\020\377U\206\001\377Q\177\004\377\210\264X\377r\245\023\377\205" + "\266$\377~\254\071\377{\226n\377{v\204\377\214\177\216\377\214{\213\377\270" + "\242\251\377\260\224\225\377\260\217\216\377\303\241\230\377\324\265\253" + "\377\323\265\260\377\326\272\266\377\340\310\304\377\354\326\320\377\360" + "\340\330\377\357\334\314\377\366\335\302\377\302|]\377\304\177a\377\362\253" + "\227\377\365\263\241\377\314\222\202\377\231f`\377\227fW\377\310\240\211" + "\377\261\220\206\377\265\224\215\377\246gY\377\376\354\315\377\373\367\346" + "\377\356\352\330\377\357\350\323\377\360\350\325\377\347\340\321\377\324" + "\316\307\377\366\361\360\377\351\335\335\377\343\321\317\377\353\332\327" + "\377\240\214\214\377t]_\377\326\301\274\377\343\323\315\377\330\305\277\377" + "\257\214\201\377\265\224\202\377\241xa\377\216jW\377\201y\201\377\235\240" + "\247\377\224\251\207\377{\253\063\377n\244\015\377r\253\010\377q\255\007\377f" + "\236\005\377l\242\001\377{\256\002\377y\245\001\377\201\255\001\377~\250\001\377|\251" + "\001\377z\250\001\377|\254\001\377\203\274\013\377\207\300\032\377\200\270\013\377" + "\203\272\013\377\200\262\002\377x\250\001\377x\255\001\377q\245\001\377p\240\001\377" + "\202\264\017\377\233\312\062\377z\257\005\377{\263\026\377g\245\034\377\177\272" + "\067\377s\260\016\377u\260\002\377y\263\002\377\200\266\002\377\206\270\002\377|\254" + "\002\377t\251\002\377w\263\013\377\211\277(\377\217\302)\377|\263\012\377x\262" + "\001\377u\254\002\377u\252\002\377z\257\002\377}\262\001\377\177\261\002\377\234\277" + "\002\377\177\261\027\377\210\274K\377a\234'\377m\244\061\377~\264\065\377\210" + "\272\013\377\222\276\003\377\210\263\001\377\177\256\017\377\203\264\033\377u\251" + "\011\377p\246\011\377x\257\022\377\202\266\034\377v\253\012\377h\236\001\377g\236" + "\002\377h\243\002\377r\251\013\377v\254\017\377s\252\021\377~\265)\377{\263'\377" + "^\227\004\377n\243\035\377\177\264\070\377[\213\003\377Nx\007\377t\246\070\377n\240" + ")\377\177\253\064\377\203\245R\377\215\224\221\377\202v\202\377\217\200\217" + "\377\251\227\237\377\317\273\274\377\246\221\226\377\302\252\251\377\340" + "\306\277\377\355\327\317\377\324\271\261\377\306\250\244\377\340\313\305" + "\377\354\331\316\377\361\341\324\377\375\374\366\377\372\365\357\377\215" + "fX\377X%\033\377\310eE\377\260aF\377\033\005\001\377@\033\013\377B\034\027\377\316" + "\237~\377\361\323\265\377\366\347\324\377\326\265\253\377\354\332\316\377" + "\366\351\343\377\361\344\336\377\356\337\330\377\341\320\311\377\326\301" + "\271\377\311\260\251\377\243\204\200\377\211lg\377\252\217\210\377\301\252" + "\236\377\301\255\242\377ubd\377\262\234\226\377\336\313\304\377\343\317\311" + "\377\242\206\204\377mUK\377Q\065$\377`J=\377\203v\203\377\237\230\246\377" + "\246\245\255\377\237\254\227\377\206\250R\377r\242\021\377~\260\021\377n\240" + "\003\377q\241\001\377z\257\001\377x\246\002\377~\257\001\377t\242\001\377s\246\001\377" + "|\261\002\377\204\267\004\377\205\270\004\377\216\300\027\377{\257\006\377\201\267" + "\017\377~\262\001\377}\257\001\377{\255\002\377s\243\000\377~\264\023\377\242\317a" + "\377\212\270\036\377|\256\000\377m\243\002\377\\\226\000\377w\264\040\377\200\273" + "%\377}\265\006\377\203\271\001\377\203\265\001\377|\255\001\377w\250\002\377u\253\001" + "\377s\255\001\377{\266\005\377~\264\005\377}\260\001\377\177\263\001\377v\247\002\377" + "s\247\002\377z\253\001\377\202\261\000\377}\254\002\377\231\276\001\377\200\267\062" + "\377q\251#\377\205\272/\377\225\305!\377\210\272\011\377x\255\002\377r\246\001" + "\377}\257\013\377\207\270%\377\204\265\030\377\201\263\013\377\215\274\032\377" + "\233\307\065\377\227\304/\377\217\300\"\377\202\264\014\377r\251\000\377q\255" + "\014\377{\265(\377k\245\025\377e\235\021\377\206\272<\377\213\276G\377i\241" + "\035\377l\240\033\377\225\303V\377o\240\037\377i\223%\377z\243K\377\201\247" + "Y\377\201\244a\377\212\230\210\377\212\204\217\377\237\222\236\377\212y\204" + "\377\266\243\245\377\313\267\264\377\253\216\221\377\272\240\233\377\266" + "\234\224\377\265\235\225\377\271\241\235\377\306\255\250\377\364\346\333" + "\377\336\276\261\377\361\330\305\377\371\365\361\377\375\375\376\377\377" + "\377\377\377\240ru\377\230J\067\377\232VD\377uZc\377\320\300\276\377\375\365" + "\353\377\377\377\370\377\372\363\340\377\370\347\322\377\360\334\313\377" + "\351\323\312\377\351\324\317\377\300\244\245\377\271\235\235\377\317\265" + "\261\377\320\265\257\377\301\245\240\377\306\253\242\377\260\222\206\377" + "\236\202u\377\233\201u\377\245\214\177\377\226~y\377yab\377\307\262\256\377" + "\310\262\257\377\315\270\263\377\231\203}\377_HB\377eOE\377\224\203\210\377" + "\214\177\215\377\232\223\242\377\237\237\250\377\223\236\225\377\206\247" + "T\377\205\265%\377z\253\002\377{\251\002\377\177\261\001\377~\261\001\377{\261\002" + "\377q\245\001\377s\253\003\377|\264\003\377\204\266\003\377\204\262\002\377\205\265" + "\005\377\177\262\005\377|\262\012\377~\270\017\377{\256\002\377z\252\002\377w\256\000" + "\377\177\267!\377\220\302<\377z\246\000\377|\256\001\377e\226\001\377j\237\001\377" + "|\267\012\377\201\271\024\377\204\274\013\377\204\266\001\377}\261\001\377t\252" + "\001\377q\246\001\377v\257\001\377x\261\001\377x\255\001\377|\254\001\377\204\270\001\377" + "\201\262\001\377y\250\001\377r\242\001\377\216\275\030\377\217\275\015\377t\246\001" + "\377\221\274\001\377t\255\035\377\214\300\060\377\202\266\032\377o\250)\377\\" + "\227\033\377Y\225\024\377k\243\022\377r\251\011\377y\253\002\377\202\263\002\377" + "\216\275\021\377\210\272&\377\207\266\067\377\204\263\066\377\210\267:\377~" + "\257\037\377|\256\000\377~\261$\377t\246)\377w\246\063\377u\243/\377\202\256" + "\062\377\230\301T\377\210\264>\377\205\266=\377\213\274H\377s\250\064\377h" + "\232,\377w\231\\\377\203\232z\377\213\226\213\377\217\212\225\377\227\212" + "\232\377\232\214\230\377ucq\377\276\254\251\377\271\243\244\377\264\236\240" + "\377\321\271\254\377\257\227\222\377\256\225\222\377\244\214\211\377\370" + "\360\355\377\374\367\356\377\367\344\321\377\342\303\261\377\371\364\363" + "\377\375\375\375\377\376\376\376\377\304\266\300\377T/<\377\202cr\377\363" + "\360\363\377\376\376\376\377\375\375\375\377\376\375\373\377\336\303\257" + "\377\323\250\217\377\301\235\222\377\331\275\264\377\371\356\350\377\337" + "\313\311\377\267\240\242\377\302\247\245\377\274\242\237\377\322\274\272" + "\377\310\262\260\377\264\232\224\377\241\204}\377\273\247\227\377\212qi\377" + "\220uq\377nXX\377\262\236\226\377\260\233\226\377\325\300\275\377\331\305" + "\303\377\241\211\212\377\202jg\377\217{z\377\217\200\213\377\220\205\226" + "\377\220\212\232\377\222\222\234\377\221\235\225\377\207\256T\377z\252\017" + "\377v\244\001\377\177\260\003\377{\253\001\377y\256\001\377t\245\001\377w\254\001\377" + "|\254\002\377\203\263\001\377\201\260\001\377~\255\001\377\210\271\012\377\222\303" + "-\377}\264\032\377z\261\016\377~\256\001\377{\257\001\377\202\273\017\377}\257\011" + "\377|\253\001\377z\251\002\377Z\222\001\377q\251\001\377\200\267\002\377\200\265\001" + "\377~\261\001\377y\254\002\377v\257\001\377r\251\001\377q\243\002\377|\254\002\377\201" + "\262\001\377~\253\002\377\202\263\001\377\202\264\001\377\177\264\001\377z\260\001\377" + "t\251\001\377\200\271\024\377z\262\007\377t\247\002\377\215\273\007\377|\267)\377" + "b\236\022\377j\242,\377u\253@\377a\231*\377q\246\071\377\205\266+\377v\250" + "\017\377f\227\001\377\201\257\001\377\214\275\003\377{\260\015\377_\230\014\377m\244" + "!\377k\241\032\377Y\217\004\377\211\267\000\377\210\270\070\377\203\262?\377\234" + "\310\\\377\221\275@\377\201\260,\377\213\270?\377\204\263B\377q\244\064\377" + "p\244\060\377l\237*\377p\235?\377p\206m\377\205\214\216\377\222\217\235\377" + "\221\206\227\377\233\217\236\377\207{\207\377\224\206\215\377\312\270\263" + "\377\264\243\240\377\314\263\250\377\320\261\235\377zYN\377yc^\377\352\342" + "\341\377\344\316\303\377\337\300\261\377\345\310\274\377\367\351\344\377" + "\375\375\374\377\373\367\370\377\374\371\373\377\344\334\347\377\200g|\377" + "\314\276\312\377\360\353\355\377\374\371\372\377\373\371\372\377\363\346" + "\342\377\324\261\233\377\354\314\261\377\312\245\221\377\325\271\247\377" + "\321\261\237\377\353\326\316\377\325\306\307\377\232\205\204\377\264\237" + "\234\377\251\224\222\377\250\221\213\377\265\234\224\377\266\233\216\377" + "\246\213~\377\234\202y\377\225~y\377kWV\377\231\207\200\377\256\233\227\377" + "\321\277\274\377\316\274\272\377\262\237\240\377\243\216\220\377\247\224" + "\226\377\241\217\230\377\212}\215\377\201x\211\377\210\203\223\377\213\213" + "\230\377\225\244\233\377x\243J\377n\245\036\377\204\271/\377t\251\002\377x\247" + "\002\377w\246\002\377\177\262\001\377\201\260\001\377\201\263\001\377{\257\002\377u\253" + "\002\377\213\276\037\377\222\301*\377n\245\001\377t\260\010\377\200\266\001\377\201" + "\256\001\377\205\265\002\377\177\253\001\377\200\253\001\377x\246\000\377\202\256\026" + "\377\200\260\006\377\204\271\001\377\201\265\001\377}\261\003\377v\255\006\377p\252" + "\001\377w\254\001\377\207\270\002\377~\254\002\377\201\261\001\377~\254\002\377\201\262" + "\001\377\201\265\002\377y\252\001\377{\261\002\377w\260\002\377u\261\000\377u\255\001\377" + "s\246\002\377\206\276\062\377p\256\"\377p\254)\377l\246'\377o\247-\377s\252" + ".\377c\233\016\377g\236\006\377Z\220\003\377]\222\001\377u\250\001\377\207\272\007\377" + "z\260\007\377n\244\023\377f\231\022\377]\221\004\377W\215\007\377\211\264\002\377q" + "\246\030\377\225\304_\377\214\275G\377y\252\027\377y\251\027\377|\253%\377\223" + "\277G\377]\222\032\377o\241+\377r\241+\377\203\243a\377\243\244\252\377\260" + "\253\270\377\251\237\257\377\241\230\250\377\220\207\226\377wiz\377\215y" + "\206\377\270\244\242\377\272\241\237\377\341\307\271\377\277\232\204\377" + "\257\214x\377\344\321\311\377\375\371\366\377\372\370\365\377\367\350\341" + "\377\352\315\307\377\364\334\333\377\367\356\360\377\374\366\370\377\371" + "\364\370\377\275\256\274\377\204v\204\377\265\243\261\377\321\273\302\377" + "\335\307\311\377\362\344\346\377\372\363\363\377\371\356\352\377\261\207" + "r\377\266\210k\377\326\262\225\377\376\366\354\377\373\370\365\377\373\364" + "\363\377\266\241\237\377\231\205\200\377\307\265\251\377\276\252\227\377" + "\275\250\225\377\314\265\250\377\264\234\217\377\276\252\240\377\247\223" + "\216\377}eh\377\212tq\377\247\217\213\377\266\241\235\377\307\265\260\377" + "\305\262\256\377\235\205\207\377\245\216\222\377\263\244\253\377\232\214" + "\232\377\200u\206\377\200x\213\377\212\205\226\377\212\213\227\377\206\226" + "\211\377~\251V\377{\257\062\377x\253\000\377\200\260\001\377v\245\002\377}\255\001" + "\377\200\263\001\377}\264\001\377w\257\002\377r\254\007\377\222\306F\377\200\264" + "\032\377q\247\001\377q\251\003\377w\262\003\377\200\260\001\377\203\256\001\377\177" + "\250\001\377~\251\002\377x\246\001\377\207\267\010\377\207\267\004\377\205\267\002\377" + "}\257\001\377\204\266\013\377\227\302&\377u\250\000\377|\255\001\377\215\300\001\377" + "\204\262\001\377\207\264\001\377\205\260\001\377\210\267\001\377\203\262\001\377x\246" + "\002\377\177\257\001\377w\246\002\377z\257\001\377~\263\001\377s\243\001\377`\241\021" + "\377N\214\000\377c\240\011\377y\262\037\377d\240\005\377b\237\006\377p\253\017\377" + "l\243\002\377g\225\003\377w\246\014\377q\241\000\377\177\263\004\377u\255\005\377~\263" + "\"\377\\\204\000\377e\216\007\377d\217\012\377}\253\003\377\216\303\064\377\177\262" + "@\377g\236\026\377[\216\005\377h\231\006\377~\254!\377\220\274A\377u\241)\377" + "\206\255S\377\232\260\204\377\243\250\243\377\234\230\244\377\210\200\225" + "\377\222\211\233\377\217\204\226\377\224\204\225\377\215\200\216\377\242" + "\224\231\377\240\217\223\377\320\274\271\377\336\311\302\377\262\226\217" + "\377\267\233\231\377\363\357\360\377\373\370\365\377\351\324\312\377\351" + "\317\311\377\366\346\346\377\365\353\355\377\360\352\356\377\361\347\356" + "\377\322\306\322\377}i|\377P\070H\377\216\200\215\377\313\275\306\377\340" + "\322\330\377\364\347\353\377\345\316\313\377\347\320\312\377\327\265\250" + "\377\336\304\265\377\320\262\242\377\311\252\233\377\332\306\275\377\352" + "\342\342\377\331\314\316\377\216\200|\377`TR\377ygb\377iXT\377\231\200t\377" + "\314\270\255\377\301\254\247\377\262\237\236\377\234\212\211\377\202on\377" + "\266\243\237\377\234\206\205\377\267\242\240\377\302\256\256\377\253\226" + "\233\377\237\211\221\377\315\300\306\377\322\306\313\377\241\225\242\377" + "\210~\220\377\214\205\227\377\214\207\226\377\211\211\224\377\210\233\204" + "\377x\250\066\377l\232\000\377\203\257\002\377\205\265\001\377\202\265\002\377\203" + "\267\002\377|\257\002\377x\255\001\377w\262\007\377w\261\022\377n\237\001\377q\245\001" + "\377r\251\001\377t\256\002\377\202\266\004\377\204\262\002\377\205\257\002\377~\251" + "\001\377{\247\002\377\204\265\001\377\206\265\002\377\204\266\001\377~\257\001\377\205" + "\261\002\377\222\274\002\377\206\264\001\377y\251\002\377\177\261\001\377\205\271\002" + "\377\201\256\001\377\205\261\001\377\212\272\002\377\206\265\001\377{\252\002\377}" + "\253\002\377y\250\001\377}\260\001\377{\255\001\377r\241\002\377d\236\002\377[\223\002" + "\377l\250\025\377|\264\031\377t\255\011\377^\225\001\377l\246\004\377w\252\003\377" + "b\215\001\377^\215\002\377q\242\001\377\205\266\005\377v\253\007\377\216\305+\377p" + "\236\003\377f\214\001\377Ry\001\377t\254\020\377z\264$\377Q\205\010\377~\260\040\377" + "T\206\003\377_\217\005\377\177\253\040\377\260\317t\377q\230B\377k\222E\377\213" + "\230\206\377\252\250\262\377\254\245\263\377\232\216\242\377rdx\377\201m" + "}\377\205q|\377\220|\201\377\221}|\377\202ge\377\334\300\254\377\340\274" + "\242\377\255~d\377\353\333\324\377\372\367\367\377\344\325\315\377\356\337" + "\325\377\351\324\312\377\275\245\242\377\307\265\267\377\342\331\337\377" + "\307\301\310\377zq~\377\070\036+\377N\067D\377UBN\377\232\217\233\377\325\312" + "\323\377\312\273\302\377\331\310\306\377\343\320\315\377\343\313\304\377" + "\277\236\220\377\310\237\203\377\370\355\345\377\357\341\331\377\332\312" + "\306\377\327\320\315\377\274\253\246\377\273\246\235\377\271\242\232\377" + "\225}w\377mOB\377\271\233\210\377\335\312\301\377\251\216\207\377\250\220" + "\212\377nVS\377\253\227\216\377\302\257\251\377\247\220\217\377\261\234\235" + "\377\254\232\237\377\240\213\227\377\260\237\252\377\314\277\305\377\323" + "\306\314\377\235\221\241\377\214\203\226\377\205~\220\377\202\177\215\377" + "\211\217\224\377v\233U\377i\234\010\377\200\261\001\377\203\262\002\377\202\262" + "\001\377\201\262\001\377}\253\001\377}\257\002\377~\262\001\377v\247\002\377x\246\001\377" + "x\247\002\377s\241\002\377x\260\003\377y\254\001\377\177\256\001\377\202\263\001\377" + "\202\256\002\377\200\254\002\377\206\264\001\377\207\264\002\377\211\271\002\377\204" + "\255\001\377\223\267\001\377\245\307\001\377\212\263\002\377{\256\001\377s\245\002\377" + "~\260\001\377}\254\002\377\203\262\002\377\207\267\001\377\211\271\001\377\177\256" + "\001\377~\256\002\377\177\263\002\377}\257\001\377u\244\001\377z\252\002\377g\236\001\377" + "f\236\001\377s\256\026\377\210\276.\377z\262\020\377t\256\011\377i\235\001\377s" + "\246\002\377p\240\001\377o\235\001\377w\242\000\377\223\301\071\377w\254\040\377\214" + "\301/\377y\256\013\377m\240\002\377a\222\000\377z\262\025\377k\242\012\377h\226" + "\020\377x\250\024\377Gy\000\377d\214\022\377\212\246\064\377\262\316\213\377\204" + "\245l\377\207\224\211\377\220\216\234\377\207\177\222\377\205{\220\377\204" + "w\212\377l_r\377\211w\203\377\234\206\217\377\266\243\250\377\254\233\235" + "\377\277\256\255\377\367\347\340\377\326\271\243\377\314\262\244\377\336" + "\324\322\377\354\342\336\377\367\356\347\377\324\300\262\377\236\214\207" + "\377\261\247\242\377\271\262\264\377\267\260\266\377l^g\377XIP\377ven\377" + "cNZ\377eS\\\377aNY\377\210z\210\377\277\271\301\377\316\301\306\377\252\220" + "\215\377\273\240\231\377\337\306\277\377\314\270\255\377\320\267\243\377" + "\336\314\276\377\363\353\346\377\327\313\312\377\335\317\322\377\355\341" + "\342\377\364\355\355\377\341\323\315\377\234\205z\377\243\213\201\377\316" + "\272\262\377\305\256\242\377\270\241\225\377\207sm\377zd`\377\261\237\225" + "\377\254\231\225\377\262\240\235\377\262\243\247\377\227\205\222\377\221" + "~\216\377\304\266\276\377\326\311\316\377\315\300\310\377\241\226\246\377" + "\213\205\227\377\205\201\221\377\216\215\233\377z\216x\377\223\273S\377\205" + "\265\016\377\210\266\005\377\213\271\002\377\205\261\001\377~\257\001\377y\257\001\377" + "s\245\001\377z\254\001\377\200\257\002\377}\254\002\377}\253\010\377}\261\013\377y" + "\254\004\377\177\257\002\377}\253\002\377~\253\001\377v\246\002\377u\245\002\377\200" + "\260\001\377\205\265\002\377\203\253\001\377\225\275\001\377\223\267\001\377\200\253" + "\003\377\200\262\001\377{\261\002\377t\246\002\377x\251\002\377\203\264\001\377\212\267" + "\001\377\221\301\001\377\206\262\001\377\200\257\001\377\201\263\001\377z\251\002\377" + "w\246\001\377\204\264\001\377\226\275\002\377q\247\001\377m\246\002\377u\256\011\377" + "o\247\001\377y\255\001\377~\260\002\377~\256\002\377z\247\001\377}\253\001\377\220\271" + "#\377\227\302a\377Y\227\003\377\205\272\060\377}\260\026\377o\242\002\377s\253" + "\004\377v\254\003\377y\252\003\377z\247\010\377\210\263I\377}\243F\377\201\242\066" + "\377\232\264g\377\242\276\202\377z\215u\377|}\205\377\203|\214\377\204x\211" + "\377\213~\220\377~p\201\377|n{\377\265\245\252\377\327\313\317\377\354\344" + "\351\377\326\320\326\377\343\326\327\377\342\315\277\377\310\265\251\377" + "\341\324\317\377\344\326\323\377\350\335\334\377\352\343\340\377\262\243" + "\217\377\234\207l\377wjX\377\204\201v\377smk\377iZ^\377\212x\177\377\235" + "\212\226\377\221~\213\377\204nx\377\214x\200\377eV\\\377kci\377\244\237\242" + "\377\267\252\252\377\240\217\215\377\212tf\377\301\255\235\377\333\311\275" + "\377\331\316\307\377\333\312\277\377\365\357\355\377\351\336\334\377\335" + "\317\310\377\303\250\224\377\301\250\226\377\326\310\301\377\223\203\177" + "\377\270\250\242\377\333\311\303\377\264\237\221\377\220zv\377`LN\377\221" + "~z\377\236\215\206\377\226\203\204\377\247\227\233\377\234\213\230\377\232" + "\214\234\377\255\235\252\377\311\273\303\377\323\310\317\377\314\303\312" + "\377\243\233\252\377\205\177\220\377\215\212\230\377\202\207\213\377v\233" + "?\377\214\272&\377\210\264\001\377\213\257\001\377\215\264\001\377\201\260\001\377" + "t\250\001\377q\243\001\377u\244\002\377z\245\002\377z\250\001\377z\255\010\377\200\265" + "!\377\202\271'\377x\253\001\377\206\263\003\377\200\260\002\377w\247\002\377u\245" + "\001\377\201\260\002\377\207\265\002\377\206\260\001\377\214\267\001\377\214\273\004" + "\377\201\255\003\377}\262\001\377p\255\002\377k\242\002\377h\234\001\377}\257\002\377" + "\202\256\002\377\217\274\002\377\221\276\001\377\200\257\002\377{\256\002\377w\247" + "\001\377~\261\001\377\202\265\001\377\215\265\001\377\202\264\003\377x\253\006\377i\227" + "\001\377y\256\001\377w\251\001\377d\224\001\377g\226\002\377\200\255\002\377\205\262" + "\000\377\204\266\"\377f\232\020\377Z\231\001\377p\250\034\377\203\264%\377}\261" + "\035\377v\255\013\377r\251\003\377{\254\001\377\201\257\001\377\211\260i\377\216" + "\265q\377\234\276i\377|\246C\377n\225J\377\201\211\205\377\203\177\213\377" + "\203y\213\377\204w\212\377\204w\211\377\242\231\244\377\253\242\252\377\307" + "\275\306\377\342\325\336\377\313\277\306\377\277\265\276\377\323\310\314" + "\377\312\261\247\377\306\247\217\377\327\301\255\377\317\274\265\377\325" + "\307\277\377\312\266\242\377\272\242\202\377\234\202a\377oaM\377ph\\\377" + "tol\377\247\234\237\377\275\261\271\377\252\233\247\377\226\204\220\377\245" + "\225\240\377\246\230\241\377\242\230\234\377pfl\377ife\377\213\211\201\377" + "|rh\377\242\232\222\377\204t_\377\257\237\214\377\321\304\264\377\344\336" + "\331\377\365\363\364\377\343\335\330\377\317\275\257\377\250\205e\377\260" + "\217r\377\247\205j\377\230xb\377\223vi\377\332\314\311\377\316\301\277\377" + "\277\254\245\377^FD\377}ib\377\225\200|\377\222\177~\377\230\206\213\377" + "\227\210\224\377\260\244\261\377\252\232\247\377\262\243\256\377\312\274" + "\306\377\313\277\307\377\307\273\305\377\233\221\242\377\211\200\222\377" + "\203~\215\377u\203h\377u\246\022\377~\257\007\377\177\247\002\377\225\264\001\377" + "\202\253\002\377j\231\002\377z\251\002\377\201\252\001\377\201\254\001\377r\234\001\377" + "~\257\002\377}\260\007\377w\255\010\377w\251\003\377\223\277\031\377\207\270\011\377" + "\200\261\013\377x\245\001\377\206\262\001\377\213\267\001\377\206\257\002\377\212" + "\262\001\377\220\276\016\377\214\276\026\377{\264\002\377m\250\001\377n\251\001\377" + "h\235\001\377|\262\001\377}\255\001\377\210\264\001\377\221\275\002\377\203\263\002\377" + "q\245\001\377u\252\001\377|\267\003\377~\270\001\377\207\260\001\377\206\256\001\377" + "\211\266\007\377r\236\002\377y\251\002\377j\234\001\377P\202\002\377\\\216\002\377x\245" + "\001\377\210\267\026\377\206\266\021\377|\250\002\377g\237\012\377c\232#\377\200" + "\264\060\377t\253\034\377l\247\017\377m\244\014\377\206\264\032\377\227\274\070" + "\377w\240\037\377z\246\064\377\216\267X\377}\252S\377q\217`\377\212\207\217" + "\377\215\205\222\377\230\214\234\377\230\215\235\377\241\223\242\377\210" + "z\210\377\302\263\275\377\335\321\331\377\353\340\347\377\325\314\324\377" + "\266\245\255\377\247\217\214\377\243\213\200\377\330\312\302\377\326\310" + "\300\377\303\255\241\377\302\245\210\377\301\246\210\377\263\227z\377\221" + "v\\\377`M@\377pgb\377\252\247\251\377\312\304\310\377\305\276\305\377\313" + "\306\314\377\260\250\262\377\275\264\275\377\274\263\274\377\254\245\254" + "\377\255\250\256\377xss\377ysl\377ri`\377uja\377\242\232\212\377\265\257" + "\231\377\302\275\252\377\317\310\271\377\331\325\320\377\340\332\332\377" + "\325\305\276\377\303\252\224\377\322\272\246\377\277\244\212\377\237}a\377" + "tS@\377\275\243\216\377\333\313\277\377\343\331\326\377\214{}\377xd`\377" + "ve\\\377\204rq\377\210w\200\377\224\204\221\377\251\232\247\377\274\256\270" + "\377\273\253\266\377\265\242\256\377\313\275\306\377\330\313\323\377\321" + "\305\314\377\217\201\225\377\211\200\221\377x~y\377o\234<\377w\253.\377p" + "\245\001\377\201\256\001\377\177\250\001\377r\236\002\377\177\254\002\377\206\261\002" + "\377\201\253\001\377{\245\001\377\177\250\001\377~\247\001\377\202\261\001\377r\242" + "\002\377|\253\001\377\202\262\002\377|\254\003\377|\252\001\377\211\267\001\377\206\262" + "\001\377\203\256\001\377\206\257\001\377\202\253\001\377\201\264\000\377\201\272\006" + "\377n\244\001\377q\252\001\377l\240\002\377~\261\004\377\206\266\012\377\215\266\002" + "\377\213\264\001\377\211\265\001\377p\251\001\377s\254\001\377{\257\001\377\202\265" + "\001\377e\221\001\377\203\255\002\377\216\270\001\377\207\261\001\377j\232\001\377_\217" + "\002\377X\210\002\377s\241\002\377z\254\001\377\214\274A\377g\230\020\377g\223\002\377" + "y\251\010\377\203\265'\377\215\277@\377y\256\063\377\233\303i\377\236\302a" + "\377\177\254\015\377{\254\003\377l\236\011\377n\236#\377o\235\071\377p\230L\377" + "}\205\202\377\217\210\226\377\242\234\247\377\221\210\230\377\214\177\220" + "\377\226\206\224\377\256\234\245\377\320\303\311\377\334\322\332\377\311" + "\271\302\377\277\257\270\377\252\226\241\377\245\226\227\377\177j_\377\320" + "\300\264\377\320\276\257\377\320\275\252\377\312\271\244\377\301\257\234" + "\377\274\254\232\377\274\256\241\377\236\224\216\377\250\244\243\377\302" + "\300\304\377\277\275\301\377\311\307\314\377\317\315\322\377\300\270\301" + "\377\303\300\306\377\311\307\314\377\266\263\270\377\315\314\320\377\271" + "\271\274\377\235\234\230\377~ys\377TFB\377YOS\377\215}e\377\277\264\237\377" + "\266\253\223\377\273\253\220\377\300\264\243\377\336\325\323\377\337\324" + "\322\377\330\311\301\377\321\275\255\377\273\240\206\377vVA\377\225sZ\377" + "\336\316\300\377\350\336\327\377\260\244\250\377\227\210\221\377\224\207" + "\205\377ucd\377\206x\202\377\223\205\222\377\243\225\242\377\262\242\256" + "\377\300\261\272\377\273\252\263\377\301\261\271\377\312\273\301\377\317" + "\300\306\377\321\306\312\377\233\222\240\377z{\203\377w\221o\377\211\264" + "c\377p\247\004\377t\250\002\377y\247\002\377}\253\001\377~\251\001\377\201\257\001\377" + "{\247\001\377z\244\002\377v\237\002\377\203\252\001\377\204\261\002\377w\245\002\377" + "s\243\001\377z\253\001\377}\261\001\377\201\262\002\377\203\255\002\377\207\263\002\377" + "\203\256\002\377\201\254\002\377\204\255\001\377\206\267\002\377z\257\002\377s\251" + "\001\377t\254\002\377j\233\001\377n\237\002\377\220\300\024\377\212\270\000\377\211" + "\264\001\377{\251\002\377{\260\002\377v\252\002\377\177\257\001\377\202\263\001\377b" + "\221\002\377g\225\001\377\213\267\001\377\224\276\012\377\205\261\005\377l\232\001\377" + "d\221\002\377w\246\002\377{\253\001\377r\247\000\377T\211\000\377P\207\002\377O\210\002" + "\377{\255#\377\227\303K\377\231\304Z\377|\256\067\377z\251\037\377\204\260" + "\002\377\210\261\007\377\202\253*\377q\237.\377o\233>\377q\217[\377\231\227\241" + "\377\226\216\236\377\200u\211\377\207}\221\377\200t\206\377\235\215\232\377" + "\303\267\273\377\306\267\276\377\251\221\233\377\266\240\253\377\315\276" + "\306\377\311\274\304\377\241\216\217\377\264\237\226\377\275\243\214\377" + "\272\241\206\377\264\235\177\377\242\210j\377\254\225}\377\304\264\253\377" + "\331\323\322\377\316\313\315\377\300\277\300\377\300\300\300\377\271\271" + "\272\377\311\311\315\377\312\311\316\377\302\301\306\377\304\303\311\377" + "\310\307\315\377\312\311\316\377\273\272\276\377\320\320\324\377\276\301" + "\277\377\266\266\263\377\233\232\223\377\210\177\204\377jY]\377\212pV\377" + "\305\273\247\377\276\263\235\377\301\262\232\377\301\265\241\377\327\317" + "\310\377\316\277\261\377\301\261\237\377\304\261\237\377\261\231~\377|\\" + "C\377\312\263\232\377\342\325\312\377\300\262\257\377l`g\377\200t~\377\243" + "\232\241\377\215\201\213\377\230\213\227\377\217\201\221\377\243\225\244" + "\377\271\252\264\377\310\271\277\377\261\240\251\377\274\255\264\377\276" + "\255\265\377\323\306\311\377\315\303\311\377\205\202\216\377m|q\377\234\272" + "\220\377z\261\034\377l\241\000\377r\247\003\377\177\256\015\377s\246\001\377|\262" + "\012\377{\256\006\377v\244\001\377x\246\001\377\177\256\001\377s\241\002\377p\236\001" + "\377z\251\002\377z\250\001\377w\251\001\377y\260\001\377|\256\002\377\201\262\001\377" + "\177\252\001\377{\245\002\377\206\261\001\377\207\271\002\377o\242\001\377r\255\006\377" + "u\256\005\377p\240\001\377j\233\002\377\204\270\025\377\231\304.\377\207\270\004\377" + "x\252\002\377k\240\001\377|\254\001\377~\255\001\377~\261\001\377k\235\001\377m\235\002" + "\377{\252\003\377\223\277\031\377}\253\002\377}\254\001\377~\256\002\377|\261\003\377" + "m\236\002\377p\245\001\377i\245\004\377u\257&\377_\232\037\377\214\275f\377\215" + "\277T\377z\255\013\377m\237\002\377p\230\002\377\211\262\001\377\213\262\004\377u" + "\237\003\377]\214\000\377h\224\062\377\213\225\214\377\222\213\227\377~t\204\377" + "xp\200\377}u\207\377\265\254\271\377\276\260\264\377\271\244\250\377\267" + "\240\244\377\270\243\253\377\303\264\276\377\277\256\266\377\273\254\264" + "\377\241\217\223\377\227{p\377\276\241\213\377\267\235\210\377\272\246\223" + "\377\275\253\227\377\271\246\222\377\314\276\267\377\340\333\334\377\333" + "\331\335\377\307\307\307\377\275\276\271\377\261\262\254\377\275\302\276" + "\377\276\301\300\377\271\272\272\377\272\273\274\377\303\304\307\377\301" + "\303\304\377\257\262\255\377\274\300\273\377\307\314\312\377\311\313\312" + "\377\302\302\303\377\274\273\276\377\264\257\260\377\256\241\234\377\326" + "\321\312\377\265\256\234\377\305\274\253\377\266\246\223\377\314\300\261" + "\377\301\270\261\377\320\306\272\377\306\272\254\377\304\253\226\377\216" + "pS\377\242\207l\377\312\271\243\377\256\231\216\377yjs\377tdj\377rek\377" + "\245\234\246\377\264\251\261\377\223\204\223\377\237\215\235\377\256\236" + "\250\377\301\262\271\377\277\256\267\377\265\242\254\377\271\250\260\377" + "\273\253\257\377\343\327\327\377\236\225\242\377sux\377\215\243\203\377\177" + "\260)\377p\243\000\377n\236\002\377r\244\003\377j\231\003\377|\262\016\377p\244\001" + "\377u\253\003\377\200\264\021\377t\247\002\377k\232\002\377t\242\002\377y\246\001\377" + "\204\262\002\377u\244\002\377t\250\001\377m\237\001\377x\257\002\377|\255\001\377~\251" + "\002\377\213\273\007\377\201\264\002\377q\246\003\377u\260\011\377n\240\001\377s\243" + "\001\377y\250\002\377\201\266\011\377\243\310B\377\201\261\006\377j\237\002\377a\227" + "\002\377\217\274\002\377\206\260\001\377\201\262\001\377h\227\002\377u\246\001\377l\234" + "\002\377p\243\017\377{\256\030\377e\234\006\377Y\220\003\377v\257\024\377l\242\004\377" + "p\246\002\377u\255\010\377\211\277:\377\177\263:\377r\250\065\377\200\264B\377" + "`\227\022\377[\216\001\377|\253\001\377\215\266\002\377\210\257\006\377\227\265\065" + "\377\201\246@\377w\230a\377\211\216\210\377\214\207\217\377\217\204\222\377" + "\226\217\236\377\253\241\256\377\233\207\222\377\264\240\244\377\303\261" + "\263\377\270\244\252\377\270\246\260\377\246\223\236\377\275\256\267\377" + "\315\300\311\377\244\217\225\377\206ig\377\250\216{\377\265\236\213\377\252" + "\223\203\377\261\232\206\377\261\235\214\377\317\306\301\377\327\322\322" + "\377\327\325\326\377\313\314\311\377\263\266\251\377\243\251\231\377\254" + "\261\244\377\261\266\253\377\263\270\256\377\265\272\262\377\273\301\272" + "\377\271\300\271\377\265\272\262\377\263\271\261\377\301\306\300\377\305" + "\312\310\377\316\320\322\377\317\317\323\377\316\315\321\377\326\326\330" + "\377\315\312\305\377\302\274\263\377\273\261\243\377\270\256\233\377\255" + "\237\211\377\277\265\244\377\315\305\273\377\307\275\257\377\301\263\241" + "\377\227~h\377gK\063\377\245\215u\377\211xk\377XLL\377h`c\377\204z\177\377" + "\204u\200\377\255\242\252\377\261\242\254\377\236\214\232\377\252\231\244" + "\377\273\253\264\377\277\260\266\377\271\250\256\377\267\246\255\377\272" + "\251\254\377\325\307\310\377\304\270\300\377\177y\203\377\214\232{\377\211" + "\265\067\377r\250\002\377k\233\001\377q\246\003\377s\247\004\377r\247\004\377j\237\002" + "\377^\217\001\377z\246\004\377~\253\007\377r\234\001\377\177\253\000\377\203\257\002" + "\377~\253\002\377z\251\002\377p\245\002\377k\235\001\377n\237\001\377|\257\004\377\201" + "\263\011\377{\256\003\377\202\267\014\377\201\270\023\377o\246\000\377o\241\001\377" + "o\240\002\377~\254\001\377y\251\000\377\212\267\016\377w\246\002\377h\235\002\377Y\221" + "\002\377|\260\001\377\217\272\002\377\212\265\000\377z\246\002\377v\245\001\377f\225" + "\002\377_\215\001\377v\251\014\377\205\266$\377e\233\003\377_\224\001\377w\254\032" + "\377\204\267+\377\207\273)\377~\262\020\377z\254\000\377{\255\014\377\205\263" + "\067\377\205\265\063\377U\216\003\377f\234\005\377\207\262\013\377\211\260\030\377" + "\301\301\247\377\212\217x\377{\204y\377\204\204\210\377\221\207\223\377\242" + "\227\247\377\233\222\240\377\220~\216\377\232\207\221\377\302\260\267\377" + "\311\272\301\377\271\250\260\377\254\227\242\377\265\243\255\377\276\257" + "\271\377\301\264\274\377\274\256\265\377\222\200}\377p_R\377\256\223~\377" + "\250\215r\377\253\216s\377\306\262\244\377\277\261\253\377\322\314\311\377" + "\324\324\323\377\305\307\301\377\266\271\252\377\247\255\224\377\237\251" + "\212\377\242\253\215\377\242\256\220\377\240\256\221\377\252\266\237\377" + "\255\272\245\377\255\271\243\377\255\270\245\377\266\277\262\377\303\310" + "\302\377\313\316\316\377\316\322\325\377\330\331\335\377\332\332\334\377" + "\317\314\307\377\301\271\255\377\275\261\240\377\273\257\241\377\217zc\377" + "~iP\377\214{c\377\233\213x\377\311\273\261\377\241\211w\377kVH\377q\\J\377" + "aNB\377UF?\377bUU\377xlt\377\212~\212\377\225\207\221\377\245\227\240\377" + "\232\213\230\377\260\242\255\377\257\236\252\377\312\272\302\377\300\261" + "\267\377\260\236\250\377\301\261\267\377\321\302\305\377\312\275\301\377" + "\200x\207\377\213\225l\377\217\262=\377i\241\003\377r\250\012\377t\247\004\377" + "o\234\001\377g\224\002\377[\213\001\377m\231\002\377\206\253\002\377\206\261\002\377" + "~\250\005\377\232\301\020\377\203\256\001\377|\250\002\377w\247\002\377s\250\001\377" + "k\233\002\377n\236\002\377s\246\000\377\216\275/\377{\260\005\377x\254\002\377{\263" + "\006\377v\254\004\377r\244\002\377v\245\002\377\202\256\002\377n\235\002\377l\235\001\377" + "s\244\001\377j\237\001\377c\234\001\377\202\273\031\377\210\270\001\377\207\262\002" + "\377\206\260\001\377i\226\002\377U\206\001\377_\215\002\377i\232\001\377e\227\006\377" + "y\257\026\377z\255\035\377\220\277\066\377\206\265\037\377m\237\002\377p\240\000" + "\377\177\256\000\377\221\270\000\377\214\267\061\377v\241G\377^\177\070\377awE" + "\377q{c\377c_h\377G>N\377TLY\377olt\377\227\223\235\377\241\231\247\377\224" + "\210\230\377\203t\206\377\222\200\216\377\256\236\245\377\310\273\300\377" + "\243\217\231\377\266\243\253\377\231\204\220\377\300\262\271\377\300\262" + "\273\377\246\225\234\377\261\240\242\377\265\246\243\377\211so\377rQF\377" + "nK>\377\227xe\377\252\212{\377\276\253\241\377\306\272\264\377\273\263\253" + "\377\265\262\244\377\251\251\222\377\245\251\207\377\233\244y\377\227\241" + "q\377\221\235i\377\214\231g\377\222\236p\377\222\240u\377\225\243|\377\244" + "\261\222\377\250\265\235\377\267\301\262\377\306\315\306\377\306\313\311" + "\377\323\327\327\377\327\330\332\377\323\322\322\377\311\304\277\377\304" + "\274\266\377\271\254\243\377\302\272\264\377\271\256\245\377\252\232\214" + "\377\252\223{\377\270\245\217\377\264\237\217\377\203ie\377t`\\\377[EC\377" + "wfd\377vkm\377znu\377\202t}\377\217\201\212\377\231\211\224\377\232\207\222" + "\377\222\200\216\377\255\236\252\377\274\255\264\377\304\265\273\377\252" + "\230\242\377\233\207\222\377\257\233\237\377\320\303\305\377\271\254\267" + "\377\240\236{\377\225\260H\377\202\261\064\377\200\260\036\377x\251\003\377u" + "\243\003\377l\226\001\377j\223\002\377{\242\001\377\205\256\007\377\206\264\016\377" + "\206\260\031\377\255\315;\377\201\263\002\377\177\250\002\377\177\247\002\377{\244" + "\002\377y\242\002\377w\237\000\377\215\273$\377\227\304\067\377z\252\003\377z\247" + "\001\377\200\257\002\377~\255\004\377o\236\002\377\203\256\002\377\204\255\003\377q\236" + "\002\377m\235\002\377y\252\001\377u\247\002\377k\237\003\377t\252\003\377y\247\002\377" + "\206\263\002\377z\246\002\377o\237\002\377Hw\002\377\\\213\001\377d\225\003\377c\226" + "\014\377\224\306<\377\214\276\065\377\210\264\026\377\201\247\003\377q\241\002\377" + "p\237\003\377c\204,\377Yo\064\377alI\377KDN\377=\064?\377G=J\377RET\377@\060=" + "\377-\033(\377NAL\377\216\207\225\377\232\221\236\377\217\205\223\377\213" + "~\217\377\203u\205\377\221\200\216\377\312\273\301\377\272\251\254\377\272" + "\247\250\377\254\230\235\377\236\212\224\377\276\257\265\377\262\241\251" + "\377\252\227\240\377\257\235\236\377\242\224\222\377\266\250\237\377\213" + "uh\377qWM\377pTC\377\206gO\377\207jV\377\216q`\377\241\215|\377\234\215z" + "\377\230\222v\377\230\227t\377\211\211`\377}}N\377oo=\377tvE\377\203\211" + "W\377\205\216\\\377\210\220d\377\232\245~\377\250\261\224\377\251\262\233" + "\377\266\274\255\377\276\304\273\377\303\306\300\377\307\307\306\377\326" + "\326\326\377\316\312\311\377\310\301\275\377\273\256\246\377\257\235\217" + "\377\264\246\234\377\247\222{\377\275\250\235\377\242\205e\377\231}d\377" + "\215wo\377\202pk\377\203wt\377\201xu\377shj\377\211z\200\377\207w|\377\212" + "x~\377\235\213\222\377\256\240\250\377\217\200\212\377\252\232\244\377\261" + "\243\254\377\261\241\251\377\253\232\242\377\237\216\226\377\240\216\226" + "\377\263\242\250\377\324\310\314\377\257\245\216\377\236\257[\377r\243(\377" + "\226\303N\377s\246\010\377o\235\001\377n\233\002\377e\220\002\377\177\242\002\377" + "\221\270\034\377\207\266#\377\247\311J\377\233\277'\377|\252\000\377\211\263" + "\001\377\207\256\000\377\230\265\024\377\231\266\034\377|\244\000\377\177\253\011" + "\377}\254\002\377~\250\002\377z\245\001\377\204\264\003\377\201\261\003\377t\241\002" + "\377y\243\002\377z\245\002\377o\234\002\377w\250\001\377z\254\001\377}\260\003\377r\241" + "\001\377u\250\001\377n\236\002\377\204\261\001\377x\245\001\377k\232\002\377k\232\001\377" + "t\244\005\377\214\274\"\377\220\300&\377s\252\016\377b\235\005\377\215\272\017" + "\377x\252\005\377k\240\020\377\177\226V\377\200\201\203\377\237\234\242\377" + "\220\207\217\377ymt\377XHT\377=+\071\377;)\063\377cT]\377qfn\377gZe\377{p\200" + "\377\203w\207\377\207y\212\377\214~\217\377\213}\215\377\246\227\241\377" + "\305\265\264\377\254\230\232\377\241\215\217\377\220}\206\377\270\250\254" + "\377\274\254\261\377\254\233\242\377\266\243\251\377\262\242\242\377\236" + "\216\217\377\214\177~\377\245\227\221\377\227\203t\377jL\063\377iO\067\377" + "ubO\377\\H;\377dND\377^K=\377XF\064\377P@)\377aV\070\377bV\064\377uoI\377\207" + "\207\\\377\212\214b\377\221\224k\377\214\216i\377\224\227w\377\244\247\217" + "\377\235\237\212\377\245\247\224\377\237\234\214\377\265\262\251\377\271" + "\263\257\377\304\276\276\377\273\256\250\377\265\241\220\377\255\227\203" + "\377\240\203h\377\241\207v\377\222va\377\231|a\377\214wh\377nS<\377n[M\377" + "zfb\377\244\226\223\377}qs\377{po\377\212{|\377\223\204\207\377\221\203\206" + "\377\220~\201\377\220~\203\377\242\222\234\377\240\220\232\377\232\210\222" + "\377\267\253\260\377\266\251\257\377\226\205\217\377\241\220\227\377\255" + "\232\236\377\313\300\300\377\274\267\252\377\227\253i\377i\234\027\377t\246" + "\032\377y\253\034\377n\241\003\377o\241\002\377g\221\001\377y\240\001\377\226\274\064" + "\377t\243\012\377\262\320M\377\205\256\010\377\200\255\003\377}\254\001\377{\250" + "\002\377\201\251\002\377\212\255\004\377~\245\002\377i\230\001\377s\243\001\377\200\254" + "\002\377u\243\001\377y\250\002\377w\252\001\377r\242\002\377w\250\002\377w\246\002\377" + "u\244\001\377u\250\002\377{\257\012\377w\246\013\377{\253\007\377v\247\002\377w\251" + "\003\377v\243\001\377y\251\001\377_\214\001\377v\251\012\377\224\306%\377\206\267" + "\022\377{\253\001\377]\221\001\377W\215\001\377_\227\004\377d\200>\377LJR\377\063#" + "\065\377\067!\065\377_KZ\377\226\214\224\377\270\261\267\377\206|\203\377E\066" + ":\377/\033'\377P,\377j_O\377uog\377pjf\377aZZ\377" + "i^Z\377\217~y\377\204qj\377\211ys\377\220\200|\377\233\213\207\377\220}z" + "\377\264\245\242\377\231\214\225\377\206w\202\377\243\227\236\377\260\246" + "\252\377\232\213\224\377\226\204\216\377\246\221\225\377\310\273\274\377" + "\273\265\265\377~\232j\377t\234:\377o\236\021\377r\241\007\377v\245\003\377u\245" + "\001\377r\242\013\377~\260\071\377l\234\000\377\253\303\062\377\206\252\010\377s" + "\240\001\377s\241\002\377p\242\001\377q\236\001\377~\250\002\377\213\261\002\377\211" + "\255\001\377\202\251\002\377v\242\002\377z\244\002\377~\252\011\377y\247\004\377t\243" + "\001\377u\250\001\377q\243\001\377j\235\002\377o\250\002\377u\250\001\377s\242\001\377" + "g\230\002\377\\\220\001\377\200\257$\377r\244\013\377\206\265\021\377\222\302\035" + "\377\203\265\024\377[\213\000\377x\242\001\377\205\260\001\377t\241\002\377p\243\010" + "\377Ts\071\377?V\061\377$%\031\377\017\013\006\377\015\011\003\377\007\006\002\377\000\000\001\377" + "!\037!\377\000\000\000\377\000\000\000\377%!$\377\012\005\011\377$\040&\377'\024#\377i[h\377" + "zk{\377\203u\204\377\203w\204\377\223\206\225\377\246\232\242\377\317\302" + "\305\377\313\274\275\377\230\200\206\377yem\377\212v\200\377\264\244\253" + "\377\250\227\236\377\253\225\233\377\272\244\241\377\232\203\200\377\241" + "\212\205\377\226\203}\377uc_\377M\071\061\377o^V\377\250\242\232\377\265\260" + "\247\377\210~h\377{oO\377ta?\377[@\027\377U<\022\377dR-\377aN*\377bN*\377l" + "`\070\377\204\202Z\377\206\206_\377\217\217m\377\233\234\177\377\227\227z" + "\377\213\206h\377\202yY\377\221\204c\377\201pQ\377udA\377\212y\\\377~nN\377" + "^I-\377W>#\377u]M\377gJ\060\377v`G\377\245\234\224\377\231\221\201\377\204" + "zi\377mbS\377cXQ\377i`\\\377od^\377~lg\377|jg\377\204sr\377\225\205\203\377" + "\203qp\377\206qo\377\254\235\233\377\203u\201\377\221\206\220\377\231\213" + "\223\377\252\235\241\377\234\215\224\377zju\377\215|\204\377\270\253\256" + "\377\302\272\276\377\177\225n\377~\240A\377}\245\026\377o\234\001\377r\240\001" + "\377z\255\001\377u\244\010\377z\255\026\377{\250\007\377\201\250\004\377z\241\001\377" + "\214\261\033\377r\236\003\377w\247\002\377w\243\002\377\213\264\001\377\215\261\002" + "\377\212\255\002\377\207\256\002\377\202\252\001\377p\233\002\377x\242\014\377\212" + "\265,\377|\253\014\377y\251\001\377|\257\001\377s\240\001\377z\251\003\377x\245\001" + "\377q\236\001\377g\227\002\377i\233\006\377\211\265%\377\213\271\060\377~\261\026" + "\377h\233\005\377i\231\001\377p\234\001\377\201\253\002\377\220\271\003\377s\237\035" + "\377Zz+\377?S(\377\061@\036\377\012\012\003\377\000\000\000\377\000\000\000\377\000\000\000\377\015" + "\015\016\377\004\004\004\377\000\000\000\377\001\001\001\377\001\000\001\377\020\015\020\377\002\002\003\377" + "\001\000\000\377;-\071\377xgv\377zlz\377\217~\221\377\217}\216\377\243\222\235\377" + "\324\312\312\377\264\240\241\377\220{{\377lZb\377\212z\201\377\266\246\254" + "\377\261\240\243\377\303\263\261\377\301\255\250\377\266\235\221\377\231" + "~s\377\226|q\377}fa\377l[U\377g]T\377k`M\377{nU\377\177pT\377\215~b\377\210" + "y^\377q_?\377]G!\377\\D\032\377aK\037\377iV&\377m^,\377\200xI\377|tC\377wp" + "B\377\177xQ\377~xS\377\214\205`\377\206~X\377xkC\377\201uN\377\203zW\377" + "\205}^\377\233\226~\377\236\231\206\377\224\212w\377\235\223\200\377\232" + "\214u\377\237\224~\377\227\213x\377yl[\377bSA\377^UC\377PC/\377XJ;\377cV" + "L\377xmg\377\203tr\377wdd\377vef\377\204qs\377\224\200\201\377\256\240\240" + "\377\204x\201\377\204y\203\377\233\216\226\377\240\221\232\377\232\212\224" + "\377veq\377\223\201\207\377\263\247\247\377\310\304\305\377\221\241\201\377" + "m\227)\377n\231\010\377s\236\003\377t\232\003\377\213\267\032\377x\250\003\377{\251" + "\001\377k\237\002\377h\233\001\377w\247\000\377\260\277\061\377\216\246\024\377q\232" + "\001\377\177\252\002\377\203\261\002\377}\242\002\377\220\264\002\377\213\261\001\377" + "{\244\002\377u\240\003\377z\245\010\377x\246\012\377k\234\002\377z\257\002\377y\250" + "\002\377s\236\002\377\177\253\003\377z\246\001\377i\230\002\377j\231\002\377w\241\003\377" + "y\244\002\377u\246\004\377v\256\006\377e\220\002\377q\230\001\377\205\253\001\377\214" + "\261\000\377\211\265\017\377f\215\037\377Vi\063\377+>\015\377\040\062\012\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377!\027\036\377h[g\377\200q\177\377" + "\237\223\241\377\204v\206\377\251\233\243\377\303\267\270\377\244\216\221" + "\377\204pv\377mYe\377\224\204\211\377\270\251\254\377\264\242\246\377\263" + "\233\232\377\271\242\231\377\267\237\224\377\237\203|\377\235\204\177\377" + "\200h`\377aH>\377ZA\064\377n_E\377sfG\377\204zX\377\242\237\204\377\243\241" + "\210\377\227\224w\377\200uT\377\203vS\377\210|U\377\203vL\377\213\204Z\377" + "\212\205Z\377\213\205]\377\213\204_\377\177vO\377xk?\377m]/\377iY.\377pb" + "\067\377skB\377}zZ\377\226\227\204\377\223\216|\377\237\235\213\377\250\246" + "\224\377\240\235\210\377\213\200k\377Q<'\377VD\066\377eVO\377O>'\377XH+\377" + "^O/\377]J,\377eT:\377\217\200x\377\210xx\377\177nl\377\202pn\377\214|x\377" + "\210sq\377\236\215\212\377~pz\377\215\201\212\377\246\231\241\377\221\201" + "\213\377\237\222\231\377rdn\377\215}\207\377\256\241\244\377\274\266\267" + "\377\226\243\205\377g\223\033\377e\224\003\377j\222\002\377p\225\004\377\224\272" + "\061\377\204\256\020\377~\251\002\377i\237\001\377b\230\000\377n\237\001\377\210\247" + "\021\377\276\311P\377|\241\004\377x\243\002\377{\253\002\377v\240\001\377\215\264" + "\002\377\202\250\002\377\200\246\002\377\204\254\002\377s\232\001\377y\245\001\377t\241" + "\001\377r\240\002\377o\234\002\377o\237\002\377y\250\002\377v\242\002\377s\237\002\377" + "l\232\002\377i\224\002\377u\242\001\377q\236\001\377r\244\002\377m\233\002\377\203\257" + "\003\377\211\262\003\377\222\272\021\377\213\263E\377\221\266V\377w\215d\377a" + "\202E\377\065M\"\377\034.\020\377\016\032\002\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\"" + "\035!\377b[c\377sds\377\230\212\235\377\204u\211\377\246\227\241\377\302\267" + "\266\377\232\210\210\377\213y\200\377s`m\377|jv\377\247\225\233\377\216z" + "\203\377\260\234\230\377\254\225\216\377\262\232\215\377\224zo\377\207le" + "\377oVO\377s\\S\377fRE\377\\N\066\377h^=\377ld@\377}wW\377\215\211k\377zu" + "Z\377}x\\\377\206|^\377|mF\377\215\202]\377\215\203\\\377|qI\377\212\200" + "\\\377\224\215n\377\203uP\377{kA\377m\\.\377hW/\377gW\061\377ujI\377\203|" + "b\377\203}c\377\202y]\377|rO\377xkH\377YF)\377maG\377K<\034\377RE'\377zqf" + "\377SC&\377P@\036\377VA!\377P:\031\377aO\063\377\204tc\377\226\213\213\377u" + "cb\377\213zw\377\220\177y\377\216xt\377\230\205\203\377wep\377\206u\201\377" + "\246\234\241\377\220\206\216\377\227\214\223\377k^h\377qcm\377\244\227\232" + "\377\274\270\272\377\212\227w\377e\222\035\377`\216\001\377p\233\002\377r\231" + "\003\377\225\273-\377|\243\001\377\201\257\003\377r\244\007\377r\247\017\377g\230" + "\001\377t\231\002\377\270\306S\377\214\256\033\377y\245\001\377o\234\002\377r\237" + "\001\377{\246\001\377\201\250\001\377\201\246\002\377\206\250\002\377}\240\003\377\211" + "\257\002\377\177\247\001\377w\241\001\377q\240\001\377w\252\002\377f\232\002\377w\244" + "\001\377~\244\001\377j\223\002\377q\227\002\377t\243\002\377v\246\002\377y\251\005\377" + "g\227\001\377s\243\002\377|\252\002\377u\241\002\377l\232\010\377c\215+\377=]$\377" + "Ms,\377Jb>\377WlL\377[mX\377XlW\377NcM\377N`H\377AR?\377-\066,\377\012\016\011" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377'#'\377JAI\377ses\377\225\207" + "\230\377\205v\210\377\270\255\262\377\271\254\254\377\245\224\224\377}kt" + "\377k[g\377\201t}\377\253\232\236\377\240\215\220\377\251\222\216\377\255" + "\222\213\377\274\242\225\377\220ra\377\225xj\377\220ti\377x^R\377iRA\377" + "N\071\036\377=,\013\377dW\064\377{rO\377\221\213m\377\214\204j\377}qW\377|lP" + "\377\203sU\377\210wZ\377\201pO\377\212yX\377\204rW\377\234\224\200\377\231" + "\221{\377\211|b\377\221\207m\377\211\201h\377\207\201h\377\212\206m\377\203" + "ze\377\204{e\377\200xZ\377YO$\377_R(\377reF\377gZ\064\377[M%\377Q@\030\377" + "rdP\377@/\013\377VC\040\377ZD#\377\\E(\377yfP\377wdT\377~oj\377\210yy\377~" + "je\377\206pi\377\224\200y\377\227\203\201\377wfr\377\215~\207\377\252\236" + "\244\377\235\220\227\377\235\217\227\377l^j\377\203u}\377\230\213\215\377" + "\265\256\264\377u\211X\377_\212\022\377_\214\001\377x\244\022\377u\243\011\377" + "|\247\013\377y\244\001\377u\245\000\377\203\265)\377\201\265\063\377g\236\017\377" + "}\250\004\377\207\252\014\377\240\270\035\377\214\256\002\377\201\245\002\377|\243" + "\001\377k\231\002\377x\243\002\377}\243\002\377\217\261\002\377\215\255\002\377p\230" + "\002\377u\236\002\377~\252\001\377~\254\001\377u\245\002\377Y\216\002\377l\230\002\377" + "v\235\001\377q\230\002\377u\234\002\377z\252\003\377p\231\003\377x\240\001\377g\236\003" + "\377t\247\001\377z\246\002\377u\234\002\377x\240\002\377W\201\003\377;a\003\377#H\002\377" + "\036\064\004\377\013\013\006\377\004\004\003\377\001\001\001\377\001\000\001\377\000\001\001\377\003\011\003\377" + "(\067%\377b{^\377w\222y\377AQ?\377\015\023\011\377\035\037\023\377==\066\377<:<\377" + "kbn\377\215\177\220\377\216\177\217\377\264\251\255\377\270\254\253\377\240" + "\216\217\377\211w~\377m\\g\377\202v|\377\267\252\254\377\234\206\216\377" + "\244\215\214\377\226yq\377\256\225\211\377\237\201p\377\221qa\377~bO\377" + "rWB\377fN\066\377UB\"\377C\064\020\377?\061\017\377J\066\026\377kV\070\377n\\B\377" + "\213\205v\377\230\224\205\377\211\200m\377\215\200k\377\231\214w\377\241" + "\226\201\377\232\216w\377\215\201i\377\241\234\210\377\243\241\215\377\235" + "\234\207\377\223\220y\377\224\216t\377\205}[\377ykC\377kZ\061\377sd<\377]" + "H\033\377Q\071\014\377sb=\377l^\064\377VC\033\377G\063\020\377]L\070\377G.\024\377" + "K\063\025\377M\066\027\377q^D\377zfT\377r]Q\377\202oi\377zgb\377\230\207|\377" + "\223\200u\377\205qn\377\226\207\210\377udp\377\200q|\377\223\210\216\377" + "\232\220\224\377\220\205\214\377fYe\377o`k\377\240\224\231\377\261\254\260" + "\377s\211X\377a\212\037\377f\222\027\377i\230\010\377x\250\010\377v\244\003\377" + "v\241\001\377{\246\007\377\200\261\032\377t\250\013\377l\243\006\377e\234\002\377q" + "\237\002\377\200\247\011\377\230\267\013\377\231\267\002\377\225\265\002\377m\232" + "\002\377u\241\002\377\205\260\002\377\223\270\002\377\212\254\001\377i\225\002\377x\242" + "\002\377\206\256\001\377\202\254\002\377u\242\002\377l\232\002\377c\222\001\377y\245" + "\003\377u\237\001\377p\233\002\377t\237\002\377w\233\001\377}\241\003\377g\240\006\377" + "u\250\001\377y\245\002\377}\247\001\377z\242\002\377]\211\001\377Ak\004\377%@\002\377." + "\063#\377!%\026\377\016\016\013\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\011\015\011\377:J#\377`l;\377@G*\377;<\064\377l" + "gn\377\224\210\224\377\207}\210\377\255\244\250\377\272\257\260\377\233\214" + "\216\377xjo\377^PZ\377{ms\377\260\244\245\377\246\225\230\377\243\220\213" + "\377\212ql\377\272\245\235\377\240\202q\377~XB\377yT?\377V\063&\377Z=,\377" + "s^E\377mW\070\377S<\033\377G/\013\377K\064\023\377xhR\377\202ub\377wiV\377\225" + "\217}\377\220\211v\377\230\221\200\377\235\226\204\377\250\241\216\377\243" + "\236\211\377\223\214q\377\221\212m\377\215\204d\377ykE\377xgB\377vb\064\377" + "lV%\377gP#\377\\D\031\377aK!\377ziB\377\177qF\377fP&\377B*\013\377\060\027\003" + "\377ZA\063\377W=,\377pYC\377\202nX\377\213xj\377fMC\377pYP\377nUN\377\206" + "oj\377\237\212\202\377\202ka\377\231\206}\377\235\220\214\377~v{\377\212" + "\201\210\377\235\221\230\377\241\226\234\377\205x\202\377`S`\377l_j\377\241" + "\232\240\377\244\243\246\377i~\071\377`\202\025\377x\244@\377`\216\002\377|\254" + "\005\377v\243\001\377y\247\002\377z\250\006\377w\246\004\377t\241\000\377}\256\005\377" + "i\235\001\377k\225\001\377|\242\001\377\210\251\002\377\214\255\001\377\214\257\001\377" + "y\241\001\377v\240\001\377\201\250\002\377\203\247\002\377\205\250\002\377s\233\001\377" + "~\247\002\377\210\264\002\377}\250\001\377x\242\002\377x\245\001\377m\232\001\377{\245" + "\004\377}\250\020\377x\245\002\377s\232\002\377l\217\002\377\207\246\002\377n\251\003" + "\377k\237\002\377k\232\002\377k\233\001\377g\231\003\377]\221\001\377Hx\003\377\031+\005" + "\377'\060\030\377MOE\377,**\377\012\007\010\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\001\002\000\377\011\027\002\377,D\010\377\060I\010\377\062E\020\377=" + "H)\377rtq\377\232\221\231\377\221\206\216\377\221\205\214\377\261\245\244" + "\377\211y\177\377hX`\377UHP\377\221\206\212\377\252\240\243\377\243\224\227" + "\377\235\212\204\377\215yl\377\253\227\212\377\257\224\206\377\203]I\377" + "\222o^\377\205h\\\377t]R\377nWA\377xaD\377u`;\377hR*\377aI#\377eN+\377Y:" + "\030\377wbK\377xiS\377\177pZ\377\215\177h\377\177pT\377\230\216t\377\226\212" + "n\377\204vR\377\205wU\377\222\210i\377\216\206f\377\214\203e\377\221\206" + "h\377\223\204f\377\202pW\377\224\203o\377\215}a\377xdB\377hM-\377;\040\006\377" + "@'\015\377A'\015\377{fR\377s\\@\377jR<\377oZK\377yf[\377aKG\377eOI\377oWQ\377" + "\241\213\203\377\210lg\377\203bZ\377\232\203\200\377\233\221\223\377\205" + "\200\205\377\230\224\230\377\252\242\245\377\252\237\244\377\216\200\211" + "\377VGR\377gXe\377\230\216\226\377\236\233\235\377{\210S\377g\201\060\377" + "{\251F\377f\234\006\377p\236\002\377\177\252\002\377z\245\003\377y\244\002\377s\241" + "\003\377t\237\002\377\200\250\001\377|\252\004\377j\226\002\377w\240\001\377}\241\003\377" + "\177\243\001\377\200\244\002\377\177\245\001\377\177\246\003\377\201\251\002\377s\232" + "\002\377\177\243\001\377\200\244\002\377\207\260\002\377|\251\002\377w\246\001\377r\234" + "\001\377v\244\000\377\212\256\016\377l\222\002\377|\252\027\377r\240\002\377w\236\002" + "\377|\237\002\377\177\241\002\377p\253\011\377U\200\000\377Z\204\002\377Y\206\002\377" + "[\220\006\377\\\232\006\377S\211\004\377\020\034\003\377\000\000\000\377\023\022\016\377\"\034" + "\025\377\013\012\006\377\000\000\000\377\000\000\000\377\000\000\000\377\001\001\001\377\007\014\001\377\027" + ")\001\377&E\003\377\066^\011\377;e\020\377Mr&\377.A\030\377_[Z\377\220\203\215\377" + "\214\200\211\377\231\216\224\377\276\263\262\377\220\200\203\377g[d\377O" + "@K\377QDH\377\236\223\222\377\247\227\226\377\243\217\213\377\200kd\377\231" + "\204y\377\262\235\221\377\237\201t\377\201aS\377mOA\377I*\036\377hO>\377r" + "X?\377|gI\377v`;\377lW-\377r\\/\377oV)\377~iA\377u_\071\377~jH\377\210yY\377" + "\231\217q\377\235\224x\377\241\231}\377\216\202b\377\215\200a\377\205z]\377" + "\211~d\377\200s\\\377\177r`\377\213\177p\377\222\206z\377~oZ\377|jJ\377Q" + "\071\024\377:!\005\377F.\020\377\\H#\377nX\066\377\231\210s\377{gK\377nYE\377v" + "cQ\377]L>\377aQB\377bL>\377zaT\377\220{l\377|bT\377\200fW\377\273\262\252" + "\377\222\205\210\377\200pz\377\210}\202\377\227\216\220\377\216\205\212\377" + "i^d\377L?K\377d[d\377\226\214\225\377\207\206|\377l|M\377k\225>\377t\251" + "M\377i\240\021\377i\227\001\377t\237\002\377x\241\002\377w\237\000\377\200\254\016" + "\377v\236\003\377\206\250\002\377\230\274\026\377|\250\010\377t\240\002\377~\250" + "\002\377y\237\001\377\220\256\002\377~\240\002\377\205\254\001\377\203\253\002\377\200" + "\247\001\377\202\247\002\377}\243\001\377\204\255\002\377m\233\002\377z\256\016\377" + "{\254\012\377m\226\002\377\207\251\011\377\210\254\015\377\177\254\017\377k\227" + "\002\377\205\252\006\377r\226\002\377{\245\004\377b\243\013\377`\230\015\377Z\206\004" + "\377_\215\001\377Y\212\002\377]\224\000\377[\231\012\377/O\006\377\022\035\002\377\001\002" + "\001\377\000\000\000\377\000\000\002\377\002\003\001\377\006\011\002\377\005\007\002\377\012\030\002\377\031" + ".\002\377;X\006\377:_\003\377\066_\003\377=j\016\377=_\032\377\071D*\377a[^\377\213\177" + "\211\377\225\213\224\377\214\200\211\377\253\237\242\377\231\211\214\377" + "pdk\377ZOW\377d^`\377\203{z\377\231\215\216\377\273\254\246\377\237\210\201" + "\377\177rh\377\261\242\226\377\263\242\224\377\215r^\377v\\H\377|bT\377i" + "O?\377s^O\377xdN\377\206sU\377\205pK\377\222\177W\377\200k?\377oU&\377\204" + "oK\377zfF\377\205rV\377\201nU\377\236\224~\377\205tX\377\216\200`\377\222" + "\204c\377\215|Y\377\230\210h\377\206wU\377\203pU\377t`G\377fS>\377\\K\066" + "\377=*\026\377\061\035\004\377N\070\021\377eR%\377ub\066\377\215{U\377\217\177h\377" + "T<\036\377zeN\377bJ\066\377bJ<\377}f\\\377xaW\377\247\223\216\377w]V\377`F" + ";\377\204rj\377\274\264\254\377\212}\200\377\217\204\211\377\237\226\232" + "\377\230\217\225\377\211\201\210\377`Xa\377F?J\377`Ya\377\243\236\240\377" + "sx`\377l\216>\377\\\217\027\377u\252>\377j\235\006\377o\233\002\377k\225\001\377" + "~\245\002\377z\241\002\377v\235\000\377w\237\002\377\217\264\021\377\212\255\006\377" + "\207\256\010\377t\240\001\377y\244\002\377w\237\002\377\214\254\002\377\211\251\001" + "\377\207\250\002\377\201\246\002\377y\240\002\377}\245\001\377w\236\002\377|\250\002" + "\377b\222\002\377x\252\011\377t\234\001\377~\240\002\377\206\252\007\377}\242\005\377" + "\200\252\014\377m\225\002\377x\236\001\377y\235\003\377\202\253\025\377U\223\001\377" + "c\237\020\377a\232\012\377_\222\001\377c\223\001\377d\227\002\377Y\206\002\377\\\220" + "\011\377R\177\014\377#\067\002\377\023$\002\377\013\031\002\377\034'\002\377?F\013\377;H" + "\013\377\012*\002\377!E\003\377\067^\002\377Ly\003\377Nz\012\377t\246H\377\067R\021\377" + "?O+\377]`Y\377\200z\177\377\201w}\377|px\377\247\233\234\377\233\215\216" + "\377rfk\377SEQ\377LCK\377\214\205\204\377\252\236\234\377\273\256\246\377" + "\250\215}\377pZJ\377\217|p\377\302\266\255\377\234\205r\377z[B\377rS<\377" + "~`I\377oQ:\377mO?\377|cO\377\213wY\377\230\205d\377\222\200^\377\210uR\377" + "\206qO\377\202jF\377|b<\377\212rQ\377\206oL\377\206oI\377\202jC\377za\070" + "\377tZ\060\377\207uX\377\213|_\377vfL\377\\N\064\377\065\"\012\377\065\040\010\377" + "\066\040\004\377Q:\026\377r_\070\377\214zW\377\223\202a\377\206rU\377\201kV\377" + "kT;\377\\A*\377fK\067\377sZK\377|d[\377\233\213\204\377\232\213\205\377}h" + "_\377hRI\377\240\221\207\377\263\253\250\377|ou\377\214\202\206\377\211\177" + "\205\377\243\233\240\377\203\177\205\377UNX\377JAL\377h`g\377z|o\377kzV\377" + "g\231\064\377c\225#\377\202\261A\377n\232\004\377n\232\002\377p\222\002\377\224" + "\251\002\377\215\251\001\377x\231\001\377}\252\006\377{\241\003\377\205\253\002\377\205" + "\255\002\377|\246\001\377y\242\002\377z\242\002\377\201\244\001\377~\236\001\377\207" + "\252\002\377\202\247\002\377\177\244\001\377\202\251\002\377\215\270\003\377x\244\002" + "\377`\217\002\377o\237\002\377r\231\002\377\207\254\002\377p\225\003\377n\223\002\377" + "y\245\010\377c\215\002\377s\234\002\377\204\256\007\377x\237\002\377d\230\010\377c" + "\227\006\377\\\214\001\377Q}\002\377]\211\001\377^\206\001\377`\206\002\377Z~\001\377Y" + "\210\005\377=a\003\377%G\002\377\060J\002\377/B\003\377/?\002\377GV\005\377-F\002\377*O\003" + "\377\065\\\002\377Q|\002\377`\220\015\377X\205\031\377;c\006\377Ih\"\377m{_\377\205" + "\206~\377\204~}\377\201vz\377\246\236\236\377\232\217\217\377pbf\377I:D\377" + "<-\067\377WJM\377\201rq\377\241\223\216\377\266\243\225\377\201fV\377dNG\377" + "\236\213~\377\250\221|\377\211lP\377pL/\377}]@\377\205hN\377\177dJ\377mL" + "\061\377qR\066\377yaF\377\221\202g\377\226\213p\377\236\225z\377\247\236\204" + "\377\232\215t\377\231\211n\377\215|_\377\222\201d\377\230\210l\377\210vV" + "\377~jK\377vbG\377iS\070\377aJ/\377S>\036\377V@\033\377mY\067\377hP.\377[@\040" + "\377\231\205e\377\243\223u\377zdM\377v^D\377\203nS\377w[B\377^@)\377\210" + "pZ\377\237\211y\377\214uh\377\242\222\211\377]LF\377G\065\062\377\204rm\377" + "\304\275\271\377\222\210\207\377\204y~\377\226\214\217\377\237\226\231\377" + "\235\227\235\377oms\377@:E\377@\071F\377pmo\377ip\\\377m\206Y\377d\222\067" + "\377a\224(\377m\244\036\377`\220\000\377~\252)\377y\232\006\377\232\260\004\377" + "\215\251\002\377\200\245\002\377z\244\004\377n\224\002\377\200\245\002\377\205\251" + "\002\377\215\263\002\377t\235\001\377t\235\002\377~\242\002\377\204\251\002\377z\237" + "\002\377\207\256\001\377\177\244\002\377\211\260\001\377\204\251\001\377~\246\002\377" + "q\236\002\377a\216\001\377s\234\001\377w\235\002\377f\214\002\377l\221\000\377v\237\003" + "\377l\223\002\377r\232\002\377\201\247\002\377{\241\000\377\204\260N\377}\246\060" + "\377a\212\002\377Do\002\377Y|\002\377X{\002\377Rx\001\377\\\203\001\377Mv\002\377Jw\003\377" + "Ky\004\377Cd\002\377\064O\003\377\035\067\001\377)=\001\377:M\002\377>V\002\377\062O\002\377" + "\377e[b\377\204z" + "y\377\210xw\377\307\267\256\377\221wj\377^H>\377\201i_\377\215uh\377\227" + "{g\377\242\206j\377\234}[\377\207gE\377sS\066\377\201bH\377a@\"\377jL-\377" + "\177jK\377\201pT\377\221\210u\377\243\235\212\377\234\226\202\377\245\236" + "\216\377\240\233\214\377\243\233\214\377\235\223\204\377\223\206v\377\213" + "|l\377\224\205w\377\206sa\377\200nV\377\206tX\377\207oR\377z_;\377\203hH" + "\377\231\204b\377\251\234y\377}gP\377eI\065\377\216yb\377~iP\377fI-\377}a" + "G\377\241\213q\377\250\227\204\377\226\205s\377\217\202u\377A,$\377\\J?\377" + "\244\230\217\377\303\276\271\377\203\177\202\377\202{\200\377mck\377\213" + "\203\207\377\203y\200\377NDN\377JDL\377MIP\377ppm\377ahR\377{\235j\377Y\207" + "-\377d\225+\377a\226\014\377a\220\003\377p\232\012\377n\221\002\377{\244\002\377" + "y\241\002\377z\243\003\377k\223\002\377o\230\002\377}\250\002\377\203\254\002\377\207" + "\263\002\377p\241\003\377j\226\002\377\177\246\001\377\210\256\001\377\205\253\001\377" + "\202\252\002\377~\246\002\377\206\255\001\377w\234\002\377\206\254\006\377u\233\002\377" + "x\241\003\377w\240\002\377l\221\001\377m\224\001\377|\252\017\377\204\265\030\377|" + "\256\016\377\211\272\035\377\224\300)\377\215\270\036\377^\222\005\377p\233\012" + "\377r\226\007\377n\215\007\377`}\001\377_\177\001\377]\177\001\377Z\203\002\377Kt\002\377" + "It\002\377J\177\003\377Cj\002\377\060O\001\377@X\004\377\032\070\002\377\037<\002\377,I\000\377" + "\063W\001\377Mv\004\377\\\206\037\377Jj\002\377Yx\005\377]x\036\377Xe?\377poi\377\205" + "{\201\377\205z\201\377\225\213\215\377\232\217\217\377~tz\377_S[\377A\071" + ">\377\071\063\064\377]YU\377\177xv\377\272\255\245\377\253\227\203\377lP@\377" + "T\066-\377\203m`\377\264\240\215\377\253\226y\377\252\224v\377\231\177c\377" + "\207mS\377tU;\377fE$\377nP/\377bH%\377\201oP\377\215\177d\377\204uZ\377\210" + "}f\377\224\217\201\377\222\216\200\377\212\201r\377\232\221\204\377\243\235" + "\215\377\226\216z\377\227\211t\377\215|f\377\203pS\377q[\067\377\206nJ\377" + "\240\214k\377\257\242\205\377\242\222t\377{dK\377^A.\377w^I\377zcK\377nR" + "\070\377\200eM\377\237\213x\377\261\245\223\377\236\217|\377\220\201q\377" + "S?\062\377P<\061\377\214zj\377\271\262\244\377\244\233\223\377\205|{\377\220" + "\207\207\377\220\207\213\377\206{\200\377obl\377OEP\377F?G\377VRU\377qrg" + "\377brQ\377r\241S\377\\\216-\377b\227%\377T\204\005\377f\222\010\377g\215\001" + "\377k\223\001\377v\244\003\377\177\255\013\377y\246\004\377g\222\002\377x\243\002\377" + "\177\254\002\377\201\255\002\377t\243\001\377m\243\002\377l\232\001\377|\244\001\377" + "\210\256\001\377\201\247\002\377}\245\002\377|\247\002\377\177\247\001\377{\237\002\377" + "\210\254\002\377{\237\002\377\206\262\005\377z\244\005\377\204\257\016\377\224\275" + "$\377\225\277#\377\206\263\017\377\205\264\013\377y\243\004\377\207\265\033\377" + "\214\270#\377V\213\003\377\\\215\002\377Z}\001\377x\222\021\377]v\003\377^x\001\377" + "Zv\001\377^}\003\377Wu\002\377A`\002\377\"K\002\377<^\002\377Rk\003\377Z\003\377o\237I\377j\223>\377" + "_\210\030\377Hm\003\377Or\007\377Tv\006\377g\207!\377g\205!\377b\177!\377u\206U" + "\377_^O\377pmg\377vsn\377tqj\377\221\211\200\377\210\177x\377h^^\377g\\_" + "\377dYa\377ZRY\377GAD\377]XP\377\200zl\377\270\261\227\377\214\177a\377G" + "\060!\377V?.\377{fR\377\232\211{\377\212yh\377}jU\377\240\217y\377\227\204" + "i\377\205iH\377\201e?\377\210oI\377\206pJ\377\217\200]\377\177oK\377\211" + "{X\377\213\203^\377\200wU\377\215\206d\377\212\202a\377\222\215o\377\220" + "\207n\377\226\212p\377\200oK\377\177hC\377~e=\377\207tO\377\210uT\377\232" + "\211k\377\244\224z\377\257\242\214\377\267\253\232\377\252\235\213\377\231" + "\210v\377\203sf\377scZ\377K\066(\377fN\067\377\246\231\204\377\252\244\223" + "\377\203wk\377ynd\377ka[\377\177{v\377\213\211\207\377xtx\377d^g\377`Zc\377" + "igk\377ddc\377w|t\377bwS\377]\216\066\377My\002\377R\203\005\377Y\215\011\377f" + "\230\005\377|\244\004\377w\235\004\377u\234\002\377p\226\002\377r\231\002\377r\232\001" + "\377j\226\002\377\202\252\002\377\201\252\002\377|\246\001\377u\243\007\377t\246\031" + "\377\211\277U\377\201\255\021\377~\246\002\377n\230\002\377\177\251\004\377\207" + "\251\002\377\250\275\025\377\230\263\016\377\205\240\002\377\221\260\023\377\210" + "\257\010\377\204\251\020\377}\232\006\377\216\250\002\377\254\271\035\377\222\256" + "\003\377\201\242\001\377\206\257\001\377\200\251\001\377z\246\001\377T\203\002\377V\206" + "\006\377[\214\006\377@e\001\377[\207\005\377a\221\004\377Z\203\001\377`\207\006\377S~\002" + "\377Z\202\001\377Op\004\377Vs\002\377Gf\002\377\063S\002\377Dd\002\377Ec\007\377h\243\060" + "\377S\205\000\377Lq\003\377C`\001\377Tq\003\377Lg\001\377o\211'\377{\230F\377drC\377" + "VYE\377yvn\377yvo\377||s\377rph\377\225\221\211\377~vu\377WLS\377ZNW\377" + "nhm\377^[[\377_\\U\377\\XL\377\213\205r\377\250\241\207\377o_J\377M:\"\377" + ">)\023\377{pe\377\222\212z\377kXC\377uaL\377\231\211r\377\243\223y\377\226" + "\201b\377\202jB\377\207pI\377\210wN\377\212|T\377\221\206_\377~tN\377\207" + "\200^\377\203~Z\377}uT\377\215\205j\377\204uX\377\217\200_\377\204qJ\377" + "\210tL\377\204tK\377\242\224t\377\250\232~\377\264\252\226\377\234\215u\377" + "\264\250\225\377\237\222\200\377\221\202n\377\226\210w\377{na\377G\061+\377" + "T?\071\377\236\217}\377\265\253\235\377\233\220\207\377\177rl\377ped\377~" + "wu\377}xy\377c^e\377D;I\377H?L\377c`e\377||z\377lri\377s\201n\377f\213U\377" + "X\177!\377Z\213\010\377X\211\003\377`\216\003\377t\240\002\377\206\253\017\377r\232" + "\002\377x\244\002\377f\215\002\377\202\246\007\377\201\243\002\377|\236\003\377\205\257" + "\002\377\177\245\002\377\200\250\001\377p\237\007\377n\236\015\377\225\304n\377}\246" + "\024\377x\240\002\377y\245\002\377~\247\002\377\201\247\002\377\246\300\022\377\262" + "\265/\377~\230\004\377\236\277&\377}\253\006\377{\246\015\377\201\242\003\377\221" + "\252\001\377\242\250\025\377\226\257\011\377\206\251\002\377\204\257\001\377\203" + "\253\000\377\177\245\000\377a\222\001\377q\242\023\377V\207\002\377M~\002\377L|\003\377" + "Y\216\002\377W\214\002\377Y\217\016\377Y\213\026\377T~\023\377X|\006\377Kf\001\377b" + "\223\015\377En\001\377Z\201\012\377f\223*\377V\213\010\377W\215\003\377Hn\001\377" + "Pp\002\377Qp\004\377Oo\007\377t\223H\377[|\060\377Le%\377HT.\377Y[J\377kl_\377d" + "dZ\377{ws\377\207~|\377vlm\377ZMV\377f[c\377i^f\377\\RT\377kea\377}yn\377" + "xth\377\224\217z\377\251\244\210\377{mR\377cSF\377P>\065\377via\377\206ug" + "\377\202o_\377\207ue\377\245\226\202\377\250\234\201\377\224\204c\377\220" + "}Z\377\205sL\377\212|U\377\212\201[\377\211\201\\\377\212\177]\377\216\205" + "b\377\203vT\377\220\204f\377\206wW\377\223\205b\377\215}Z\377\235\216l\377" + "\237\220m\377\232\215k\377\245\230|\377\243\227\200\377\222\202j\377\221" + "\201j\377~l[\377\214~m\377uf]\377Q:\070\377bN@\377\227\211o\377\246\232\205" + "\377\237\226\205\377\200yn\377\200}v\377vtr\377xtu\377khi\377okn\377a^e\377" + "KIO\377\\]^\377pzo\377n}j\377bwZ\377e\234K\377Hz\004\377V\211\003\377b\226\005" + "\377g\231\002\377k\232\001\377t\244\005\377q\240\002\377t\245\004\377j\227\001\377|\251" + "\003\377|\234\000\377|\237\002\377v\236\002\377z\246\002\377n\232\002\377p\235\001\377" + "y\250\062\377\240\313\213\377z\250%\377\177\252\000\377m\232\002\377\200\246\002" + "\377\213\260\002\377\224\265\003\377\243\267!\377\227\265\036\377w\237\000\377u" + "\236\001\377z\247\002\377\201\252\002\377\204\247\002\377\216\255\016\377\224\266" + "\012\377\211\262\002\377|\244\002\377\235\273\026\377\212\246\001\377b\232\006\377" + "g\233\020\377]\215\002\377X\214\002\377Y\217\004\377^\222\004\377X\211\001\377N|\003\377" + "S\200\007\377a\216\020\377p\221\"\377Ru\002\377Py\002\377e\230\004\377k\226\024\377" + "m\236\035\377e\225\002\377\\\215\001\377V\212\001\377Hr\002\377Jq\003\377`\204,\377" + "Y\177)\377Qq!\377Og#\377EU\040\377bfJ\377ij[\377xvl\377}tp\377\210~|\377n" + "ad\377eW]\377pdl\377S@N\377WJO\377j`^\377rog\377||r\377ywk\377\224\216v\377" + "\236\225v\377{qY\377L<\060\377D\060-\377mZV\377\230\215z\377\216{k\377\215" + "{h\377\235\216z\377\241\224{\377\240\222u\377\224\206e\377\222\203d\377\205" + "vY\377\217\203g\377\217\204g\377\223\210k\377\207y\\\377\224\214q\377\213" + "\203e\377\234\222u\377\215\177]\377\243\225s\377\225\206b\377\212xR\377\204" + "pK\377\205qO\377\222\201i\377\235\220|\377\217\203w\377vi]\377O\071\061\377" + "M/(\377\235\216|\377\256\243\222\377\225\210y\377vk`\377xpk\377spn\377zz" + "z\377utw\377^Y\\\377B;C\377TNU\377baf\377pss\377qys\377m\201l\377_\203R\377" + "X\215\"\377T\205\001\377\\\217\004\377Y\211\003\377b\231\007\377c\227\006\377u\236" + "\020\377q\236\002\377t\242\003\377s\240\002\377p\233\001\377\207\260#\377v\237\002\377" + "i\224\001\377s\241\002\377c\223\002\377p\237\002\377e\226\002\377\222\302z\377\200" + "\264\065\377y\243\000\377\204\250\001\377\220\262\003\377\221\270\001\377\207\260" + "\002\377\207\253\006\377\256\302C\377\204\245\014\377w\233\001\377\204\254\002\377" + "\212\257\002\377\217\255\012\377\211\245\004\377\217\262\001\377|\241\002\377\177" + "\244\004\377\215\242\016\377\214\245\001\377_\227\004\377a\230\004\377]\223\002\377" + "R\206\001\377U\211\002\377]\221\003\377W\204\001\377Ot\002\377Y\200\002\377Z}\002\377U" + "w\001\377Nr\001\377Qw\002\377Ow\000\377a\224\007\377\\\216\002\377s\241\002\377e\224\002" + "\377`\222\002\377X\220\004\377l\233=\377Y\177'\377X}\032\377b\206*\377Zx&\377" + "Xo,\377aoC\377tzd\377vvl\377xso\377|sq\377znn\377|tt\377zps\377jad\377ul" + "g\377H>?\377\\XT\377lja\377oh^\377_TB\377\205}`\377~sR\377\177pV\377n\\L" + "\377YIE\377D\062*\377rdV\377ygV\377\177kX\377\235\220\177\377\236\224|\377" + "\235\220u\377\236\220p\377\206wW\377\207vY\377\207x\\\377\211y\\\377\177" + "lO\377\221\203e\377\223\207f\377\212|[\377\214|Z\377\210wR\377xe:\377wb\066" + "\377\217\177\\\377\230\210l\377\222\207q\377\231\220\200\377i^Y\377QCA\377" + "H\067\061\377\217\177b\377\261\246\221\377\235\221\202\377\215\203x\377\216" + "\205~\377jaa\377zyy\377wxx\377]^a\377C>D\377*\040*\377RQQ\377]_[\377rxv\377" + "{\206|\377s\216l\377b\230G\377Jz\000\377b\230\006\377^\223\003\377\\\221\001\377" + "e\234\016\377h\233\021\377u\241!\377n\233\002\377\177\253\004\377w\246\003\377[\204" + "\000\377v\247\034\377m\232\005\377l\227\004\377p\236\001\377v\250\020\377{\252\002\377" + "o\233\001\377n\241\014\377y\255\022\377u\236\001\377\211\251\002\377\231\263\010\377" + "\244\300\010\377\204\246\000\377\221\260\026\377\215\250\"\377\270\315n\377\201" + "\243\006\377\202\246\002\377\210\250\000\377\232\252\017\377\213\235\001\377\233\273" + "\001\377\211\254\001\377\204\252\002\377v\231\001\377\206\250\002\377e\234\014\377c" + "\231\012\377^\222\002\377Z\214\002\377a\221\003\377b\216\003\377e\212\003\377_\200\002" + "\377Su\002\377[y\002\377Vr\001\377[{\002\377`\210\002\377i\223#\377]\213\016\377Nu\005" + "\377v\241\005\377T\202\002\377\067d\000\377^\212'\377o\234\064\377S\200\000\377v\241" + "\020\377\223\265\022\377\237\301(\377\214\260Q\377g\202:\377`tB\377~\200v\377" + "\201\200|\377\205\202\177\377\210\205\200\377\204\204~\377\214\222\210\377" + "fa`\377?\060\066\377<\061\064\377PIK\377d[[\377phc\377uo`\377rjQ\377\201zZ\377" + "\247\237\204\377\227\211l\377fVC\377SB\065\377\067$\036\377QA;\377dSG\377uh" + "[\377\217\202o\377\214zf\377\240\220w\377\215|[\377\210y[\377\200sX\377}" + "oS\377\200pQ\377weB\377\203sO\377\212{W\377vbC\377\202oQ\377\207uZ\377\232" + "\212r\377\224\204p\377\225\207v\377ti`\377bVT\377L=?\377jZN\377\231\211n" + "\377\261\246\213\377\237\221w\377\215\201k\377\202{j\377ytk\377\204\205\200" + "\377{\177|\377rtt\377YZZ\377QTQ\377\\dU\377bp[\377\\gX\377mzk\377o\210m\377" + "Eu%\377P\207\036\377^\220\001\377[\221\002\377V\213\003\377c\227\002\377c\224\013\377" + "g\227\010\377Z\201\000\377h\220\002\377\202\252\001\377~\251\020\377\201\262/\377" + "\203\261\063\377\202\260\063\377\203\254\025\377\204\261\040\377v\244\021\377" + "y\247\002\377h\227\002\377o\237\003\377|\264;\377}\257\026\377\214\264\002\377\204" + "\246\002\377\225\261\001\377\246\267\002\377\212\245\013\377\233\267<\377\244\302" + "U\377\223\267*\377z\237\000\377\216\257\005\377\246\263$\377\247\274/\377\231" + "\266\000\377\216\254\002\377\203\246\003\377\203\251\003\377\217\263\002\377_\220\001" + "\377Y\212\000\377M~\000\377U\204\000\377V\205\000\377a\217\003\377c\214\002\377_\203" + "\000\377_\204\002\377b\212\003\377X\177\002\377Y\204\003\377d\223\023\377Oy\033\377C" + "o\000\377h\212\036\377~\246\024\377O\200\000\377Z\211)\377Kp&\377U\202\001\377f\225" + "\003\377x\241\003\377\211\254\002\377o\220\010\377\216\260R\377u\226;\377Yv,\377" + "\205\213\203\377\227\236\225\377\221\227\217\377\221\224\217\377\201{\200" + "\377\217\207\211\377cW`\377B\062<\377NAH\377i^e\377\202v~\377\223\214\217" + "\377\206\204z\377yq^\377rkP\377\233\224x\377\246\240\203\377\234\221s\377" + "\213yb\377cQC\377\070&\040\377=+(\377K=:\377RD>\377]J@\377vgX\377m^O\377qe" + "W\377wiX\377\177p[\377\210wa\377\225\206l\377\220\201i\377\227\212t\377\206" + "xf\377{l^\377\211~q\377\207|s\377i[Q\377S?:\377I\066\065\377]LG\377\205vi\377" + "\240\223~\377\255\242\216\377\245\231\204\377\203qX\377\202t_\377vnd\377" + "\203~|\377\221\221\221\377~~\202\377NOT\377MVL\377Q^N\377BP@\377\070D\064\377" + "VfO\377bzU\377Sz?\377H~\032\377P\204\012\377V\213\003\377P\202\003\377W\206\002\377" + "i\225\002\377g\222\012\377a\217\001\377p\232\003\377l\226\002\377p\235\002\377k\225" + "\002\377z\247\007\377p\236\006\377h\235\010\377\207\262\067\377r\244\020\377j\234" + "\003\377\202\261\013\377m\236\003\377s\243\006\377\204\266:\377s\255\061\377\202" + "\253\001\377\201\242\002\377\216\256\002\377\212\250\002\377\256\267\007\377\244\300" + "M\377\215\260\031\377\210\260\040\377~\247\007\377\206\253\013\377\242\273,\377" + "\230\271\037\377\222\264\002\377\211\251\002\377\216\262\004\377\205\250\001\377\207" + "\251\001\377u\235\065\377\236\277v\377\302\333\270\377t\244\066\377\220\266M" + "\377m\225\025\377_\216\006\377\\\217\024\377S\205\005\377\\\221\004\377_\225\003\377" + "Y\211\001\377e\227\031\377En\000\377Ks\001\377U~\002\377n\241\003\377Y\211\030\377O|" + "\017\377W\211\005\377g\231\015\377V\203\001\377]\210\000\377V\177\000\377]\207\033\377" + "y\244<\377h\224-\377c\211\066\377o\212U\377~\205x\377\231\231\227\377\235" + "\233\233\377\233\230\230\377\223\216\214\377qim\377\\QV\377h`b\377ztw\377" + "\211\203\210\377\233\226\224\377\210\202x\377\212\203s\377\200u_\377\207" + "}b\377\233\223t\377\246\240\203\377\242\231{\377\240\221u\377|iX\377eOH\377" + "`MJ\377A*(\377G\060.\377L;\070\377A/*\377WG>\377M\071,\377^L>\377{jZ\377\220" + "\201r\377\207zj\377\202wj\377pe[\377UIB\377WHC\377^LB\377wfW\377q`Q\377}" + "j]\377\243\232\205\377\260\251\226\377\260\250\224\377\235\222~\377\201s" + "]\377\201s`\377\202ym\377\177vo\377\204~{\377\250\247\246\377\221\225\222" + "\377q}n\377PUO\377AEE\377ENE\377:L/\377>?\377>A=\377UjH\377W\177:\377N\203&\377F\210\016\377T\223\025" + "\377t\231\023\377c\216\004\377Q\201\001\377`\214\002\377_\211\002\377c\210\001\377o" + "\233\026\377\201\253\064\377\205\245\012\377e\222\002\377n\235\002\377j\236\014\377" + "l\243\002\377c\225\010\377^\221\005\377r\244\005\377x\247\012\377q\235\001\377w\253" + "\003\377t\251\030\377x\237\013\377\226\270W\377u\235\040\377|\251E\377\217\255" + "\005\377\223\262\003\377\204\244\002\377\203\244\003\377\253\312J\377\222\266\023" + "\377~\244\000\377\211\266\034\377\213\271#\377\207\262\010\377\215\270\007\377" + "\231\274\017\377\233\266\003\377\247\275\011\377\223\253\001\377\233\270\002\377" + "o\257\061\377[\221\000\377d\230\001\377Q\203\002\377Y\223\017\377O\205\002\377Ix\001" + "\377[\214\002\377W\213\002\377V\212\003\377T\205\004\377\\\225\013\377\\\217\016\377" + "S}\003\377Tz\001\377^\216\007\377_\221\005\377U\200\005\377Z\201\020\377o\236\016\377" + "h\227\005\377\207\241\017\377e\206\005\377f\210\002\377p\227\002\377Z}\001\377Y|\012" + "\377\067R\000\377Fb\030\377izN\377t}f\377\231\232\224\377\227\227\217\377\220" + "\217\207\377\202{x\377h]d\377lfl\377lgm\377\231\220\225\377\262\253\247\377" + "\252\244\234\377\221\216\201\377\220\213y\377\211\202j\377|tS\377\216\202" + "`\377~qI\377\213{Y\377\253\243\211\377\263\255\230\377\252\243\211\377\256" + "\244\216\377\241\222}\377\233\212w\377\237\216{\377\250\232\207\377\231\213" + "x\377\225\207u\377\212{m\377\240\222\203\377\247\232\213\377\234\215}\377" + "\245\227\200\377\254\234\202\377\251\230~\377\255\236\206\377\273\255\233" + "\377\277\266\244\377\262\252\226\377\235\225\201\377\224\212u\377\211}e\377" + "\227\216x\377\220\210u\377\224\217\201\377\211\207}\377\217\216\207\377\247" + "\250\242\377\235\252\224\377gw`\377bkT\377ScN\377bv`\377Hc\065\377\030=\000\377" + "+Z\005\377E\203\013\377B}\007\377\\\222\003\377\227\240\011\377X\204\002\377c\215\002" + "\377_\207\002\377a\211\001\377e\224\014\377R\200\003\377\206\256(\377c\214\001\377" + "s\237\001\377k\233\002\377l\243\023\377g\231\010\377d\223\003\377q\242\003\377p\237" + "\002\377\177\260\005\377k\233\002\377k\235\000\377\201\250\006\377w\246\034\377\203" + "\253\066\377\251\273W\377\215\253\037\377\250\267\040\377\215\251\004\377\210" + "\252\002\377\212\263\010\377\214\263\016\377\177\242\001\377\246\273\005\377\217" + "\274B\377\213\271\040\377\214\271\013\377\205\251\001\377\243\276\006\377\230\260" + "\002\377\220\252\003\377\231\263\002\377Z\220\001\377g\233\002\377W\211\002\377Y\216" + "\005\377_\224\003\377b\223\002\377V\210\002\377_\222\003\377^\220\003\377b\235\002\377" + "d\235\003\377]\227\002\377^\230\003\377R\210\003\377[\221\003\377d\231\012\377b\227" + "\006\377g\226\023\377e\227\007\377l\232\011\377\215\253&\377_z\002\377Z}\004\377q\213" + "\003\377p\212\002\377Ts\003\377\065W\002\377Cg\004\377-Q\001\377X\202\004\377_tD\377t|n" + "\377y{r\377\212\211\201\377\222\220\213\377\217\214\210\377XQY\377b[c\377" + "~w\177\377\230\223\223\377\270\263\254\377\237\232\217\377\217\210w\377\224" + "\215x\377\220\212q\377\215\202b\377\215}Z\377\221\203_\377\217\202a\377\227" + "\214p\377\245\234\202\377\253\244\215\377\267\255\230\377\260\244\217\377" + "\264\250\225\377\264\252\230\377\262\247\226\377\253\240\217\377\254\241" + "\222\377\251\235\216\377\232\213z\377\262\245\223\377\262\244\215\377\255" + "\235\205\377\264\242\211\377\247\223z\377\267\250\225\377\272\255\233\377" + "\232\215v\377\221\207r\377\231\222~\377\215\206p\377\223\215y\377\231\223" + "\204\377\221\220\205\377\213\213\203\377\211\211\200\377\241\246\226\377" + "\201\217u\377|\211d\377\201\177U\377Q[I\377)E\035\377.T\040\377p\227Z\377J" + "\200\021\377=p\002\377P\210\003\377[\222\003\377o\212\016\377c\203\003\377b\210\002\377" + "h\224\002\377e\222\001\377R\202\001\377M\200\002\377\204\254/\377b\212\000\377q\237" + "\002\377i\236\013\377p\247\023\377j\235\005\377s\241\011\377u\243\001\377t\246\002\377" + "q\242\003\377h\232\002\377m\236\004\377|\247\003\377n\245\024\377\210\266G\377\260" + "\313P\377o\234\010\377\234\263\022\377\250\265!\377\222\256\017\377\212\261" + "\024\377\221\271'\377\203\245\001\377\236\243\003\377\221\271\060\377\216\272:" + "\377\223\300\017\377\222\272\004\377\220\256\002\377\213\247\002\377\221\255\002\377" + "\222\257\002\377_\223\002\377m\235\001\377g\230\010\377p\230\011\377n\221\012\377" + "z\234\033\377_\213\005\377W\206\003\377\\\220\002\377^\225\002\377c\234\011\377Z\210" + "\001\377j\226\026\377s\244'\377^\220\026\377V\204\014\377_\217\002\377]\206\001\377" + "o\227\002\377{\230\023\377\205\243\030\377Mp\001\377Ln\002\377f\202\002\377m\206\002" + "\377Uo\002\377\065J\001\377;T\002\377Ff\003\377?c\001\377If\040\377Sa@\377ei^\377\177" + "~v\377\220\221\204\377\201\201w\377gfc\377cac\377a]a\377\233\227\225\377" + "\252\245\235\377\223\214\201\377\224\220\203\377\215\213{\377\231\226\205" + "\377\212\205j\377\206\200]\377\216\205c\377\217\205g\377\222\206j\377\204" + "uU\377\217\201a\377\241\225x\377\260\244\211\377\250\235\202\377\235\220" + "w\377\252\240\207\377\237\224\177\377\260\247\224\377\227\211u\377\224\203" + "o\377\247\233\206\377\266\252\223\377\250\231\201\377\260\243\214\377\254" + "\237\210\377\247\232\204\377\250\233\205\377\230\213u\377\221\205r\377\212" + "\201o\377\235\227\207\377\234\233\216\377\232\232\221\377\202\202|\377\207" + "\207\177\377\177\202t\377\216\241w\377\177\217d\377r\177^\377eoc\377G\\>" + "\377&K\015\377)V\003\377Fy\021\377n\232H\377J\201\014\377d\235\002\377[\217\020\377" + "x\241=\377o\217\012\377o\220\005\377d\222\003\377_\217\001\377Z\215\003\377Y\212\002" + "\377t\241#\377p\235\017\377p\243\010\377k\234\015\377o\243\010\377f\223\005\377" + "p\236\005\377w\245\002\377}\254\002\377{\255\004\377n\237\002\377\\\217\000\377\205\256" + "\000\377f\241\017\377\210\270G\377\224\273(\377x\243\002\377u\245\013\377\202\244" + "\000\377\225\257#\377\224\263#\377\223\271\063\377\203\250\001\377\222\246\004\377" + "\200\246\006\377\206\256+\377\207\262\011\377\204\251\001\377\217\264\002\377\214" + "\256\002\377\214\264\007\377\215\261\006\377a\225\003\377e\230\002\377Y\220\004\377U" + "\213\002\377U\205\001\377`\215\001\377Sq\001\377]\211\002\377^\216\003\377]\222\003\377" + "_\220\030\377\212\264T\377k\234\025\377`\222\001\377U\203\001\377Fp\001\377b\217" + "\002\377S|\003\377[\210\005\377\223\257>\377f\217\006\377W\201\002\377Z~\005\377i\223" + "\002\377Yw\003\377Xi\001\377do\007\377Vg\004\377Rm\006\377Km\011\377:\\\000\377Qr\016\377" + "\067H\040\377]kK\377|\206l\377\214\216\200\377\227\230\217\377kih\377ead\377" + "\221\213\213\377\205{w\377\216\204{\377\214\203y\377\206\177t\377\217\214" + "|\377\217\214u\377\222\215o\377\200zX\377\203~]\377\226\215k\377\204vP\377" + "\216~Z\377\216\200]\377\230\213h\377\230\215j\377\241\230u\377\234\222t\377" + "\242\227}\377\245\233\207\377\222\205s\377\226\212t\377\250\236\211\377\256" + "\241\213\377\253\235\205\377\253\235\205\377\227\213u\377\240\224\202\377" + "\207\177m\377\214\200o\377\215\205t\377\245\241\220\377\227\226\211\377\216" + "\217\205\377\213\213\203\377\177~y\377}}t\377\220\236x\377y\221V\377^qK\377" + "Q^K\377L]D\377.N\024\377+Q\004\377@m\005\377M\203\011\377?s\001\377\202\260R\377" + "a\232\001\377Bm\025\377\203\255O\377k\217\035\377X\177\006\377O\177\001\377_\216" + "\001\377c\225\002\377V\202\002\377V\202\000\377\231\302J\377j\231\010\377i\230\002\377" + "t\246\002\377w\245\002\377t\243\003\377l\232\002\377\200\261\003\377l\236\003\377l\241" + "\001\377W\217\017\377\222\270&\377r\246\024\377\215\276Y\377\224\273,\377\201" + "\246\000\377}\247\002\377\200\255\002\377\177\245\002\377\200\246\005\377\242\302G" + "\377\177\251\006\377w\235\001\377\220\261#\377\220\256.\377\206\261.\377v\237" + "\000\377\212\265\001\377\205\253\002\377\224\271\005\377\243\305#\377O\204\001\377" + "Z\217\003\377Z\221\003\377P\210\002\377Z\224\004\377W\213\003\377\\\215\000\377e\226" + "\007\377d\226\005\377Vx\005\377t\222\024\377c\217\006\377\202\240>\377a\205\010\377" + "_\207\004\377U~\002\377f\227\006\377t\251\036\377\204\261#\377\202\252'\377q\235" + "\010\377^\214\001\377a\214\015\377\\\212\000\377T~\001\377]\205\003\377_\206\002\377" + "[|\002\377Xw\004\377Ss\004\377Db\001\377;Z\001\377\063P\003\377p\224D\377`fS\377tvh\377" + "~\177t\377d^]\377tps\377uoq\377\240\230\223\377\254\245\234\377\243\236\222" + "\377\230\222\205\377{yk\377\204\203r\377\215\213r\377\217\212k\377\211\202" + "`\377\207\200[\377\212\203]\377\227\217j\377\230\220l\377\242\232{\377\223" + "\211j\377\232\224u\377\247\240\204\377\223\215s\377\234\224\177\377\206y" + "f\377\220\203p\377\226\212t\377\244\230\177\377\254\240\207\377\247\234\206" + "\377\222\207u\377~ua\377\226\221\200\377\234\232\213\377\230\230\212\377" + "\230\231\216\377\206\206}\377\226\225\217\377\212\212\204\377\177~y\377\201" + "\201z\377\215\244h\377k\177Q\377ERA\377/B&\377\062M\031\377,N\007\377Af\007\377" + "Fr\002\377V\214\007\377F~\003\377_\237\002\377d\230,\377[\204\023\377s\235D\377Mv" + "\000\377p\216-\377Ft\000\377]\213\002\377Y\211\001\377W\207\003\377`\222\002\377\226" + "\300H\377c\215\007\377h\227\001\377i\232\003\377u\244\002\377g\230\002\377i\230\002\377" + "x\254\002\377m\235\015\377b\231\000\377{\257<\377\234\306h\377\213\266\063\377" + "\217\272\061\377\206\260\022\377\212\257\031\377\244\300=\377~\251\035\377\216" + "\261\006\377\201\244\000\377\255\310N\377\317\326\217\377n\222\000\377\220\244" + "'\377\246\276M\377r\240\006\377\211\256\023\377\223\264\002\377\206\246\002\377" + "\214\257\001\377\227\273\030\377h\236\013\377P\206\001\377W\215\003\377_\226\003\377" + "T\212\002\377^\226\000\377q\243\034\377q\245\037\377\\\216\001\377}\223\027\377\257" + "\257F\377Z\203\001\377\\\212\017\377Rz\002\377d\217\015\377c\215\007\377s\244\003\377" + "q\234\002\377\233\272\066\377h\206\001\377l\220\003\377V}\001\377o\233\004\377m\232" + "\016\377h\216\013\377b\203\001\377]|\002\377Zw\002\377Vr\002\377Ee\002\377?a\002\377Cb" + "\002\377[~\004\377Lk\004\377!\063\006\377?K,\377prd\377\214\213\204\377\212\213\204" + "\377\210\203\201\377\232\223\224\377\225\222\220\377\235\234\226\377\203" + "\201x\377|{r\377ccZ\377\214\214|\377\221\216y\377\220\214q\377\224\217q\377" + "\206~\\\377\214\204`\377\222\214j\377\251\245\214\377\223\220t\377\222\220" + "s\377\217\214p\377\222\213n\377\225\211n\377\210y`\377\203r[\377\216~h\377" + "\245\232\200\377\240\230\177\377\232\223\177\377\230\221\200\377\240\233" + "\212\377\243\243\227\377\224\224\211\377\214\215\203\377yyr\377lkh\377on" + "o\377xxw\377YYY\377x\177h\377p\213N\377DND\377ES?\377)C\030\377+M\012\377?" + "k\017\377/Z\002\377=l\002\377M\202\010\377G\201\002\377\\\226\004\377Bl\022\377{\253" + "F\377y\244H\377Cn\000\377\245\274m\377d\222\017\377b\225\003\377^\222\012\377^" + "\226\020\377\\\222\002\377n\232\026\377\201\255'\377_\221\001\377l\236\002\377d\227" + "\003\377]\220\002\377o\242\002\377o\242\006\377\214\257G\377\223\277]\377i\235\014" + "\377\\\216\003\377~\262\030\377p\230\004\377\204\263\035\377\212\273L\377\227\305" + "h\377\231\304r\377\265\327\215\377\240\307F\377\221\267\065\377\245\272\071" + "\377\223\251,\377\250\272I\377\271\313`\377\276\320`\377\233\271/\377\211" + "\245\001\377x\226\002\377\212\254\001\377\242\301)\377V\210\003\377V\211\003\377c\223" + "\004\377\\\225\004\377]\223\005\377Y\217\000\377`\222\004\377e\224\001\377d\216\004\377" + "n\223\003\377h\214\002\377n\232\002\377h\231\003\377u\247\040\377\216\274J\377\201" + "\256\015\377}\244\002\377\224\264\022\377\224\261A\377Z\201\000\377a\215\002\377" + "p\232\012\377u\227\004\377w\222\003\377l\201\002\377l\200\002\377_p\002\377`p\001\377" + "J_\002\377So\003\377+C\000\377Jp\013\377z\237\036\377<]\002\377\031\065\000\377Oi%\377" + "dv?\377np_\377kjb\377}wz\377xmu\377ndn\377qfn\377^SZ\377>\070<\377>\071=\377" + "efc\377]^V\377\207\210t\377\227\225z\377\206\202`\377\213\210d\377\216\211" + "g\377\230\227|\377\177\207]\377\236\235\177\377\226\224v\377\234\226y\377" + "\201uX\377\207z\\\377\222\207j\377\237\230}\377\230\222{\377\233\225\201" + "\377\235\232\206\377\231\230\205\377\231\231\212\377\226\227\213\377\231" + "\232\220\377wwr\377^]]\377>==\377B?A\377JFI\377B>B\377\231\245v\377]bQ\377" + "-<\"\377(A\025\377(I\011\377\062Y\017\377*S\001\377\063d\006\377\067k\005\377Cx\002\377" + "U\224\005\377Cl\002\377Nt\000\377o\233+\377y\245M\377Q\203\000\377Y\212\024\377\214" + "\266Q\377S\205\002\377R\202\002\377S\201\002\377b\222\002\377e\226\015\377{\252&\377" + "j\236\001\377k\235\001\377`\217\003\377i\227\000\377v\246\001\377s\250\026\377s\247" + "\062\377f\233\015\377p\237\003\377m\232\005\377y\255\040\377v\232\003\377t\245\012" + "\377o\236\000\377\225\273?\377\300\341\224\377\215\271<\377\204\260\027\377" + "\204\263)\377\212\261(\377\254\306]\377\247\264?\377\214\257\030\377\207\256" + "\023\377\232\303B\377\217\267(\377u\217\001\377\202\236\002\377\236\301/\377`" + "\221\003\377\\\221\003\377\\\227\004\377`\231\005\377n\237\012\377\200\245\035\377" + "g\227\001\377Y\215\002\377X\212\001\377q\231\004\377p\227\002\377f\224\002\377g\231\002" + "\377u\251\"\377f\224\005\377m\230\001\377u\236\001\377s\233\006\377b\221\004\377a\221" + "\004\377a\212\003\377m\217\006\377k\212\002\377l\210\001\377x\225\003\377j\212\002\377" + "a\203\002\377a\201\003\377`\201\007\377h\216\007\377\\\203\011\377o\244\026\377Lk\012" + "\377Hf\020\377Xy\034\377" + "\071;\377\063.\060\377zx[\377\210\230h\377XdV\377\064K*\377\036@\010\377-V\016\377" + "\037L\002\377,\\\005\377:m\007\377F{\004\377X\204\012\377`\215\021\377Lu\010\377`\207" + "\013\377U\202\024\377~\261C\377U\210\000\377U\204\000\377\210\270G\377Z\213\004\377" + "Q\204\001\377c\226\003\377W\205\002\377`\215\012\377|\252\032\377l\242\002\377[\215" + "\002\377g\232\007\377\207\264\064\377|\254\012\377h\237\014\377`\227\020\377r\245" + "\004\377}\246\001\377z\252\037\377p\244(\377\202\235\001\377\204\264\060\377\242" + "\312^\377\260\323k\377\200\243\020\377v\244\004\377\214\275@\377\230\303T\377" + "\255\320g\377\204\261.\377\240\274\040\377\210\255\020\377\235\253\040\377\224" + "\273@\377\230\274G\377\225\264\021\377\217\260\002\377\227\302\070\377V\206\003" + "\377Y\223\011\377O\216\002\377W\222\002\377U\220\003\377Q\212\003\377U\214\002\377Z" + "\220\001\377\\\221\002\377^\222\001\377n\236\006\377t\247\024\377[\211\001\377i\233" + "\015\377g\224\002\377h\217\002\377b\206\000\377}\240\022\377\224\267\011\377r\237" + "\001\377j\226\002\377r\237\003\377i\221\002\377l\227\002\377q\225\002\377k\203\003\377" + "`l\002\377eo\002\377{\230\002\377u\235\002\377Sl\010\377l\217\034\377o\213,\377Ei\005" + "\377?`\001\377Np\022\377Pc\037\377!\060\006\377FN\071\377Z[S\377XUS\377skk\377\211" + "\203\200\377ka]\377H\071\061\377TF>\377L?\071\377_VO\377e^R\377uo\\\377\211" + "\210k\377\211\215g\377{\204S\377\210\212a\377~|V\377\222\222m\377\224\221" + "m\377\177|T\377\203\177W\377\233\232x\377\213\211k\377\220\220v\377\221\222" + "~\377\222\227\200\377\227\240\204\377\210\213x\377\226\226\211\377\233\234" + "\223\377\216\214\205\377zri\377tk_\377zqe\377~yn\377kh]\377\224\245r\377" + "ViB\377\067O\060\377>Y,\377\064T\024\377\032C\002\377\037Q\001\377;s\020\377A{\006\377" + "X\213\020\377p\231#\377Nz\031\377M\204\015\377I{\000\377b\226\031\377v\253\062\377" + "_\220\002\377W\201\001\377t\245\023\377^\220\004\377`\224\005\377K}\003\377U\203\002\377" + "m\235\011\377\203\262%\377c\224\000\377[\214\000\377i\233\006\377j\227\004\377t\247" + "\002\377i\240\012\377c\226\002\377\206\260\004\377~\246\012\377l\236\017\377i\242" + "/\377\226\276K\377\221\272I\377\201\256&\377\212\237\020\377\211\241\005\377" + "o\230\005\377\235\275O\377\221\257:\377\206\252!\377}\252\022\377\214\242\006" + "\377\203\236\004\377\213\254\011\377\325\336q\377\272\311>\377\224\260\005\377" + "\221\260\002\377\225\300=\377Bx\002\377T\217\004\377S\214\002\377U\215\002\377Y\221" + "\003\377]\223\002\377X\213\002\377p\236\027\377p\233\010\377f\225\003\377b\221\007\377" + "a\220\002\377\\\212\002\377W\205\001\377f\220\002\377X\202\002\377_\211\002\377\210\256" + "\005\377\221\263\002\377l\222\002\377r\232\003\377v\236\001\377z\245\002\377q\216\003\377" + "k\200\002\377hz\004\377jw\002\377x\206\005\377{\225\001\377g\210\011\377m\230\024\377" + "q\226\040\377Zl\016\377Tl\012\377Uy\012\377Oi\034\377\040.\000\377\061<\025\377js[" + "\377\202\204u\377{zn\377qh_\377}tm\377\230\220\200\377\225\210p\377\230\210" + "m\377\230\211l\377\213|c\377\202u]\377|w\\\377tuO\377v\200N\377\206\214b" + "\377\216\220n\377~}W\377\203\201Z\377\213\212b\377\203\201R\377\212\210^" + "\377\224\223q\377\214\214l\377\213\213n\377\201\201j\377\223\226\200\377" + "\227\236\201\377\224\232\200\377\225\226\211\377\230\231\220\377\211\206" + "|\377\216\206y\377\226\216|\377\235\227\206\377\214\211|\377\201\205o\377" + "\212\262j\377BY\064\377&?\031\377+K\021\377$H\010\377\"J\002\377J\001\377x\005\377;p\001\377e\230\027\377Z\217\003\377Q\204\001\377" + "Hz\002\377I{\001\377Z\212\002\377a\224\022\377l\226\021\377n\225\004\377`\212\002\377" + "l\227\003\377p\230\004\377i\231\007\377j\227\014\377\205\265H\377o\240\031\377\211" + "\265/\377c\227\014\377Z\211\002\377m\232\004\377l\231\002\377\220\257\003\377z\251" + "\002\377u\236\026\377q\240\035\377\212\271F\377s\242\032\377h\230\001\377q\240\001" + "\377\251\315\065\377n\237\016\377u\244\006\377k\230\003\377\204\251\001\377\230\262" + "\017\377u\242\016\377\225\270\066\377v\247+\377\230\271*\377\246\276#\377\245" + "\270\013\377\240\252\001\377\241\274\014\377\215\264\007\377\207\260\000\377\202" + "\257\003\377\\\233\003\377T\221\004\377O\206\003\377T\200\003\377Ao\001\377c\225\004\377" + "c\226\004\377d\221\005\377d\224\002\377X\205\002\377O|\001\377s\232\033\377g\221\012" + "\377a\211\001\377^\212\002\377`\212\001\377i\223\002\377}\247\004\377\205\244\004\377" + "z\224\003\377p\214\003\377h\205\001\377\221\234\023\377v\212\004\377z\231\002\377s\234" + "\005\377}\234B\377[x\011\377e\225\006\377Z\205\003\377[\210\007\377h\220\020\377_\203" + "\005\377Qo\002\377Sn\001\377^{\005\377H_\002\377:M\001\377FU\003\377\066F\006\377aj@\377z" + "{k\377\177\201t\377{zr\377gdW\377\201xe\377\223\214i\377\234\232e\377\237" + "\234_\377\236\217a\377\226\201[\377\203qL\377q`@\377wlO\377\202|_\377\205" + "\201b\377|yQ\377\201\200Y\377\220\220j\377\217\216i\377\221\220r\377\211" + "\211m\377\217\221|\377\221\222\204\377\224\226\211\377\222\236|\377ztn\377" + "qj_\377nf[\377WNI\377JFF\377B@B\377ivU\377^\205D\377}\253R\377Iv#\377\062" + "\\\016\377Kx\030\377\065f\005\377/e\004\377\066l\002\377N\201\006\377]\220\003\377P\203" + "\002\377Q\201\003\377[\216\002\377\\\220\002\377F{\001\377]\225\023\377_\221\012\377" + "_\217\004\377Fv\001\377t\235\003\377]\206\002\377^\221\015\377\216\274R\377n\235\004" + "\377T\200\002\377\212\271\067\377a\227\010\377m\227\010\377l\227\002\377\240\264" + "\014\377r\231\002\377p\243\012\377`\225\011\377t\247\040\377p\231\014\377\177\251" + "\002\377n\234\003\377e\226\001\377\230\300\040\377\221\271\064\377k\230\004\377k\230" + "\003\377\177\244\002\377\240\270!\377\200\247\016\377\226\272'\377a\224\004\377" + "\202\262\023\377\223\273\033\377\245\300\"\377\233\257\007\377\221\252\001\377" + "\213\260\013\377\210\265\022\377\272\320H\377L\210\001\377T\217\003\377L\205\002" + "\377R\213\005\377[\224\004\377b\223\006\377`\205\002\377o\222\004\377g\215\000\377X\203" + "\000\377]\207\010\377[\177\001\377e\215\005\377f\217\016\377i\223\012\377W\202\001\377" + "]\205\001\377x\241\013\377g\215\004\377j\212\003\377o\213\002\377n\215\002\377i\212" + "\004\377~\234\010\377\204\250\013\377|\241\"\377x\222\027\377]\205\003\377g\225" + "\002\377\\\201\001\377Ot\003\377^\213\023\377^\214\012\377^\204\010\377f\206\015\377" + "Xr\002\377Pi\003\377Ga\002\377G_\001\377?W\003\377F[\021\377fnH\377rsb\377ii]\377kk" + "`\377[[I\377s\200E\377\224\221e\377\241\224o\377\203pP\377nY\070\377ub<\377" + "ziC\377yjH\377{pS\377yqT\377{vV\377|yU\377\202\177]\377\214\214k\377\215" + "\216o\377\214\215t\377\225\226\204\377\213\213\200\377~}t\377\202\220f\377" + "pmZ\377icZ\377]ZT\377JGF\377KJJ\377RRO\377r\212U\377ZoE\377t\244O\377Gv\036" + "\377W\214&\377?u\024\377\067p\005\377M\212\004\377Q\210\004\377T\212\002\377W\212\002" + "\377Dw\002\377O|\002\377Y\212\003\377V\211\003\377J~\002\377X\224\020\377N\202\007\377" + "L}\003\377o\230\005\377o\233\004\377e\222\004\377s\242'\377m\230\016\377t\236\004\377" + "h\220\012\377\207\265\062\377Y\205\000\377o\222\005\377\226\255\007\377o\217\003\377" + "l\236\005\377s\247\063\377f\232\030\377d\230\007\377v\236\001\377z\240\003\377u\240" + "\005\377s\241\003\377\212\270\013\377\200\254\026\377\\\212\002\377q\234\001\377\177" + "\246\002\377\214\261\005\377\177\250\006\377l\234\005\377h\233\002\377}\257\015\377" + "\223\274\"\377\246\273$\377\273\311R\377\236\264\023\377\216\261\011\377\203" + "\257\027\377\216\261,\377H~\002\377M\205\004\377L~\003\377G\200\002\377j\246\015\377" + "`\231\015\377h\241\023\377|\254*\377\213\267A\377\205\265/\377\202\253\065\377" + "f\220\004\377j\224\004\377O|\002\377[\211\013\377X\207\001\377e\204\020\377\202\256" + "\004\377Y\200\002\377Y~\002\377_\204\003\377V{\002\377_\202\003\377\200\245\004\377t\234" + "\022\377~\222\032\377w\225\014\377e\216\003\377_\215\004\377\\\214\013\377S\202\003" + "\377Jo\001\377W\203\017\377o\240'\377^\202\002\377]|\003\377_\202\002\377Uz\003\377" + "X\177\005\377Oo\002\377Da\003\377DZ\016\377S[\060\377__H\377fiQ\377Yt\061\377ZXG\377" + "tlY\377iYD\377qcE\377{lG\377\221\201U\377\233\211^\377\202oG\377\177mL\377" + "m_B\377kbE\377\200z_\377{uX\377\202~a\377\222\220y\377\213\212v\377\207\207" + "y\377{yn\377eaX\377ekP\377x\205X\377\\UN\377eb[\377ec_\377`^W\377`bQ\377" + "q\215T\377w\233Y\377s\247C\377Ar\031\377\067i\020\377\066m\007\377D\201\005\377I" + "\205\002\377Bz\002\377C}\001\377Av\002\377\067l\004\377a\227\010\377L\202\003\377I}\001\377" + "K\201\001\377]\232\016\377>u\005\377k\230\005\377^\211\002\377T\205\003\377c\226\022" + "\377x\243\061\377r\232\006\377\207\250\003\377j\213\004\377\220\267\066\377b\215" + "\000\377\200\235\004\377\252\270\005\377f\224\002\377n\244\031\377\\\220\025\377v\253" + "\060\377u\241\001\377\201\245\005\377{\235\002\377z\242\003\377z\254\006\377\205\262" + "\002\377k\232\002\377`\222\003\377q\237\005\377\200\252\003\377\201\251\005\377}\252" + "\005\377i\235\006\377i\237\004\377w\242\004\377\255\311V\377\311\322a\377\256\304" + "@\377\234\271!\377\220\262\032\377\202\261$\377t\237\023\377v\244\030\377J~" + "\004\377Av\002\377;v\004\377\\\217\015\377v\252\"\377\232\307h\377\234\307k\377" + "\241\314v\377k\230.\377N|\000\377t\237\022\377f\226\007\377e\226\003\377b\230\003" + "\377]\222\002\377`\223\002\377\231\275\004\377r\222\004\377h\206\002\377l\211\002\377" + "s\221\003\377~\237\004\377o\231\017\377\205\247\037\377d\220\007\377g\224\014\377" + "h\225\004\377]\215\002\377[\211\016\377\\\211\013\377Rw\002\377Qy\005\377\201\253N" + "\377b\213\027\377f\210\024\377a\204\013\377Xv\001\377[y\004\377Ul\003\377GZ\001\377" + "HZ\007\377GV\022\377w\203H\377~\210Q\377qpX\377ZVI\377`YJ\377nfL\377\221\207" + "^\377\237\222e\377\222|P\377\222yN\377\217uM\377\217wS\377}jL\377aR<\377" + "rkX\377wr[\377\204~i\377\213\206u\377\205\200t\377yri\377[QJ\377KC\070\377" + "GD\066\377\205\227O\377wo_\377yrc\377yuh\377oma\377x\207^\377a\211=\377\204" + "\256[\377Ai\025\377\065\\\023\377.\\\003\377P\210\003\377Dv\002\377F{\010\377Av\005\377" + "N\202\002\377\061j\002\377L\201\003\377O\211\003\377O\210\002\377N\202\003\377K\201\002" + "\377Y\221\007\377t\240\006\377g\221\002\377X\204\001\377^\220\002\377q\251/\377e\232" + "\021\377x\236\004\377q\216\002\377l\207\014\377\221\272A\377c\215\000\377\253\267" + "\011\377~\232\002\377q\241\000\377x\252,\377y\252\031\377{\255\"\377\224\270\010" + "\377y\236\011\377\224\257\006\377\177\246\006\377\210\265\002\377\200\253\002\377" + "m\233\010\377_\222\002\377b\222\004\377\177\247\013\377\222\271'\377\206\256\036" + "\377q\236\003\377\243\270\027\377\232\260!\377\215\260.\377\200\244\022\377\233" + "\272&\377\216\256\025\377\212\253\024\377\243\304Y\377\203\257\"\377[\217\002" + "\377N\177\002\377I|\002\377Q\204\004\377L{\002\377o\235!\377x\250\061\377\234\304" + "i\377p\226-\377Z\206\002\377\\\205\005\377^\210\005\377m\211\004\377X{\003\377^\212" + "\005\377O\200\002\377X\221\014\377\242\305\002\377\202\240\026\377q\217\001\377z\231" + "\002\377u\230\003\377h\221\003\377\\\211\004\377s\246\033\377d\233\016\377`\226\011" + "\377v\244\035\377a\225\021\377e\222\036\377d\213!\377c\205\020\377]|\003\377k\217" + "\037\377s\227\065\377n\223/\377n\227&\377a\204\017\377^\200\014\377Yr\021\377" + "du\031\377@N\004\377Md\007\377r}\071\377\224\212h\377\205~b\377zx`\377xx^\377s" + "pW\377zsW\377qbF\377w_?\377\231\200^\377\242\211f\377\234\202a\377\216v[" + "\377XH;\377G<\061\377geX\377spd\377zsi\377|si\377wka\377fYK\377dXG\377woT" + "\377\203\235+\377vzS\377qsT\377so_\377xtf\377\213\242m\377W\204%\377w\251" + "K\377Ds\030\377Ar\016\377Z\217\004\377M|\001\377\070g\011\377Y\213\033\377L\177\002" + "\377I\177\002\377M\201\003\377F}\001\377W\215\003\377P\210\002\377_\220\002\377Z\204" + "\002\377s\240\005\377b\213\003\377f\216\003\377a\215\003\377[\215\002\377\\\231\025\377" + "e\234\017\377\222\260\011\377j\222\014\377c\214\003\377|\255\060\377\224\252\007" + "\377~\245\004\377m\233\002\377f\225\000\377\203\264\061\377|\253\013\377|\251\005\377" + "\202\252\002\377\204\245\002\377\241\270\005\377v\247\003\377\201\255\003\377\203\256" + "\002\377x\234\005\377i\224\007\377|\247\015\377}\244\003\377\205\255\000\377\210\261" + "\000\377\216\257\013\377z\236\004\377v\245\040\377\203\252\012\377\203\247\007\377" + "\252\300\031\377\215\252\004\377\230\263&\377\271\320w\377r\237\000\377`\205\014" + "\377_\205\003\377\\\200\003\377Z|\004\377Rw\003\377V|\003\377t\221\005\377]\201\007\377" + "e\212\004\377m\223\002\377e\215\010\377[\207\002\377e\213\006\377j\214\005\377r\232" + "\003\377W\203\003\377Y\202\002\377\246\310\007\377\202\255(\377r\236\016\377k\227" + "\002\377f\227\000\377k\233\012\377`\224\003\377`\221\014\377`\216\005\377i\225\010\377" + "`\213\006\377u\231\015\377\\\205\014\377l\240\067\377j\245E\377b\230=\377^\223" + "-\377\\\215\027\377Q\177\020\377Y\214\016\377X\177\020\377=V\002\377@P\001\377We" + "\002\377e\210\010\377fx\030\377qp\070\377\202}U\377\225\220k\377\220\213i\377" + "\204~c\377xk[\377|n]\377\212yb\377\244\217n\377\253\224r\377\242\213h\377" + "\240\207h\377\222zc\377PH=\377\027\022\020\377<\067\070\377RLE\377maU\377\203" + "vc\377vdO\377mWD\377]I:\377TB\066\377}\214=\377qrP\377am\071\377o\177E\377" + "h}H\377d\221>\377Bf\023\377\204\263R\377P\207\026\377;g\002\377J~\005\377o\001\377K\177\001\377J\200\004\377H~\003\377F~\001\377M\203\003\377]\220\003" + "\377c\222\003\377}\251\002\377g\233\031\377R\201\006\377U\200\003\377i\221\003\377t" + "\247\001\377l\232'\377n\240\023\377\226\267\004\377f\237\022\377P\207\007\377s\242" + "\017\377\241\254\013\377u\240\004\377n\232\002\377i\225\005\377\177\262\016\377w\251" + "\011\377w\247\004\377l\226\003\377\224\264\003\377\201\250\003\377w\246\003\377u\244" + "\003\377|\251\004\377d\216\007\377i\223\007\377\207\255\002\377\225\275\002\377\200\260" + "\013\377v\252\017\377k\237\032\377k\236\003\377{\255;\377\203\243\000\377\202\254" + "\021\377\211\255\006\377\221\261\013\377\246\303N\377n\222\005\377\207\250\012\377" + "q\212&\377Hh\002\377Zz\003\377Hr\003\377Uy\002\377Xy\003\377Gc\002\377i\215\003\377a\207" + "\003\377_}\003\377U\200\002\377U\202\006\377i\220\003\377k\212\002\377\202\245\002\377" + "j\211\004\377j\223\010\377\230\276\011\377j\224\010\377p\241\001\377e\227\007\377" + "w\250\065\377s\236\067\377k\225\033\377d\220\012\377c\205\003\377i\212\002\377q\222" + "\006\377f\217\003\377U~\003\377[\212\010\377Z\215\021\377c\230!\377b\235$\377Y\226" + "\030\377W\222\030\377N\206\012\377Y\220\020\377d\224\016\377f\212\016\377h\214" + "\020\377cz\024\377Wj\013\377Zk\026\377\233\234m\377\236\227x\377\225\207s\377" + "\241\220\201\377\236\215~\377\236\216}\377\226\205o\377\215y_\377\231\202" + "e\377\251\224s\377\245\220r\377\224}g\377OJC\377\011\007\005\377'\035\032\377\\" + "K?\377nXG\377x_J\377u[G\377zeU\377wh\\\377WJ?\377}\210T\377PY/\377DR$\377" + "Zt<\377X\213\062\377S\212+\377Z\204\063\377n\240\062\377Ct\007\377Y\221\022\377" + "\377w`<\377" + "n]\061\377hc*\377n{\065\377^w!\377u\224)\377W\202\020\377Jz\004\377T\216\010\377" + "V\215\024\377Z\227\033\377i\243\062\377f\242+\377`\230\034\377V\215\010\377W\214" + "\001\377M\204\005\377H\200\006\377O\203\003\377L\177\002\377Y\210\006\377U|\003\377u\243" + "\036\377S\213\003\377Q\204\002\377Q~\006\377y\242.\377\211\265D\377[\221\006\377n" + "\224\005\377\204\251\013\377\202\256)\377g\230\002\377q\245\005\377\201\250\004\377" + "\210\253\002\377`\217\002\377p\245\012\377\202\255\002\377z\246\001\377o\236\003\377" + "`\221\002\377\254\270$\377\201\240(\377\241\274\017\377\231\262\004\377\205\250" + "\003\377r\233\004\377f\215\001\377i\221\003\377{\242\006\377s\232\005\377t\233\002\377" + "}\245\013\377{\242\022\377p\233\014\377|\250\002\377o\230\000\377x\236!\377|\233" + "\010\377\213\254\023\377\224\276D\377\200\246\000\377\231\301Z\377\200\256\061" + "\377\227\270\025\377\203\237\004\377Pk\002\377V|\003\377=c\001\377Sp\002\377Zk\002\377" + "Nc\002\377]z\002\377j\202\003\377z\243\003\377o\224\005\377Z\220\012\377O\212\005\377" + "{\253\003\377t\246\002\377_\217\004\377]\213\002\377\232\277\002\377u\240\025\377[\211" + "\023\377e\227\013\377e\225\034\377t\243\037\377e\235\017\377m\237\"\377\206\260" + "N\377~\254C\377c\226\021\377m\230\025\377e\230\002\377_\220\003\377a\224\004\377" + "n\237\010\377m\231\022\377\221\262\067\377\260\303\064\377\254\275=\377c\231" + "\021\377Z\225\024\377a\236\026\377Z\223\007\377_\230\012\377W\220\001\377V\216\002" + "\377U\211\002\377b\217\011\377q\230\026\377g\221\015\377_\212\017\377b\205\034\377" + "i\202)\377|\214J\377\212\220c\377\223\225o\377\213\213c\377\213\201a\377" + "\215\177c\377W@\070\377zdR\377\250\225q\377}nC\377\207yL\377\205zG\377zx\071" + "\377gn\037\377j\203+\377Qt\015\377[~\026\377Hl\013\377Is\003\377m\245,\377k\243" + ".\377}\260J\377Q\210\040\377u\253A\377V\212\004\377Z\215\003\377M\177\002\377I~" + "\004\377\377j\206;\377l\207\061\377y\214;\377w\211\071\377Rf!\377?S\016\377`t\025" + "\377j\203\020\377o\213\011\377m\212\004\377~\225\004\377q\232\003\377\200\220\003\377" + "{\230\061\377\201\252L\377j\227\004\377l\235\023\377\203\250\061\377]\222\031\377" + "]\215\003\377m\232\030\377[{\034\377Fj\002\377@g\003\377By\005\377F}\012\377]\230'\377" + "l\252\060\377r\250\014\377]\214\003\377l\234\011\377a\215\007\377b\226\006\377~\252" + "\023\377n\234\014\377O~\002\377d\226\013\377W\207\025\377j\227\014\377\211\262\003" + "\377\\\214\011\377b\235\025\377\226\260\003\377k\225\007\377z\247\002\377\234\261" + "\025\377q\240\003\377f\221\005\377|\241\004\377x\236\002\377\\\206\002\377w\233\004\377" + "\206\245\002\377\210\244\004\377\223\255\010\377y\232\003\377|\242\013\377s\235\004" + "\377x\242\003\377\215\254\005\377e\217\002\377m\237\014\377u\233\000\377\242\264." + "\377\204\223\016\377\221\261\003\377\201\255\002\377t\252\026\377t\247\023\377\207" + "\270\060\377\205\260\037\377\211\267\021\377s\235\004\377\211\261\"\377\205\256" + "\024\377q\231\002\377ct\004\377u\230\005\377p\241\004\377j\225\005\377Z\210\004\377l\235" + ")\377v\237'\377_\202\005\377g\231\004\377L\201\001\377l\230\016\377Q\177\003\377N" + "\202\002\377b\233\002\377O\212\002\377_\226\002\377p\250\003\377O\202\001\377X\211\005" + "\377l\235\030\377p\236\063\377T\202\020\377T\203\007\377m\233*\377Z\224\007\377" + "L\203\002\377b\227\003\377T\207\002\377`\231\002\377d\235\014\377S\213\003\377P\204" + "\004\377J~\003\377V\213\003\377[\214\004\377`\212\002\377b\212\004\377w\236\012\377{\250" + "\016\377a\217\024\377Nu\003\377Nf\011\377IW\007\377\\o\010\377z\211#\377\220\241" + "I\377\207\252=\377\243\274X\377\201\222-\377x\213\036\377Qe\010\377`x\031\377" + "n\220\064\377d\204\032\377]\205\012\377\\\213\012\377U\204\002\377Gp\001\377Pu\000" + "\377R}\010\377q\232\023\377\223\265\061\377Rt\012\377`\214\000\377r\243-\377w\241" + "\071\377h\237\017\377X\211\014\377\177\233%\377f\215&\377M\177\002\377:j\000\377" + "Z\207!\377N|\012\377Cm\001\377H{\004\377a\001\377n\235@\377\223\304" + "o\377s\252K\377Dk\013\377Is\004\377m\232\010\377`\212\002\377c\216\004\377m\223\004" + "\377\224\265(\377\202\243\033\377\204\247\015\377d\237\026\377P\203\004\377V\206" + "\011\377X\206\012\377u\236\010\377\206\256\002\377\210\262\031\377\214\264\007\377" + "\214\240\015\377\211\252\005\377\237\267\034\377~\253\024\377\207\257\023\377m" + "\224\004\377z\237\004\377r\227\011\377i\223\011\377u\242\004\377\212\257\003\377\203" + "\247\003\377u\234\004\377z\242\002\377q\235\003\377u\236\002\377u\226\015\377u\235\006" + "\377f\225\003\377o\236\003\377{\245\006\377\220\264\010\377\214\252\004\377\206\252" + "\003\377\215\261\004\377\204\246\003\377[z\001\377\212\241\020\377\206\232\004\377\224" + "\246\005\377\226\263\024\377x\247\034\377\200\255\036\377s\236\000\377i\233\006\377" + "g\230\007\377_\227\005\377\\\214\004\377Fn\000\377V\206\003\377Aw\004\377R\207\003\377" + "f\232\005\377Y\204\002\377\\\215\003\377V\220\002\377f\234\003\377^\223\004\377_\216" + "\004\377p\237\004\377P{\003\377W\177\013\377b\221\004\377v\252\002\377P\204\003\377V\204" + "\004\377a\213\010\377P\201\000\377k\236\037\377b\217\027\377o\242+\377U\213\000\377" + "_\221\006\377Z\216\007\377X\223\007\377m\242\024\377o\241\013\377k\240\016\377z\254" + ";\377y\254?\377a\230\027\377]\220\007\377f\222\031\377Wx\025\377Nk\002\377q\225" + "\067\377o\241\065\377q\245\"\377z\254'\377Z\212\006\377]~\007\377]\210\022\377c" + "\221\025\377c\226\017\377Y\177\022\377_\220\006\377_\223\003\377O}\007\377c\221\030" + "\377v\243/\377\206\271/\377~\255'\377\223\266K\377\220\261K\377\227\276]" + "\377\237\311o\377\230\306k\377m\236\020\377h\230\016\377\210\257-\377\\\226" + "\012\377Y\216\023\377d\226\006\377Hs\000\377n\222/\377_\177\035\377Rl\004\377\202" + "\255G\377|\256P\377}\256=\377Kt\015\377a\217$\377t\244\011\377X\212\006\377S" + "\203\010\377a\222\012\377y\243\002\377\240\275\070\377Q\207\012\377j\237\013\377" + "h\227\021\377Kx\003\377m\243\021\377Y\211\006\377w\241\003\377\203\262\011\377\203" + "\260\005\377\177\246\014\377y\231\005\377\216\256\002\377o\222\003\377v\240\005\377" + "g\221\002\377\200\251\004\377k\227\006\377m\240\013\377h\231\004\377y\244\005\377\231" + "\300\010\377s\240\011\377n\230\004\377w\237\002\377e\221\002\377}\234\003\377\250\254" + "\024\377l\224\003\377k\232\003\377e\226\002\377\202\247\030\377\204\246\002\377\212" + "\253\003\377\217\262\004\377\227\270\003\377\221\262\003\377d\211\002\377k\223\003\377" + "~\240\004\377\227\245\006\377\220\237\006\377\222\255\015\377\214\257\015\377y\250" + "\007\377c\222\005\377[\213\004\377r\245\021\377q\253\030\377k\246\030\377Bv\001\377" + "@s\002\377J}\002\377S\203\003\377U|\011\377U\211\003\377c\232\004\377f\225\002\377W\210" + "\003\377h\230\002\377m\233\002\377Z\210\005\377c\222\023\377L\214\006\377p\244\003\377" + "l\236\003\377R\201\004\377M\177\002\377_\217\002\377^\215\002\377a\216\024\377o\243" + "=\377a\240\011\377h\241\034\377\205\270N\377b\236\026\377T\216\010\377i\233\016" + "\377_\222\003\377x\254/\377\200\265;\377d\234\014\377g\240\005\377e\230\001\377" + "X\207\013\377h\224\065\377f\222\026\377~\250\060\377~\255=\377}\255/\377q\234" + "\021\377t\240\023\377e\231\005\377\\\216\003\377a\225\004\377p\243\016\377o\243\016" + "\377}\265\061\377j\236\066\377\226\302v\377\220\276J\377q\243\035\377c\214\023" + "\377Vt\014\377u\235\061\377\210\264\\\377V\204\037\377Z\221\031\377\201\266;" + "\377z\257,\377w\250\025\377^\213\004\377V}\016\377l\223\012\377i\221#\377e\212" + "%\377]\200\007\377\177\252O\377_\222-\377v\244(\377Rx\022\377m\237\061\377W\204" + "\027\377m\232\003\377h\227\005\377W\203\006\377q\233\003\377\225\267\062\377s\227\020" + "\377n\233\034\377h\234\014\377i\233\011\377f\233\015\377g\225\026\377j\231\013" + "\377\205\266\003\377m\252\025\377s\245\010\377a\217\004\377o\237\004\377~\254\002\377" + "m\234\004\377\211\256\007\377z\240\010\377i\224\004\377y\242\007\377r\236\020\377n" + "\235\003\377\203\257\004\377t\244\007\377`\214\005\377s\236\003\377n\231\003\377l\226" + "\005\377\225\247\002\377{\236\024\377k\223\002\377t\233\007\377g\217\002\377~\253\005" + "\377p\233\004\377s\232\003\377\211\261\002\377\216\263\002\377\200\247\002\377\212" + "\260\004\377p\227\002\377\204\244\003\377}\224\003\377\243\257\002\377\242\271'\377" + "}\241\002\377t\241\006\377R\206\002\377Z\222\005\377O\205\006\377`\226\027\377w\247" + "-\377v\246\062\377[\216\"\377M\202\000\377>r\001\377W\205\005\377N\204\003\377W\212" + "\004\377o\240\010\377Kz\003\377d\230\004\377Y\213\004\377[\214\012\377Z\216\004\377F" + "~\002\377O\205\003\377{\254\002\377i\226\003\377R{\003\377X\214\003\377]\217\004\377Y\212" + "\013\377q\236'\377|\265;\377q\251-\377o\241+\377`\224\016\377L\202\003\377M\200" + "\003\377M~\010\377V\205\003\377d\213\023\377y\231\030\377p\235\035\377d\231\026\377" + "s\247)\377\\\220\021\377{\257<\377\201\260P\377]\224\010\377d\231\007\377a\231" + "\004\377h\242\022\377^\220\011\377t\241\024\377\212\264'\377k\225\013\377o\227" + "\024\377\207\252A\377\225\273P\377|\256\061\377k\235\035\377k\230\035\377}\245" + "/\377\201\260E\377r\247>\377e\231!\377^\220\022\377m\234!\377[\215\016\377" + "V\213\020\377b\227\026\377y\244\033\377~\247\064\377o\222\015\377u\243\066\377" + "Z\207\037\377|\255H\377W\177\037\377s\252*\377Rz\005\377f\223\"\377T\204\015\377" + "Gp\001\377[\204\002\377w\245+\377x\242\003\377v\236\023\377\204\250\024\377k\217" + "\026\377h\230\006\377l\236\031\377o\236\007\377q\234\030\377p\240\007\377\206\266" + "\010\377r\244\011\377h\246\036\377y\250\000\377i\226\002\377r\240\003\377\200\261" + "\003\377q\235\003\377\201\242\020\377\215\261\032\377q\236\003\377Zs\002\377l\230\003" + "\377r\231\003\377\214\263\004\377u\242\005\377n\230\006\377\203\245\013\377q\224\020" + "\377p\234\011\377\201\247\036\377u\241\013\377v\236\006\377y\243\001\377s\241\001" + "\377m\233\003\377]\212\004\377t\237\004\377{\245\003\377\222\267\004\377n\227\002\377" + "\204\246\006\377\204\246\005\377\227\266\013\377\220\252\004\377\231\257\005\377\270" + "\274@\377|\243\016\377p\233\001\377`\225\003\377a\225\002\377a\223\003\377T\206\004" + "\377X\216\003\377Y\220\001\377b\220\031\377\225\275`\377c\211\030\377T\204\003\377" + "H~\002\377V\212\003\377g\240\002\377M\205\003\377a\227\004\377Q\202\002\377Ky\002\377^" + "\212\014\377S\204\002\377U\207\003\377m\236\003\377^\210\002\377h\235\002\377a\227\003" + "\377L\205\003\377a\232\011\377h\240\021\377j\242\033\377}\260A\377\232\305h\377" + "g\237\023\377Fw\002\377Z\215\003\377V\213\006\377V\206\003\377f\235\012\377V\216\004" + "\377]\222\002\377U\213\002\377V\217\002\377^\226\003\377[\224\005\377R\213\005\377o\240" + "\033\377r\241\016\377a\214\015\377f\232\"\377b\226\011\377i\236\013\377o\236\037" + "\377{\250?\377e\221\027\377\225\267\065\377\200\244\031\377^\205\004\377t\241" + "'\377i\237\037\377k\240\"\377l\232%\377Fg\006\377^\216\024\377z\241\063\377\212" + "\235<\377b\220\031\377L~\003\377V\211\004\377p\235\016\377g}\035\377\207\264\065" + "\377z\246*\377}\264B\377^\215'\377l\221\062\377d\214!\377k\222\033\377Mu\010" + "\377@f\002\377Z\205\004\377v\244\"\377w\247\022\377m\224\001\377\241\272*\377h\213" + "\017\377^\214\011\377j\230\005\377\216\257#\377k\227\004\377\207\260\005\377\204" + "\255\007\377h\224\005\377f\235\021\377j\244\024\377|\252\002\377q\240\004\377\206\256" + "\004\377u\241\002\377h\224\002\377r\217\007\377\211\251\013\377j\227\003\377]\201\003" + "\377i\220\002\377\227\270\003\377p\227\002\377z\243\020\377u\242\001\377\177\255\031" + "\377g\221\003\377g\217\010\377g\215\012\377n\230\005\377\177\252\007\377y\242\014" + "\377{\243\025\377l\225\003\377f\221\004\377\210\261\004\377s\234\003\377\200\246\003" + "\377}\240\003\377\207\247\003\377\214\250\003\377\200\241\006\377\220\267\021\377" + "\202\245\003\377\242\261\031\377\226\260-\377u\236\002\377]\227\023\377Y\210\001" + "\377Nv\000\377_\202\017\377U\210\005\377]\231\010\377a\236\010\377Y\216\000\377_\223" + "\027\377W\214\006\377G\202\003\377b\226\023\377d\232\001\377[\223\007\377f\225\002\377" + "Q}\005\377Hs\002\377`\212\013\377S\200\004\377W\212\003\377X\212\002\377^\206\003\377" + "l\234\004\377n\245\004\377J\177\003\377i\235\007\377t\250\026\377|\260+\377u\253\065" + "\377\207\266L\377\225\300T\377\207\264B\377r\225\066\377^\201\023\377`\220" + "\021\377Iu\003\377L~\006\377R\205\004\377S\201\003\377]\221\015\377`\233\005\377M\201" + "\000\377_\224\005\377d\225\013\377`\220\011\377h\231\021\377\\\214\004\377h\241\017" + "\377b\227\012\377v\246\036\377k\231\035\377j\232\014\377u\245\022\377~\241\033" + "\377r\230\030\377\204\260Q\377\200\255J\377i\210+\377}\227A\377}\251A\377" + "\200\257I\377b\226)\377]\222\036\377\\\222\026\377F{\005\377\\\222\007\377v\245" + "\005\377j\225\011\377t\254\024\377^\214\020\377p\245\035\377Y\213\014\377u\246(" + "\377Z\212!\377R~\016\377F_\004\377JX\006\377h\216\007\377u\250#\377\214\261\001\377" + "\207\244\012\377\177\251\026\377l\224\024\377i\232\002\377t\251\035\377h\231\011" + "\377\204\260\005\377x\244\010\377\200\251\005\377`\216\011\377k\245.\377j\240\013" + "\377w\246\002\377\205\260\004\377w\235\004\377i\225\002\377v\235\003\377l\221\005\377" + "}\247\002\377u\245\006\377q\235\007\377r\232\003\377\224\265\010\377v\237$\377l\232" + "\012\377l\231\004\377~\254\002\377w\247\030\377r\236\015\377w\236\007\377{\243\004\377" + "u\232\004\377\212\261\032\377`\214\003\377\222\267)\377v\243\004\377\200\253\005\377" + "p\225\000\377\210\255\004\377\203\246\005\377\207\247\007\377\215\245\001\377l\212" + "\003\377~\225\004\377\204\242\010\377\222\261\010\377\211\247\024\377\220\262\016" + "\377=u\005\377[\216\"\377\213\271G\377Fw\005\377Dp\002\377:j\001\377b\231\013\377" + "Y\217\002\377Q\210\005\377o\250\005\377X\212\001\377p\244\030\377\\\225\001\377]\225" + "\007\377a\223\003\377Ls\005\377V\203\005\377U\202\005\377Xv\010\377c\223\004\377N\201" + "\003\377Y\213\005\377a\220\006\377a\215\005\377\\\213\003\377V\206\001\377~\251\061\377" + "\216\301F\377l\242\"\377\241\313i\377\211\267A\377\215\274L\377\262\334\214" + "\377h\215<\377h\214/\377h\240\007\377U\216\005\377W\216\004\377^\215\004\377Q\203" + "\003\377e\234\004\377s\254\034\377\202\262E\377z\244\060\377i\227\011\377e\223\013" + "\377d\225\013\377j\237\015\377c\222\017\377j\225\011\377\211\261\011\377{\245" + "\012\377\177\250\037\377\234\300c\377|\251F\377\204\251Z\377{\241G\377g\226" + "/\377R\206\015\377a\210\020\377dw\004\377\201\262\032\377\201\255\067\377x\247" + "\061\377r\251.\377i\233\017\377z\245\002\377q\243-\377l\240\020\377`\224\027\377" + "d\233\"\377d\234\030\377|\261-\377v\252(\377c\223\010\377[\205\005\377Nu\005\377" + "x\244\005\377\200\260\022\377x\234\005\377\204\245\023\377z\245\031\377m\233\005\377" + "q\233\005\377}\256&\377\204\260\005\377V\204\004\377z\247\015\377^\204\004\377g\222" + "!\377\211\265a\377\200\254\011\377}\253\003\377v\241\002\377n\230\004\377c\212\002" + "\377{\233\010\377\\\210\002\377r\243\014\377n\233\002\377q\232\002\377\201\250\005" + "\377y\231\016\377u\237\011\377\217\254\017\377\202\256\025\377\236\312Q\377\206" + "\261\061\377p\237\005\377{\245\013\377\234\274\016\377q\225\002\377\215\264\010\377" + "t\237\021\377a\211\005\377\214\271\022\377z\254\016\377}\254\034\377\231\273\016" + "\377\207\244\011\377\216\251\012\377\211\236\033\377\211\247\006\377Oc\001\377w" + "\233\003\377\211\261\017\377\205\247\013\377r\227\006\377[\207\003\377\200\246A\377" + "y\253\071\377d\221\034\377@c\002\377;a\002\377Iq\003\377W\211\005\377J\177\001\377g\231" + "\003\377Lu\004\377W\216\003\377b\242\006\377[\230\010\377P\203\000\377u\230.\377U\202" + "\005\377T\201\002\377Ir\003\377U\215\003\377I\177\003\377]\225\010\377Hi\003\377Vq\007\377" + "Or\002\377]\212\004\377;X\001\377^\207\015\377c\217\020\377w\253\032\377\203\270\060" + "\377\205\273\066\377\210\274\067\377r\244%\377Ms\015\377\177\241,\377a\213\023" + "\377l\244\025\377[\217\003\377c\234\004\377a\227\005\377j\234\005\377v\251\013\377" + "s\244\016\377x\247'\377y\245.\377q\242'\377o\237(\377g\226\036\377_\222\032" + "\377]\217\016\377`\222\017\377h\234\033\377Z\215\010\377\201\257\020\377\201\242" + "\040\377\177\253\037\377{\254%\377x\251\015\377~\246\013\377\206\254\031\377w" + "\246\020\377m\243\027\377d\231\025\377b\232\027\377W\213\011\377v\246\003\377]\205" + "\013\377e\225\013\377n\243\040\377z\253*\377X\211\020\377P~\004\377{\247=\377{" + "\245*\377Wz\006\377Qv\011\377\200\256\003\377p\240\001\377\240\272+\377r\243\027" + "\377\205\252\005\377s\240\004\377\214\264\004\377~\253\004\377^\216\020\377y\252\007" + "\377R\201\003\377h\230\027\377t\242%\377\231\300S\377\243\303!\377\234\271\"" + "\377\232\264$\377\207\240\021\377r\216\005\377\202\237\005\377h\226\003\377o\235" + "\013\377\206\254\003\377j\227\003\377\214\261\005\377y\241\006\377r\236\007\377\221" + "\262\014\377\205\255\016\377V\201\010\377}\247\015\377\216\261\013\377n\225\013" + "\377\220\265\007\377\205\260\005\377\210\256\024\377z\243\004\377x\241\003\377v\243" + "\010\377\215\267)\377\250\301.\377\252\302\033\377\222\257(\377\224\260\025" + "\377~\233\006\377\220\256\007\377\203\241\003\377\207\254\002\377y\232\002\377\212" + "\247\011\377\207\256\022\377", +}; + diff --git a/examples/cat.h b/examples/cat.h new file mode 100644 index 0000000..46dc50f --- /dev/null +++ b/examples/cat.h @@ -0,0 +1,13 @@ +#ifndef _CAT_H +#define _CAT_H + +struct gimp_texture { + unsigned int width; + unsigned int height; + unsigned int bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */ + unsigned char pixel_data[128 * 128 * 4 + 1]; +}; + +extern const struct gimp_texture cat_tex; + +#endif diff --git a/examples/embedded.c b/examples/embedded.c new file mode 100644 index 0000000..ff8b885 --- /dev/null +++ b/examples/embedded.c @@ -0,0 +1,212 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xdg-shell-client-protocol.h" + +static struct wl_display *remote_display = NULL; +static struct wl_compositor *compositor = NULL; +static struct wl_subcompositor *subcompositor = NULL; +static struct xdg_wm_base *wm_base = NULL; + +static struct wl_egl_window *egl_window = NULL; +static struct wlr_egl_surface *egl_surface = NULL; +static struct wl_surface *main_surface = NULL; +static struct wl_callback *frame_callback = NULL; + +static struct wlr_scene *scene = NULL; +static struct wlr_scene_output *scene_output = NULL; +static struct wl_listener new_surface = {0}; +static struct wl_listener output_frame = {0}; + +static EGLDisplay egl_display; +static EGLConfig egl_config; +static EGLContext egl_context; + +static int width = 500; +static int height = 500; + +static void draw_main_surface(void); + +static void frame_handle_done(void *data, struct wl_callback *callback, uint32_t t) { + wl_callback_destroy(callback); + frame_callback = NULL; + draw_main_surface(); +} + +static const struct wl_callback_listener frame_listener = { + .done = frame_handle_done, +}; + +static void draw_main_surface(void) { + frame_callback = wl_surface_frame(main_surface); + wl_callback_add_listener(frame_callback, &frame_listener, NULL); + + eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); + eglSwapInterval(egl_display, 0); + + glViewport(0, 0, width, height); + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(egl_display, egl_surface); + wl_display_flush(remote_display); +} + +static void xdg_surface_handle_configure(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) { + xdg_surface_ack_configure(xdg_surface, serial); + if (frame_callback == NULL) { + draw_main_surface(); + } +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_handle_configure, +}; + +static void xdg_toplevel_handle_configure(void *data, + struct xdg_toplevel *xdg_toplevel, int32_t w, int32_t h, + struct wl_array *states) { + if (w != 0 && h != 0) { + width = w; + height = h; + } +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + if (strcmp(interface, wl_compositor_interface.name) == 0) { + compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); + } else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { + subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); + } +} + +static void handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) { + // Who cares? +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove, +}; + +static void output_handle_frame(struct wl_listener *listener, void *data) { + wlr_scene_output_commit(scene_output, NULL); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(scene_output, &now); +} + +static void handle_new_surface(struct wl_listener *listener, void *data) { + struct wlr_surface *wlr_surface = data; + wlr_scene_surface_create(&scene->tree, wlr_surface); +} + +static void init_egl(struct wl_display *display) { + egl_display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, display, NULL); + eglInitialize(egl_display, NULL, NULL); + + EGLint matched = 0; + const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE, + }; + eglChooseConfig(egl_display, config_attribs, &egl_config, 1, &matched); + + const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE, + }; + egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attribs); +} + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + + remote_display = wl_display_connect(NULL); + struct wl_registry *registry = wl_display_get_registry(remote_display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_roundtrip(remote_display); + + init_egl(remote_display); + + struct wl_display *local_display = wl_display_create(); + struct wl_event_loop *event_loop = wl_display_get_event_loop(local_display); + struct wlr_backend *backend = wlr_wl_backend_create(event_loop, remote_display); + struct wlr_renderer *renderer = wlr_renderer_autocreate(backend); + wlr_renderer_init_wl_display(renderer, local_display); + struct wlr_allocator *allocator = wlr_allocator_autocreate(backend, renderer); + scene = wlr_scene_create(); + struct wlr_compositor *wlr_compositor = wlr_compositor_create(local_display, 5, renderer); + + wlr_xdg_shell_create(local_display, 2); + + new_surface.notify = handle_new_surface; + wl_signal_add(&wlr_compositor->events.new_surface, &new_surface); + + wlr_backend_start(backend); + + main_surface = wl_compositor_create_surface(compositor); + struct xdg_surface *xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, main_surface); + struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); + xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL); + xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL); + + egl_window = wl_egl_window_create(main_surface, width, height); + egl_surface = eglCreatePlatformWindowSurface(egl_display, + egl_config, egl_window, NULL); + + struct wl_surface *child_surface = wl_compositor_create_surface(compositor); + struct wl_subsurface *subsurface = wl_subcompositor_get_subsurface(subcompositor, child_surface, main_surface); + wl_subsurface_set_position(subsurface, 20, 20); + struct wlr_output *output = wlr_wl_output_create_from_surface(backend, child_surface); + wlr_output_init_render(output, allocator, renderer); + scene_output = wlr_scene_output_create(scene, output); + + output_frame.notify = output_handle_frame; + wl_signal_add(&output->events.frame, &output_frame); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + wlr_output_commit_state(output, &state); + wlr_output_state_finish(&state); + + wl_surface_commit(main_surface); + wl_display_flush(remote_display); + + const char *socket = wl_display_add_socket_auto(local_display); + setenv("WAYLAND_DISPLAY", socket, true); + wlr_log(WLR_INFO, "Running embedded Wayland compositor on WAYLAND_DISPLAY=%s", socket); + + wl_display_run(local_display); + + return 0; +} diff --git a/examples/fullscreen-shell.c b/examples/fullscreen-shell.c new file mode 100644 index 0000000..c12f126 --- /dev/null +++ b/examples/fullscreen-shell.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * A minimal fullscreen-shell server. It only supports rendering. + */ + +struct fullscreen_server { + struct wl_display *wl_display; + struct wlr_backend *backend; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + + struct wlr_fullscreen_shell_v1 *fullscreen_shell; + struct wl_listener present_surface; + + struct wlr_output_layout *output_layout; + struct wl_list outputs; + struct wl_listener new_output; +}; + +struct fullscreen_output { + struct wl_list link; + struct fullscreen_server *server; + struct wlr_output *wlr_output; + struct wlr_surface *surface; + struct wl_listener surface_destroy; + + struct wl_listener frame; +}; + +struct render_data { + struct wlr_output *output; + struct wlr_render_pass *render_pass; + struct timespec *when; +}; + +static void render_surface(struct wlr_surface *surface, + int sx, int sy, void *data) { + struct render_data *rdata = data; + struct wlr_output *output = rdata->output; + + struct wlr_texture *texture = wlr_surface_get_texture(surface); + if (texture == NULL) { + return; + } + + struct wlr_box box = { + .x = sx * output->scale, + .y = sy * output->scale, + .width = surface->current.width * output->scale, + .height = surface->current.height * output->scale, + }; + + enum wl_output_transform transform = wlr_output_transform_invert(surface->current.transform); + transform = wlr_output_transform_compose(transform, output->transform); + + wlr_render_pass_add_texture(rdata->render_pass, &(struct wlr_render_texture_options){ + .texture = texture, + .dst_box = box, + .transform = transform, + }); + + wlr_surface_send_frame_done(surface, rdata->when); +} + +static void output_handle_frame(struct wl_listener *listener, void *data) { + struct fullscreen_output *output = + wl_container_of(listener, output, frame); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + int width, height; + wlr_output_effective_resolution(output->wlr_output, &width, &height); + + struct wlr_output_state state; + wlr_output_state_init(&state); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(output->wlr_output, &state, NULL, + NULL); + if (pass == NULL) { + return; + } + + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .color = { 0.3, 0.3, 0.3, 1.0 }, + .box = { .width = width, .height = height }, + }); + + if (output->surface != NULL) { + struct render_data rdata = { + .output = output->wlr_output, + .render_pass = pass, + .when = &now, + }; + wlr_surface_for_each_surface(output->surface, render_surface, &rdata); + } + + wlr_render_pass_submit(pass); + wlr_output_commit_state(output->wlr_output, &state); + wlr_output_state_finish(&state); +} + +static void output_set_surface(struct fullscreen_output *output, + struct wlr_surface *surface); + +static void output_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct fullscreen_output *output = + wl_container_of(listener, output, surface_destroy); + output_set_surface(output, NULL); +} + +static void output_set_surface(struct fullscreen_output *output, + struct wlr_surface *surface) { + if (output->surface == surface) { + return; + } + + if (output->surface != NULL) { + wl_list_remove(&output->surface_destroy.link); + output->surface = NULL; + } + + if (surface != NULL) { + output->surface_destroy.notify = output_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &output->surface_destroy); + output->surface = surface; + } + + wlr_log(WLR_DEBUG, "Presenting surface %p on output %s", + surface, output->wlr_output->name); +} + +static void server_handle_new_output(struct wl_listener *listener, void *data) { + struct fullscreen_server *server = + wl_container_of(listener, server, new_output); + struct wlr_output *wlr_output = data; + + wlr_output_init_render(wlr_output, server->allocator, server->renderer); + + struct fullscreen_output *output = calloc(1, sizeof(*output)); + output->wlr_output = wlr_output; + output->server = server; + output->frame.notify = output_handle_frame; + wl_signal_add(&wlr_output->events.frame, &output->frame); + wl_list_insert(&server->outputs, &output->link); + + wlr_output_layout_add_auto(server->output_layout, wlr_output); + wlr_output_create_global(wlr_output, server->wl_display); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); +} + +static void server_handle_present_surface(struct wl_listener *listener, + void *data) { + struct fullscreen_server *server = + wl_container_of(listener, server, present_surface); + struct wlr_fullscreen_shell_v1_present_surface_event *event = data; + + struct fullscreen_output *output; + wl_list_for_each(output, &server->outputs, link) { + if (event->output == NULL || event->output == output->wlr_output) { + output_set_surface(output, event->surface); + } + } +} + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + + char *startup_cmd = NULL; + + int c; + while ((c = getopt(argc, argv, "s:")) != -1) { + switch (c) { + case 's': + startup_cmd = optarg; + break; + default: + printf("usage: %s [-s startup-command]\n", argv[0]); + return EXIT_FAILURE; + } + } + if (optind < argc) { + printf("usage: %s [-s startup-command]\n", argv[0]); + return EXIT_FAILURE; + } + + struct fullscreen_server server = {0}; + server.wl_display = wl_display_create(); + server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.wl_display), NULL); + server.renderer = wlr_renderer_autocreate(server.backend); + wlr_renderer_init_wl_display(server.renderer, server.wl_display); + server.allocator = wlr_allocator_autocreate(server.backend, + server.renderer); + + wlr_compositor_create(server.wl_display, 5, server.renderer); + + server.output_layout = wlr_output_layout_create(server.wl_display); + + wl_list_init(&server.outputs); + server.new_output.notify = server_handle_new_output; + wl_signal_add(&server.backend->events.new_output, &server.new_output); + + server.fullscreen_shell = wlr_fullscreen_shell_v1_create(server.wl_display); + server.present_surface.notify = server_handle_present_surface; + wl_signal_add(&server.fullscreen_shell->events.present_surface, + &server.present_surface); + + const char *socket = wl_display_add_socket_auto(server.wl_display); + if (!socket) { + wl_display_destroy(server.wl_display); + return EXIT_FAILURE; + } + + if (!wlr_backend_start(server.backend)) { + wl_display_destroy(server.wl_display); + return EXIT_FAILURE; + } + + setenv("WAYLAND_DISPLAY", socket, true); + if (startup_cmd != NULL) { + if (fork() == 0) { + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL); + } + } + + wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", + socket); + wl_display_run(server.wl_display); + + wl_display_destroy_clients(server.wl_display); + wl_display_destroy(server.wl_display); + return 0; +} diff --git a/examples/meson.build b/examples/meson.build new file mode 100644 index 0000000..3fe07df --- /dev/null +++ b/examples/meson.build @@ -0,0 +1,68 @@ +cairo = dependency('cairo', required: false, disabler: true) +# Only needed for drm_fourcc.h +libdrm_header = dependency('libdrm').partial_dependency(compile_args: true, includes: true) +wayland_client = dependency('wayland-client', required: false, disabler: true) +wayland_egl = dependency('wayland-egl', required: false, disabler: true) +egl = dependency('egl', version: '>= 1.5', required: false, disabler: true) +glesv2 = dependency('glesv2', required: false, disabler: true) + +compositors = { + 'simple': { + 'src': 'simple.c', + }, + 'pointer': { + 'src': 'pointer.c', + }, + 'touch': { + 'src': ['touch.c', 'cat.c'], + }, + 'tablet': { + 'src': 'tablet.c', + }, + 'rotation': { + 'src': ['rotation.c', 'cat.c'], + }, + 'output-layout': { + 'src': ['output-layout.c', 'cat.c'], + }, + 'fullscreen-shell': { + 'src': 'fullscreen-shell.c', + 'proto': ['fullscreen-shell-unstable-v1'], + }, + 'scene-graph': { + 'src': 'scene-graph.c', + 'proto': ['xdg-shell'], + }, + 'output-layers': { + 'src': 'output-layers.c', + 'proto': [ + 'xdg-shell', + ], + }, + 'cairo-buffer': { + 'src': 'cairo-buffer.c', + 'dep': cairo, + }, + 'embedded': { + 'src': [ + 'embedded.c', + protocols_code['xdg-shell'], + protocols_client_header['xdg-shell'], + ], + 'dep': [wayland_client, wayland_egl, egl, glesv2], + }, +} + +foreach name, info : compositors + extra_src = [] + foreach p : info.get('proto', []) + extra_src += protocols_server_header[p] + endforeach + + executable( + name, + [info.get('src'), extra_src], + dependencies: [wlroots, libdrm_header, info.get('dep', [])], + build_by_default: get_option('examples'), + ) +endforeach diff --git a/examples/output-layers.c b/examples/output-layers.c new file mode 100644 index 0000000..cd05d9f --- /dev/null +++ b/examples/output-layers.c @@ -0,0 +1,340 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Simple compositor making use of the output layers API. The compositor will + * attempt to display client surfaces with output layers. Input is + * unimplemented. + * + * New surfaces are stacked on top of the existing ones as they appear. + * Surfaces that don't make it into an output layer are rendered as usual. */ + +struct server { + struct wl_display *wl_display; + struct wlr_backend *backend; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1; + + struct wl_list outputs; + + struct wl_listener new_output; + struct wl_listener new_surface; +}; + +struct output_surface { + struct wlr_surface *wlr_surface; + struct wlr_output_layer *layer; + struct server *server; + struct wl_list link; + + int x, y; + struct wlr_buffer *buffer; + + bool first_commit, layer_accepted, prev_layer_accepted; + + struct wl_listener destroy; + struct wl_listener commit; + struct wl_listener layer_feedback; +}; + +struct output { + struct wl_list link; + struct server *server; + struct wlr_output *wlr_output; + struct wl_list surfaces; + + struct wl_listener frame; +}; + +static void output_handle_frame(struct wl_listener *listener, void *data) { + struct output *output = wl_container_of(listener, output, frame); + + struct wl_array layers_arr = {0}; + struct output_surface *output_surface; + wl_list_for_each(output_surface, &output->surfaces, link) { + struct wlr_output_layer_state *layer_state = + wl_array_add(&layers_arr, sizeof(*layer_state)); + *layer_state = (struct wlr_output_layer_state){ + .layer = output_surface->layer, + .buffer = output_surface->buffer, + .dst_box = { + .x = output_surface->x, + .y = output_surface->y, + .width = output_surface->wlr_surface->current.width, + .height = output_surface->wlr_surface->current.height, + }, + }; + } + + struct wlr_output_state output_state; + wlr_output_state_init(&output_state); + wlr_output_state_set_layers(&output_state, layers_arr.data, + layers_arr.size / sizeof(struct wlr_output_layer_state)); + + if (!wlr_output_test_state(output->wlr_output, &output_state)) { + wlr_log(WLR_ERROR, "wlr_output_test() failed"); + return; + } + + int width, height; + wlr_output_effective_resolution(output->wlr_output, &width, &height); + + struct wlr_render_pass *pass = wlr_output_begin_render_pass(output->wlr_output, &output_state, + NULL, NULL); + + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .box = { .width = width, .height = height }, + .color = { 0.3, 0.3, 0.3, 1 }, + }); + + size_t i = 0; + struct wlr_output_layer_state *layers = layers_arr.data; + wl_list_for_each(output_surface, &output->surfaces, link) { + struct wlr_surface *wlr_surface = output_surface->wlr_surface; + + output_surface->layer_accepted = layers[i].accepted; + i++; + + if (wlr_surface->buffer == NULL || output_surface->layer_accepted) { + continue; + } + + struct wlr_texture *texture = wlr_surface_get_texture(wlr_surface); + if (texture == NULL) { + continue; + } + + wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){ + .texture = texture, + .dst_box = { .x = output_surface->x, .y = output_surface->y }, + }); + } + + wlr_render_pass_submit(pass); + + wlr_output_commit_state(output->wlr_output, &output_state); + wlr_output_state_finish(&output_state); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + wl_list_for_each(output_surface, &output->surfaces, link) { + wlr_surface_send_frame_done(output_surface->wlr_surface, &now); + + if (output_surface->wlr_surface->buffer == NULL) { + continue; + } + + if ((output_surface->first_commit || + !output_surface->prev_layer_accepted) && + output_surface->layer_accepted) { + wlr_log(WLR_INFO, "Scanning out wlr_surface %p on output '%s'", + output_surface->wlr_surface, output->wlr_output->name); + } + if ((output_surface->first_commit || + output_surface->prev_layer_accepted) && + !output_surface->layer_accepted) { + wlr_log(WLR_INFO, "Cannot scan out wlr_surface %p on output '%s'", + output_surface->wlr_surface, output->wlr_output->name); + } + output_surface->prev_layer_accepted = output_surface->layer_accepted; + output_surface->first_commit = false; + } + + wl_array_release(&layers_arr); +} + +static void server_handle_new_output(struct wl_listener *listener, void *data) { + struct server *server = wl_container_of(listener, server, new_output); + struct wlr_output *wlr_output = data; + + wlr_output_init_render(wlr_output, server->allocator, server->renderer); + + struct output *output = calloc(1, sizeof(*output)); + output->wlr_output = wlr_output; + output->server = server; + wl_list_init(&output->surfaces); + output->frame.notify = output_handle_frame; + wl_signal_add(&wlr_output->events.frame, &output->frame); + wl_list_insert(&server->outputs, &output->link); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + + wlr_output_create_global(wlr_output, server->wl_display); +} + +static void output_surface_handle_destroy(struct wl_listener *listener, + void *data) { + struct output_surface *output_surface = + wl_container_of(listener, output_surface, destroy); + wlr_buffer_unlock(output_surface->buffer); + wl_list_remove(&output_surface->destroy.link); + wl_list_remove(&output_surface->commit.link); + wl_list_remove(&output_surface->layer_feedback.link); + wl_list_remove(&output_surface->link); + wlr_output_layer_destroy(output_surface->layer); + free(output_surface); +} + +static void output_surface_handle_commit(struct wl_listener *listener, + void *data) { + struct output_surface *output_surface = + wl_container_of(listener, output_surface, commit); + + struct wlr_buffer *buffer = NULL; + if (output_surface->wlr_surface->buffer != NULL) { + buffer = wlr_buffer_lock(&output_surface->wlr_surface->buffer->base); + } + + wlr_buffer_unlock(output_surface->buffer); + output_surface->buffer = buffer; +} + +static void output_surface_handle_layer_feedback(struct wl_listener *listener, + void *data) { + struct output_surface *output_surface = + wl_container_of(listener, output_surface, layer_feedback); + const struct wlr_output_layer_feedback_event *event = data; + + wlr_log(WLR_DEBUG, "Sending linux-dmabuf feedback to surface %p", + output_surface->wlr_surface); + + struct wlr_linux_dmabuf_feedback_v1 feedback = {0}; + const struct wlr_linux_dmabuf_feedback_v1_init_options options = { + .main_renderer = output_surface->server->renderer, + .output_layer_feedback_event = event, + }; + wlr_linux_dmabuf_feedback_v1_init_with_options(&feedback, &options); + wlr_linux_dmabuf_v1_set_surface_feedback(output_surface->server->linux_dmabuf_v1, + output_surface->wlr_surface, &feedback); + wlr_linux_dmabuf_feedback_v1_finish(&feedback); +} + +static void server_handle_new_surface(struct wl_listener *listener, + void *data) { + struct server *server = wl_container_of(listener, server, new_surface); + struct wlr_surface *wlr_surface = data; + + struct output *output; + wl_list_for_each(output, &server->outputs, link) { + struct output_surface *output_surface = calloc(1, sizeof(*output_surface)); + output_surface->wlr_surface = wlr_surface; + output_surface->server = server; + output_surface->destroy.notify = output_surface_handle_destroy; + wl_signal_add(&wlr_surface->events.destroy, &output_surface->destroy); + output_surface->commit.notify = output_surface_handle_commit; + wl_signal_add(&wlr_surface->events.commit, &output_surface->commit); + + output_surface->layer = wlr_output_layer_create(output->wlr_output); + output_surface->layer_feedback.notify = output_surface_handle_layer_feedback; + wl_signal_add(&output_surface->layer->events.feedback, + &output_surface->layer_feedback); + + int pos = 50 * wl_list_length(&output->surfaces); + + output_surface->x = output_surface->y = pos; + output_surface->first_commit = true; + + wl_list_insert(output->surfaces.prev, &output_surface->link); + } +} + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + + char *startup_cmd = NULL; + + int c; + while ((c = getopt(argc, argv, "s:")) != -1) { + switch (c) { + case 's': + startup_cmd = optarg; + break; + default: + printf("usage: %s [-s startup-command]\n", argv[0]); + return EXIT_FAILURE; + } + } + if (optind < argc) { + printf("usage: %s [-s startup-command]\n", argv[0]); + return EXIT_FAILURE; + } + + struct server server = {0}; + server.wl_display = wl_display_create(); + server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.wl_display), NULL); + + server.renderer = wlr_renderer_autocreate(server.backend); + wlr_renderer_init_wl_shm(server.renderer, server.wl_display); + + if (wlr_renderer_get_dmabuf_texture_formats(server.renderer) != NULL) { + wlr_drm_create(server.wl_display, server.renderer); + server.linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer( + server.wl_display, 4, server.renderer); + } + + server.allocator = wlr_allocator_autocreate(server.backend, + server.renderer); + + struct wlr_compositor *compositor = + wlr_compositor_create(server.wl_display, 5, server.renderer); + + wlr_xdg_shell_create(server.wl_display, 1); + + wl_list_init(&server.outputs); + + server.new_output.notify = server_handle_new_output; + wl_signal_add(&server.backend->events.new_output, &server.new_output); + + server.new_surface.notify = server_handle_new_surface; + wl_signal_add(&compositor->events.new_surface, &server.new_surface); + + const char *socket = wl_display_add_socket_auto(server.wl_display); + if (!socket) { + wl_display_destroy(server.wl_display); + return EXIT_FAILURE; + } + + if (!wlr_backend_start(server.backend)) { + wl_display_destroy(server.wl_display); + return EXIT_FAILURE; + } + + setenv("WAYLAND_DISPLAY", socket, true); + if (startup_cmd != NULL) { + if (fork() == 0) { + execlp("sh", "sh", "-c", startup_cmd, (char *)NULL); + } + } + + wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", + socket); + wl_display_run(server.wl_display); + + wl_display_destroy_clients(server.wl_display); + wl_display_destroy(server.wl_display); + return EXIT_SUCCESS; +} diff --git a/examples/output-layout.c b/examples/output-layout.c new file mode 100644 index 0000000..3a3eeb2 --- /dev/null +++ b/examples/output-layout.c @@ -0,0 +1,304 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cat.h" + +struct sample_state { + struct wl_display *display; + struct wl_listener new_output; + struct wl_listener new_input; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + struct wlr_texture *cat_texture; + struct wlr_output_layout *layout; + float x_offs, y_offs; + float x_vel, y_vel; + struct timespec ts_last; +}; + +struct sample_output { + struct sample_state *sample; + struct wlr_output *output; + struct wl_listener frame; + struct wl_listener destroy; +}; + +struct sample_keyboard { + struct sample_state *sample; + struct wlr_keyboard *wlr_keyboard; + struct wl_listener key; + struct wl_listener destroy; +}; + +static void animate_cat(struct sample_state *sample, + struct wlr_output *output) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + long ms = (ts.tv_sec - sample->ts_last.tv_sec) * 1000 + + (ts.tv_nsec - sample->ts_last.tv_nsec) / 1000000; + // how many seconds have passed since the last time we animated + float seconds = ms / 1000.0f; + + if (seconds > 0.1f) { + // XXX when we switch vt, the rendering loop stops so try to detect + // that and pause when it happens. + seconds = 0.0f; + } + + // check for collisions and bounce + bool ur_collision = !wlr_output_layout_output_at(sample->layout, + sample->x_offs + 128, sample->y_offs); + bool ul_collision = !wlr_output_layout_output_at(sample->layout, + sample->x_offs, sample->y_offs); + bool ll_collision = !wlr_output_layout_output_at(sample->layout, + sample->x_offs, sample->y_offs + 128); + bool lr_collision = !wlr_output_layout_output_at(sample->layout, + sample->x_offs + 128, sample->y_offs + 128); + + if (ur_collision && ul_collision && ll_collision && lr_collision) { + // oops we went off the screen somehow + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(sample->layout, output); + sample->x_offs = l_output->x + 20; + sample->y_offs = l_output->y + 20; + } else if (ur_collision && ul_collision) { + sample->y_vel = fabs(sample->y_vel); + } else if (lr_collision && ll_collision) { + sample->y_vel = -fabs(sample->y_vel); + } else if (ll_collision && ul_collision) { + sample->x_vel = fabs(sample->x_vel); + } else if (ur_collision && lr_collision) { + sample->x_vel = -fabs(sample->x_vel); + } else { + if (ur_collision || lr_collision) { + sample->x_vel = -fabs(sample->x_vel); + } + if (ul_collision || ll_collision) { + sample->x_vel = fabs(sample->x_vel); + } + if (ul_collision || ur_collision) { + sample->y_vel = fabs(sample->y_vel); + } + if (ll_collision || lr_collision) { + sample->y_vel = -fabs(sample->y_vel); + } + } + + sample->x_offs += sample->x_vel * seconds; + sample->y_offs += sample->y_vel * seconds; + sample->ts_last = ts; +} + +static void output_frame_notify(struct wl_listener *listener, void *data) { + struct sample_output *output = wl_container_of(listener, output, frame); + struct sample_state *sample = output->sample; + struct wlr_output *wlr_output = output->output; + + struct wlr_output_state output_state; + wlr_output_state_init(&output_state); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL); + + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .box = { .width = wlr_output->width, .height = wlr_output->height }, + .color = { 0.25, 0.25, 0.25, 1 }, + }); + + animate_cat(sample, output->output); + + struct wlr_box box = { + .x = sample->x_offs, .y = sample->y_offs, + .width = 128, .height = 128, + }; + if (wlr_output_layout_intersects(sample->layout, output->output, &box)) { + // transform global coordinates to local coordinates + double local_x = sample->x_offs; + double local_y = sample->y_offs; + wlr_output_layout_output_coords(sample->layout, output->output, + &local_x, &local_y); + + wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){ + .texture = sample->cat_texture, + .dst_box = box, + }); + } + + wlr_render_pass_submit(pass); + wlr_output_commit_state(wlr_output, &output_state); + wlr_output_state_finish(&output_state); +} + +static void update_velocities(struct sample_state *sample, + float x_diff, float y_diff) { + sample->x_vel += x_diff; + sample->y_vel += y_diff; +} + +static void output_remove_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy); + struct sample_state *sample = sample_output->sample; + wlr_output_layout_remove(sample->layout, sample_output->output); + wl_list_remove(&sample_output->frame.link); + wl_list_remove(&sample_output->destroy.link); + free(sample_output); +} + +static void new_output_notify(struct wl_listener *listener, void *data) { + struct wlr_output *output = data; + struct sample_state *sample = wl_container_of(listener, sample, new_output); + + wlr_output_init_render(output, sample->allocator, sample->renderer); + + struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); + wlr_output_layout_add_auto(sample->layout, output); + sample_output->output = output; + sample_output->sample = sample; + wl_signal_add(&output->events.frame, &sample_output->frame); + sample_output->frame.notify = output_frame_notify; + wl_signal_add(&output->events.destroy, &sample_output->destroy); + sample_output->destroy.notify = output_remove_notify; + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + struct wlr_output_mode *mode = wlr_output_preferred_mode(output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state(output, &state); + wlr_output_state_finish(&state); +} + +static void keyboard_key_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key); + struct sample_state *sample = keyboard->sample; + struct wlr_keyboard_key_event *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state, + keycode, &syms); + for (int i = 0; i < nsyms; i++) { + xkb_keysym_t sym = syms[i]; + if (sym == XKB_KEY_Escape) { + wl_display_terminate(sample->display); + } + // NOTE: It may be better to simply refer to our key state during each frame + // and make this change in pixels/sec^2 + // Also, key repeat + int delta = 75; + if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + switch (sym) { + case XKB_KEY_Left: + update_velocities(sample, -delta, 0); + break; + case XKB_KEY_Right: + update_velocities(sample, delta, 0); + break; + case XKB_KEY_Up: + update_velocities(sample, 0, -delta); + break; + case XKB_KEY_Down: + update_velocities(sample, 0, delta); + break; + } + } + } +} + +static void keyboard_destroy_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, destroy); + wl_list_remove(&keyboard->destroy.link); + wl_list_remove(&keyboard->key.link); + free(keyboard); +} + +static void new_input_notify(struct wl_listener *listener, void *data) { + struct wlr_input_device *device = data; + struct sample_state *sample = wl_container_of(listener, sample, new_input); + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD:; + struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard)); + keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device); + keyboard->sample = sample; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) { + wlr_log(WLR_ERROR, "Failed to create XKB context"); + exit(1); + } + struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + break; + default: + break; + } +} + + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + struct wl_display *display = wl_display_create(); + struct sample_state state = { + .x_vel = 500, + .y_vel = 500, + .display = display, + }; + + state.layout = wlr_output_layout_create(display); + clock_gettime(CLOCK_MONOTONIC, &state.ts_last); + + struct wlr_backend *wlr = wlr_backend_autocreate(wl_display_get_event_loop(display), NULL); + if (!wlr) { + exit(1); + } + + wl_signal_add(&wlr->events.new_output, &state.new_output); + state.new_output.notify = new_output_notify; + wl_signal_add(&wlr->events.new_input, &state.new_input); + state.new_input.notify = new_input_notify; + + state.renderer = wlr_renderer_autocreate(wlr); + state.cat_texture = wlr_texture_from_pixels(state.renderer, + DRM_FORMAT_ABGR8888, cat_tex.width * 4, cat_tex.width, cat_tex.height, + cat_tex.pixel_data); + + state.allocator = wlr_allocator_autocreate(wlr, state.renderer); + + if (!wlr_backend_start(wlr)) { + wlr_log(WLR_ERROR, "Failed to start backend"); + wlr_backend_destroy(wlr); + exit(1); + } + wl_display_run(display); + + wlr_texture_destroy(state.cat_texture); + + wl_display_destroy(state.display); +} diff --git a/examples/pointer.c b/examples/pointer.c new file mode 100644 index 0000000..fed37a5 --- /dev/null +++ b/examples/pointer.c @@ -0,0 +1,416 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct sample_state { + struct wl_display *display; + struct compositor_state *compositor; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + struct wlr_xcursor_manager *xcursor_manager; + struct wlr_cursor *cursor; + double cur_x, cur_y; + float default_color[4]; + float clear_color[4]; + struct wlr_output_layout *layout; + struct wl_list devices; + struct timespec last_frame; + + struct wl_listener new_output; + struct wl_listener new_input; + struct wl_listener cursor_motion; + struct wl_listener cursor_motion_absolute; + struct wl_listener cursor_button; + struct wl_listener cursor_axis; + + struct wl_listener touch_motion; + struct wl_listener touch_up; + struct wl_listener touch_down; + struct wl_listener touch_cancel; + struct wl_list touch_points; + + struct wl_listener tablet_tool_axis; + struct wl_listener tablet_tool_proxmity; + struct wl_listener tablet_tool_tip; + struct wl_listener tablet_tool_button; +}; + +struct touch_point { + int32_t touch_id; + double x, y; + struct wl_list link; +}; + +struct sample_output { + struct sample_state *state; + struct wlr_output *output; + struct wl_listener frame; + struct wl_listener destroy; +}; + +struct sample_keyboard { + struct sample_state *state; + struct wlr_keyboard *wlr_keyboard; + struct wl_listener key; + struct wl_listener destroy; +}; + +static void warp_to_touch(struct sample_state *state, + struct wlr_input_device *dev) { + if (wl_list_empty(&state->touch_points)) { + return; + } + + double x = 0, y = 0; + size_t n = 0; + struct touch_point *point; + wl_list_for_each(point, &state->touch_points, link) { + x += point->x; + y += point->y; + n++; + } + x /= n; + y /= n; + wlr_cursor_warp_absolute(state->cursor, dev, x, y); +} + +static void output_frame_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = wl_container_of(listener, sample_output, frame); + struct sample_state *state = sample_output->state; + struct wlr_output *wlr_output = sample_output->output; + struct wlr_renderer *renderer = state->renderer; + assert(renderer); + + struct wlr_output_state output_state; + wlr_output_state_init(&output_state); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL); + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .box = { .width = wlr_output->width, .height = wlr_output->height }, + .color = { + state->clear_color[0], + state->clear_color[1], + state->clear_color[2], + state->clear_color[3], + }, + }); + wlr_output_add_software_cursors_to_render_pass(wlr_output, pass, NULL); + wlr_render_pass_submit(pass); + wlr_output_commit_state(wlr_output, &output_state); + wlr_output_state_finish(&output_state); +} + +static void handle_cursor_motion(struct wl_listener *listener, void *data) { + struct sample_state *sample = + wl_container_of(listener, sample, cursor_motion); + struct wlr_pointer_motion_event *event = data; + wlr_cursor_move(sample->cursor, &event->pointer->base, event->delta_x, + event->delta_y); +} + +static void handle_cursor_motion_absolute(struct wl_listener *listener, + void *data) { + struct sample_state *sample = + wl_container_of(listener, sample, cursor_motion_absolute); + struct wlr_pointer_motion_absolute_event *event = data; + + sample->cur_x = event->x; + sample->cur_y = event->y; + + wlr_cursor_warp_absolute(sample->cursor, &event->pointer->base, + sample->cur_x, sample->cur_y); +} + +static void handle_cursor_button(struct wl_listener *listener, void *data) { + struct sample_state *sample = + wl_container_of(listener, sample, cursor_button); + struct wlr_pointer_button_event *event = data; + + float (*color)[4]; + if (event->state == WL_POINTER_BUTTON_STATE_RELEASED) { + color = &sample->default_color; + memcpy(&sample->clear_color, color, sizeof(*color)); + } else { + float red[4] = { 0.25f, 0.25f, 0.25f, 1 }; + red[event->button % 3] = 1; + color = &red; + memcpy(&sample->clear_color, color, sizeof(*color)); + } +} + +static void handle_cursor_axis(struct wl_listener *listener, void *data) { + struct sample_state *sample = + wl_container_of(listener, sample, cursor_axis); + struct wlr_pointer_axis_event *event = data; + + for (size_t i = 0; i < 3; ++i) { + sample->default_color[i] += event->delta > 0 ? -0.05f : 0.05f; + if (sample->default_color[i] > 1.0f) { + sample->default_color[i] = 1.0f; + } + if (sample->default_color[i] < 0.0f) { + sample->default_color[i] = 0.0f; + } + } + + memcpy(&sample->clear_color, &sample->default_color, + sizeof(sample->clear_color)); +} + +static void handle_touch_up(struct wl_listener *listener, void *data) { + struct sample_state *sample = wl_container_of(listener, sample, touch_up); + struct wlr_touch_up_event *event = data; + + struct touch_point *point, *tmp; + wl_list_for_each_safe(point, tmp, &sample->touch_points, link) { + if (point->touch_id == event->touch_id) { + wl_list_remove(&point->link); + break; + } + } + + warp_to_touch(sample, &event->touch->base); +} + +static void handle_touch_down(struct wl_listener *listener, void *data) { + struct sample_state *sample = wl_container_of(listener, sample, touch_down); + struct wlr_touch_down_event *event = data; + struct touch_point *point = calloc(1, sizeof(*point)); + point->touch_id = event->touch_id; + point->x = event->x; + point->y = event->y; + wl_list_insert(&sample->touch_points, &point->link); + + warp_to_touch(sample, &event->touch->base); +} + +static void handle_touch_motion(struct wl_listener *listener, void *data) { + struct sample_state *sample = + wl_container_of(listener, sample, touch_motion); + struct wlr_touch_motion_event *event = data; + + struct touch_point *point; + wl_list_for_each(point, &sample->touch_points, link) { + if (point->touch_id == event->touch_id) { + point->x = event->x; + point->y = event->y; + break; + } + } + + warp_to_touch(sample, &event->touch->base); +} + +static void handle_touch_cancel(struct wl_listener *listener, void *data) { + wlr_log(WLR_DEBUG, "TODO: touch cancel"); +} + +static void handle_tablet_tool_axis(struct wl_listener *listener, void *data) { + struct sample_state *sample = + wl_container_of(listener, sample, tablet_tool_axis); + struct wlr_tablet_tool_axis_event *event = data; + if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X) && + (event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) { + wlr_cursor_warp_absolute(sample->cursor, &event->tablet->base, + event->x, event->y); + } +} + +static void keyboard_key_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key); + struct sample_state *sample = keyboard->state; + struct wlr_keyboard_key_event *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state, + keycode, &syms); + for (int i = 0; i < nsyms; i++) { + xkb_keysym_t sym = syms[i]; + if (sym == XKB_KEY_Escape) { + wl_display_terminate(sample->display); + } + } +} + +static void output_remove_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy); + struct sample_state *sample = sample_output->state; + wlr_output_layout_remove(sample->layout, sample_output->output); + wl_list_remove(&sample_output->frame.link); + wl_list_remove(&sample_output->destroy.link); + free(sample_output); +} + +static void new_output_notify(struct wl_listener *listener, void *data) { + struct wlr_output *output = data; + struct sample_state *sample = wl_container_of(listener, sample, new_output); + + wlr_output_init_render(output, sample->allocator, sample->renderer); + + struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); + sample_output->output = output; + sample_output->state = sample; + wl_signal_add(&output->events.frame, &sample_output->frame); + sample_output->frame.notify = output_frame_notify; + wl_signal_add(&output->events.destroy, &sample_output->destroy); + sample_output->destroy.notify = output_remove_notify; + wlr_output_layout_add_auto(sample->layout, sample_output->output); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + struct wlr_output_mode *mode = wlr_output_preferred_mode(output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state(output, &state); + wlr_output_state_finish(&state); +} + + +static void keyboard_destroy_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, destroy); + wl_list_remove(&keyboard->destroy.link); + wl_list_remove(&keyboard->key.link); + free(keyboard); +} + +static void new_input_notify(struct wl_listener *listener, void *data) { + struct wlr_input_device *device = data; + struct sample_state *state = wl_container_of(listener, state, new_input); + switch (device->type) { + case WLR_INPUT_DEVICE_POINTER: + case WLR_INPUT_DEVICE_TOUCH: + case WLR_INPUT_DEVICE_TABLET: + wlr_cursor_attach_input_device(state->cursor, device); + break; + + case WLR_INPUT_DEVICE_KEYBOARD:; + struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard)); + keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device); + keyboard->state = state; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) { + wlr_log(WLR_ERROR, "Failed to create XKB context"); + exit(1); + } + struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + break; + default: + break; + } +} + + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + struct wl_display *display = wl_display_create(); + struct sample_state state = { + .default_color = { 0.25f, 0.25f, 0.25f, 1 }, + .clear_color = { 0.25f, 0.25f, 0.25f, 1 }, + .display = display + }; + + struct wlr_backend *wlr = wlr_backend_autocreate(wl_display_get_event_loop(display), NULL); + if (!wlr) { + exit(1); + } + + state.renderer = wlr_renderer_autocreate(wlr); + state.allocator = wlr_allocator_autocreate(wlr, state.renderer); + + state.cursor = wlr_cursor_create(); + state.layout = wlr_output_layout_create(display); + wlr_cursor_attach_output_layout(state.cursor, state.layout); + //wlr_cursor_map_to_region(state.cursor, state.config->cursor.mapped_box); + wl_list_init(&state.devices); + wl_list_init(&state.touch_points); + + // pointer events + wl_signal_add(&state.cursor->events.motion, &state.cursor_motion); + state.cursor_motion.notify = handle_cursor_motion; + + wl_signal_add(&state.cursor->events.motion_absolute, + &state.cursor_motion_absolute); + state.cursor_motion_absolute.notify = handle_cursor_motion_absolute; + + wl_signal_add(&state.cursor->events.button, &state.cursor_button); + state.cursor_button.notify = handle_cursor_button; + + wl_signal_add(&state.cursor->events.axis, &state.cursor_axis); + state.cursor_axis.notify = handle_cursor_axis; + + // touch events + wl_signal_add(&state.cursor->events.touch_up, &state.touch_up); + state.touch_up.notify = handle_touch_up; + + wl_signal_add(&state.cursor->events.touch_down, &state.touch_down); + state.touch_down.notify = handle_touch_down; + + wl_signal_add(&state.cursor->events.touch_motion, &state.touch_motion); + state.touch_motion.notify = handle_touch_motion; + + wl_signal_add(&state.cursor->events.touch_cancel, &state.touch_cancel); + state.touch_cancel.notify = handle_touch_cancel; + + wl_signal_add(&wlr->events.new_input, &state.new_input); + state.new_input.notify = new_input_notify; + + wl_signal_add(&wlr->events.new_output, &state.new_output); + state.new_output.notify = new_output_notify; + + // tool events + wl_signal_add(&state.cursor->events.tablet_tool_axis, + &state.tablet_tool_axis); + state.tablet_tool_axis.notify = handle_tablet_tool_axis; + + state.xcursor_manager = wlr_xcursor_manager_create(NULL, 24); + if (!state.xcursor_manager) { + wlr_log(WLR_ERROR, "Failed to load default cursor"); + return 1; + } + + wlr_cursor_set_xcursor(state.cursor, state.xcursor_manager, "default"); + + clock_gettime(CLOCK_MONOTONIC, &state.last_frame); + + if (!wlr_backend_start(wlr)) { + wlr_log(WLR_ERROR, "Failed to start backend"); + wlr_backend_destroy(wlr); + exit(1); + } + wl_display_run(display); + wl_display_destroy(display); + + wlr_xcursor_manager_destroy(state.xcursor_manager); + wlr_cursor_destroy(state.cursor); +} diff --git a/examples/rotation.c b/examples/rotation.c new file mode 100644 index 0000000..351973f --- /dev/null +++ b/examples/rotation.c @@ -0,0 +1,288 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cat.h" + +struct sample_state { + struct wl_display *display; + struct wl_listener new_output; + struct wl_listener new_input; + struct timespec last_frame; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + struct wlr_texture *cat_texture; + struct wl_list outputs; + enum wl_output_transform transform; +}; + +struct sample_output { + struct sample_state *sample; + struct wlr_output *output; + struct wl_listener frame; + struct wl_listener destroy; + float x_offs, y_offs; + float x_vel, y_vel; + struct wl_list link; +}; + +struct sample_keyboard { + struct sample_state *sample; + struct wlr_keyboard *wlr_keyboard; + struct wl_listener key; + struct wl_listener destroy; +}; + +static void output_frame_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = wl_container_of(listener, sample_output, frame); + struct sample_state *sample = sample_output->sample; + struct wlr_output *wlr_output = sample_output->output; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + int32_t width, height; + wlr_output_effective_resolution(wlr_output, &width, &height); + + struct wlr_output_state output_state; + wlr_output_state_init(&output_state); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL); + + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .box = { .width = wlr_output->width, .height = wlr_output->height }, + .color = { 0.25, 0.25, 0.25, 1 }, + }); + + for (int y = -128 + (int)sample_output->y_offs; y < height; y += 128) { + for (int x = -128 + (int)sample_output->x_offs; x < width; x += 128) { + wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){ + .texture = sample->cat_texture, + .dst_box = { .x = x, .y = y }, + .transform = wlr_output->transform, + }); + } + } + + wlr_render_pass_submit(pass); + wlr_output_commit_state(wlr_output, &output_state); + wlr_output_state_finish(&output_state); + + long ms = (now.tv_sec - sample->last_frame.tv_sec) * 1000 + + (now.tv_nsec - sample->last_frame.tv_nsec) / 1000000; + float seconds = ms / 1000.0f; + + sample_output->x_offs += sample_output->x_vel * seconds; + sample_output->y_offs += sample_output->y_vel * seconds; + if (sample_output->x_offs > 128) { + sample_output->x_offs = 0; + } + if (sample_output->y_offs > 128) { + sample_output->y_offs = 0; + } + sample->last_frame = now; +} + +static void update_velocities(struct sample_state *sample, + float x_diff, float y_diff) { + struct sample_output *sample_output; + wl_list_for_each(sample_output, &sample->outputs, link) { + sample_output->x_vel += x_diff; + sample_output->y_vel += y_diff; + } +} + +static void output_remove_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy); + wl_list_remove(&sample_output->frame.link); + wl_list_remove(&sample_output->destroy.link); + free(sample_output); +} + +static void new_output_notify(struct wl_listener *listener, void *data) { + struct wlr_output *output = data; + struct sample_state *sample = wl_container_of(listener, sample, new_output); + + wlr_output_init_render(output, sample->allocator, sample->renderer); + + struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); + sample_output->x_offs = sample_output->y_offs = 0; + sample_output->x_vel = sample_output->y_vel = 128; + + sample_output->output = output; + sample_output->sample = sample; + wl_signal_add(&output->events.frame, &sample_output->frame); + sample_output->frame.notify = output_frame_notify; + wl_signal_add(&output->events.destroy, &sample_output->destroy); + sample_output->destroy.notify = output_remove_notify; + wl_list_insert(&sample->outputs, &sample_output->link); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_transform(&state, sample->transform); + wlr_output_state_set_enabled(&state, true); + struct wlr_output_mode *mode = wlr_output_preferred_mode(output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state(output, &state); + wlr_output_state_finish(&state); +} + +static void keyboard_key_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key); + struct sample_state *sample = keyboard->sample; + struct wlr_keyboard_key_event *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state, + keycode, &syms); + for (int i = 0; i < nsyms; i++) { + xkb_keysym_t sym = syms[i]; + if (sym == XKB_KEY_Escape) { + wl_display_terminate(sample->display); + } + if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + switch (sym) { + case XKB_KEY_Left: + update_velocities(sample, -16, 0); + break; + case XKB_KEY_Right: + update_velocities(sample, 16, 0); + break; + case XKB_KEY_Up: + update_velocities(sample, 0, -16); + break; + case XKB_KEY_Down: + update_velocities(sample, 0, 16); + break; + } + } + } +} + +static void keyboard_destroy_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, destroy); + wl_list_remove(&keyboard->destroy.link); + wl_list_remove(&keyboard->key.link); + free(keyboard); +} + +static void new_input_notify(struct wl_listener *listener, void *data) { + struct wlr_input_device *device = data; + struct sample_state *sample = wl_container_of(listener, sample, new_input); + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD:; + struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard)); + keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device); + keyboard->sample = sample; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) { + wlr_log(WLR_ERROR, "Failed to create XKB context"); + exit(1); + } + struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + break; + default: + break; + } +} + + +int main(int argc, char *argv[]) { + int c; + enum wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; + while ((c = getopt(argc, argv, "r:")) != -1) { + switch (c) { + case 'r': + if (strcmp(optarg, "90") == 0) { + transform = WL_OUTPUT_TRANSFORM_90; + } else if (strcmp(optarg, "180") == 0) { + transform = WL_OUTPUT_TRANSFORM_180; + } else if (strcmp(optarg, "270") == 0) { + transform = WL_OUTPUT_TRANSFORM_270; + } else if (strcmp(optarg, "flipped") == 0) { + transform = WL_OUTPUT_TRANSFORM_FLIPPED; + } else if (strcmp(optarg, "flipped-90") == 0) { + transform = WL_OUTPUT_TRANSFORM_FLIPPED_90; + } else if (strcmp(optarg, "flipped-180") == 0) { + transform = WL_OUTPUT_TRANSFORM_FLIPPED_180; + } else if (strcmp(optarg, "flipped-270") == 0) { + transform = WL_OUTPUT_TRANSFORM_FLIPPED_270; + } else { + wlr_log(WLR_ERROR, "got unknown transform value: %s", optarg); + } + break; + default: + return 1; + } + } + wlr_log_init(WLR_DEBUG, NULL); + struct wl_display *display = wl_display_create(); + struct sample_state state = { + .display = display, + .transform = transform + }; + wl_list_init(&state.outputs); + + struct wlr_backend *wlr = wlr_backend_autocreate(wl_display_get_event_loop(display), NULL); + if (!wlr) { + exit(1); + } + + wl_signal_add(&wlr->events.new_output, &state.new_output); + state.new_output.notify = new_output_notify; + wl_signal_add(&wlr->events.new_input, &state.new_input); + state.new_input.notify = new_input_notify; + clock_gettime(CLOCK_MONOTONIC, &state.last_frame); + + state.renderer = wlr_renderer_autocreate(wlr); + if (!state.renderer) { + wlr_log(WLR_ERROR, "Could not start compositor, OOM"); + wlr_backend_destroy(wlr); + exit(EXIT_FAILURE); + } + state.cat_texture = wlr_texture_from_pixels(state.renderer, + DRM_FORMAT_ABGR8888, cat_tex.width * 4, cat_tex.width, cat_tex.height, + cat_tex.pixel_data); + if (!state.cat_texture) { + wlr_log(WLR_ERROR, "Could not start compositor, OOM"); + exit(EXIT_FAILURE); + } + + state.allocator = wlr_allocator_autocreate(wlr, state.renderer); + + if (!wlr_backend_start(wlr)) { + wlr_log(WLR_ERROR, "Failed to start backend"); + wlr_backend_destroy(wlr); + exit(1); + } + wl_display_run(display); + + wlr_texture_destroy(state.cat_texture); + wl_display_destroy(display); +} diff --git a/examples/scene-graph.c b/examples/scene-graph.c new file mode 100644 index 0000000..fb530c0 --- /dev/null +++ b/examples/scene-graph.c @@ -0,0 +1,207 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Simple compositor making use of the scene-graph API. Input is unimplemented. + * + * New surfaces are stacked on top of the existing ones as they appear. */ + +static const int border_width = 3; + +struct server { + struct wl_display *display; + struct wlr_backend *backend; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + struct wlr_scene *scene; + + uint32_t surface_offset; + + struct wl_listener new_output; + struct wl_listener new_surface; +}; + +struct surface { + struct wlr_surface *wlr; + struct wlr_scene_surface *scene_surface; + struct wlr_scene_rect *border; + struct wl_list link; + + struct wl_listener commit; + struct wl_listener destroy; +}; + +struct output { + struct wl_list link; + struct server *server; + struct wlr_output *wlr; + struct wlr_scene_output *scene_output; + + struct wl_listener frame; +}; + +static void output_handle_frame(struct wl_listener *listener, void *data) { + struct output *output = wl_container_of(listener, output, frame); + + if (!wlr_scene_output_commit(output->scene_output, NULL)) { + return; + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(output->scene_output, &now); +} + +static void server_handle_new_output(struct wl_listener *listener, void *data) { + struct server *server = wl_container_of(listener, server, new_output); + struct wlr_output *wlr_output = data; + + wlr_output_init_render(wlr_output, server->allocator, server->renderer); + + struct output *output = calloc(1, sizeof(*output)); + output->wlr = wlr_output; + output->server = server; + output->frame.notify = output_handle_frame; + wl_signal_add(&wlr_output->events.frame, &output->frame); + + output->scene_output = wlr_scene_output_create(server->scene, wlr_output); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + + wlr_output_create_global(wlr_output, server->display); +} + +static void surface_handle_commit(struct wl_listener *listener, void *data) { + struct surface *surface = wl_container_of(listener, surface, commit); + wlr_scene_rect_set_size(surface->border, + surface->wlr->current.width + 2 * border_width, + surface->wlr->current.height + 2 * border_width); +} + +static void surface_handle_destroy(struct wl_listener *listener, void *data) { + struct surface *surface = wl_container_of(listener, surface, destroy); + wlr_scene_node_destroy(&surface->scene_surface->buffer->node); + wlr_scene_node_destroy(&surface->border->node); + wl_list_remove(&surface->destroy.link); + wl_list_remove(&surface->link); + free(surface); +} + +static void server_handle_new_surface(struct wl_listener *listener, + void *data) { + struct server *server = wl_container_of(listener, server, new_surface); + struct wlr_surface *wlr_surface = data; + + int pos = server->surface_offset; + server->surface_offset += 50; + + struct surface *surface = calloc(1, sizeof(*surface)); + surface->wlr = wlr_surface; + surface->commit.notify = surface_handle_commit; + wl_signal_add(&wlr_surface->events.commit, &surface->commit); + surface->destroy.notify = surface_handle_destroy; + wl_signal_add(&wlr_surface->events.destroy, &surface->destroy); + + /* Border dimensions will be set in surface.commit handler */ + surface->border = wlr_scene_rect_create(&server->scene->tree, + 0, 0, (float[4]){ 0.5f, 0.5f, 0.5f, 1 }); + wlr_scene_node_set_position(&surface->border->node, pos, pos); + + surface->scene_surface = + wlr_scene_surface_create(&server->scene->tree, wlr_surface); + + wlr_scene_node_set_position(&surface->scene_surface->buffer->node, + pos + border_width, pos + border_width); +} + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + + char *startup_cmd = NULL; + + int c; + while ((c = getopt(argc, argv, "s:")) != -1) { + switch (c) { + case 's': + startup_cmd = optarg; + break; + default: + printf("usage: %s [-s startup-command]\n", argv[0]); + return EXIT_FAILURE; + } + } + if (optind < argc) { + printf("usage: %s [-s startup-command]\n", argv[0]); + return EXIT_FAILURE; + } + + struct server server = {0}; + server.surface_offset = 0; + server.display = wl_display_create(); + server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.display), NULL); + server.scene = wlr_scene_create(); + + server.renderer = wlr_renderer_autocreate(server.backend); + wlr_renderer_init_wl_display(server.renderer, server.display); + + server.allocator = wlr_allocator_autocreate(server.backend, + server.renderer); + + struct wlr_compositor *compositor = + wlr_compositor_create(server.display, 5, server.renderer); + + wlr_xdg_shell_create(server.display, 2); + + server.new_output.notify = server_handle_new_output; + wl_signal_add(&server.backend->events.new_output, &server.new_output); + + server.new_surface.notify = server_handle_new_surface; + wl_signal_add(&compositor->events.new_surface, &server.new_surface); + + const char *socket = wl_display_add_socket_auto(server.display); + if (!socket) { + wl_display_destroy(server.display); + return EXIT_FAILURE; + } + + if (!wlr_backend_start(server.backend)) { + wl_display_destroy(server.display); + return EXIT_FAILURE; + } + + setenv("WAYLAND_DISPLAY", socket, true); + if (startup_cmd != NULL) { + if (fork() == 0) { + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL); + } + } + + wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", + socket); + wl_display_run(server.display); + + wl_display_destroy_clients(server.display); + wl_display_destroy(server.display); + return EXIT_SUCCESS; +} diff --git a/examples/simple.c b/examples/simple.c new file mode 100644 index 0000000..81ed2e0 --- /dev/null +++ b/examples/simple.c @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct sample_state { + struct wl_display *display; + struct wl_listener new_output; + struct wl_listener new_input; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + struct timespec last_frame; + float color[4]; + int dec; +}; + +struct sample_output { + struct sample_state *sample; + struct wlr_output *output; + struct wl_listener frame; + struct wl_listener destroy; +}; + +struct sample_keyboard { + struct sample_state *sample; + struct wlr_keyboard *wlr_keyboard; + struct wl_listener key; + struct wl_listener destroy; +}; + +static void output_frame_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = + wl_container_of(listener, sample_output, frame); + struct sample_state *sample = sample_output->sample; + struct wlr_output *wlr_output = sample_output->output; + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + long ms = (now.tv_sec - sample->last_frame.tv_sec) * 1000 + + (now.tv_nsec - sample->last_frame.tv_nsec) / 1000000; + int inc = (sample->dec + 1) % 3; + + sample->color[inc] += ms / 2000.0f; + sample->color[sample->dec] -= ms / 2000.0f; + + if (sample->color[sample->dec] < 0.0f) { + sample->color[inc] = 1.0f; + sample->color[sample->dec] = 0.0f; + sample->dec = inc; + } + + struct wlr_output_state state; + wlr_output_state_init(&state); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &state, NULL, NULL); + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .box = { .width = wlr_output->width, .height = wlr_output->height }, + .color = { + .r = sample->color[0], + .g = sample->color[1], + .b = sample->color[2], + .a = sample->color[3], + }, + }); + wlr_render_pass_submit(pass); + + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + sample->last_frame = now; +} + +static void output_remove_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = + wl_container_of(listener, sample_output, destroy); + wlr_log(WLR_DEBUG, "Output removed"); + wl_list_remove(&sample_output->frame.link); + wl_list_remove(&sample_output->destroy.link); + free(sample_output); +} + +static void new_output_notify(struct wl_listener *listener, void *data) { + struct wlr_output *output = data; + struct sample_state *sample = + wl_container_of(listener, sample, new_output); + + wlr_output_init_render(output, sample->allocator, sample->renderer); + + struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); + sample_output->output = output; + sample_output->sample = sample; + wl_signal_add(&output->events.frame, &sample_output->frame); + sample_output->frame.notify = output_frame_notify; + wl_signal_add(&output->events.destroy, &sample_output->destroy); + sample_output->destroy.notify = output_remove_notify; + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + struct wlr_output_mode *mode = wlr_output_preferred_mode(output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state(output, &state); + wlr_output_state_finish(&state); +} + +static void keyboard_key_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key); + struct sample_state *sample = keyboard->sample; + struct wlr_keyboard_key_event *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state, + keycode, &syms); + for (int i = 0; i < nsyms; i++) { + xkb_keysym_t sym = syms[i]; + if (sym == XKB_KEY_Escape) { + wl_display_terminate(sample->display); + } + } +} + +static void keyboard_destroy_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = + wl_container_of(listener, keyboard, destroy); + wl_list_remove(&keyboard->destroy.link); + wl_list_remove(&keyboard->key.link); + free(keyboard); +} + +static void new_input_notify(struct wl_listener *listener, void *data) { + struct wlr_input_device *device = data; + struct sample_state *sample = wl_container_of(listener, sample, new_input); + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD:; + struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard)); + keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device); + keyboard->sample = sample; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) { + wlr_log(WLR_ERROR, "Failed to create XKB context"); + exit(1); + } + struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + break; + default: + break; + } +} + +int main(void) { + wlr_log_init(WLR_DEBUG, NULL); + struct wl_display *display = wl_display_create(); + struct sample_state state = { + .color = { 1.0, 0.0, 0.0, 1.0 }, + .dec = 0, + .last_frame = { 0 }, + .display = display + }; + struct wlr_backend *backend = wlr_backend_autocreate(wl_display_get_event_loop(display), NULL); + if (!backend) { + exit(1); + } + + state.renderer = wlr_renderer_autocreate(backend); + state.allocator = wlr_allocator_autocreate(backend, state.renderer); + + wl_signal_add(&backend->events.new_output, &state.new_output); + state.new_output.notify = new_output_notify; + wl_signal_add(&backend->events.new_input, &state.new_input); + state.new_input.notify = new_input_notify; + clock_gettime(CLOCK_MONOTONIC, &state.last_frame); + + if (!wlr_backend_start(backend)) { + wlr_log(WLR_ERROR, "Failed to start backend"); + wlr_backend_destroy(backend); + exit(1); + } + wl_display_run(display); + wl_display_destroy(display); +} diff --git a/examples/tablet.c b/examples/tablet.c new file mode 100644 index 0000000..8f0754d --- /dev/null +++ b/examples/tablet.c @@ -0,0 +1,416 @@ +#undef _POSIX_C_SOURCE +#define _XOPEN_SOURCE 600 // for M_PI +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct sample_state { + struct wl_display *display; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + bool proximity, tap, button; + double distance; + double pressure; + double x, y; + double x_tilt, y_tilt; + double width_mm, height_mm; + double ring; + struct wl_list link; + float tool_color[4]; + float pad_color[4]; + struct timespec last_frame; + struct wl_listener new_output; + struct wl_listener new_input; + struct wl_list tablet_tools; + struct wl_list tablet_pads; +}; + +struct tablet_tool_state { + struct sample_state *sample; + struct wlr_tablet *wlr_tablet; + struct wl_listener destroy; + struct wl_listener axis; + struct wl_listener proximity; + struct wl_listener tip; + struct wl_listener button; + struct wl_list link; + void *data; +}; + +struct tablet_pad_state { + struct sample_state *sample; + struct wlr_tablet_pad *wlr_tablet_pad; + struct wl_listener destroy; + struct wl_listener button; + struct wl_listener ring; + struct wl_list link; + void *data; +}; + +struct sample_output { + struct sample_state *sample; + struct wlr_output *output; + struct wl_listener frame; + struct wl_listener destroy; +}; + +struct sample_keyboard { + struct sample_state *sample; + struct wlr_keyboard *wlr_keyboard; + struct wl_listener key; + struct wl_listener destroy; +}; + +static void output_frame_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = wl_container_of(listener, sample_output, frame); + struct sample_state *sample = sample_output->sample; + struct wlr_output *wlr_output = sample_output->output; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + int32_t width, height; + wlr_output_effective_resolution(wlr_output, &width, &height); + + struct wlr_output_state output_state; + wlr_output_state_init(&output_state); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL); + + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .box = { .width = wlr_output->width, .height = wlr_output->height }, + .color = { 0.25, 0.25, 0.25, 1 }, + }); + + float distance = 0.8f * (1 - sample->distance); + float tool_color[4] = { distance, distance, distance, 1 }; + for (size_t i = 0; sample->button && i < 4; ++i) { + tool_color[i] = sample->tool_color[i]; + } + float scale = 4; + + float pad_width = sample->width_mm * scale; + float pad_height = sample->height_mm * scale; + float left = width / 2.0f - pad_width / 2.0f; + float top = height / 2.0f - pad_height / 2.0f; + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .box = { + .x = left, + .y = top, + .width = pad_width, + .height = pad_height, + }, + .color = { + sample->pad_color[0], + sample->pad_color[1], + sample->pad_color[2], + sample->pad_color[3], + }, + }); + + if (sample->proximity) { + struct wlr_box box = { + .x = (sample->x * pad_width) - 8 * (sample->pressure + 1) + left, + .y = (sample->y * pad_height) - 8 * (sample->pressure + 1) + top, + .width = 16 * (sample->pressure + 1), + .height = 16 * (sample->pressure + 1), + }; + + // TODO: use sample->ring + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .box = box, + .color = { + tool_color[0], + tool_color[1], + tool_color[2], + tool_color[3], + }, + }); + + box.x += sample->x_tilt; + box.y += sample->y_tilt; + box.width /= 2; + box.height /= 2; + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .box = box, + .color = { + tool_color[0], + tool_color[1], + tool_color[2], + tool_color[3], + }, + }); + } + + wlr_render_pass_submit(pass); + wlr_output_commit_state(wlr_output, &output_state); + wlr_output_state_finish(&output_state); + sample->last_frame = now; +} + +static void tablet_tool_axis_notify(struct wl_listener *listener, void *data) { + struct tablet_tool_state *tstate = wl_container_of(listener, tstate, axis); + struct wlr_tablet_tool_axis_event *event = data; + struct sample_state *sample = tstate->sample; + if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X)) { + sample->x = event->x; + } + if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) { + sample->y = event->y; + } + if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE)) { + sample->distance = event->distance; + } + if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE)) { + sample->pressure = event->pressure; + } + if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X)) { + sample->x_tilt = event->tilt_x; + } + if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y)) { + sample->y_tilt = event->tilt_y; + } +} + +static void tablet_tool_proximity_notify(struct wl_listener *listener, void *data) { + struct tablet_tool_state *tstate = wl_container_of(listener, tstate, proximity); + struct wlr_tablet_tool_proximity_event *event = data; + struct sample_state *sample = tstate->sample; + sample->proximity = event->state == WLR_TABLET_TOOL_PROXIMITY_IN; +} + +static void tablet_tool_button_notify(struct wl_listener *listener, void *data) { + struct tablet_tool_state *tstate = wl_container_of(listener, tstate, button); + struct wlr_tablet_tool_button_event *event = data; + struct sample_state *sample = tstate->sample; + if (event->state == WLR_BUTTON_RELEASED) { + sample->button = false; + } else { + sample->button = true; + for (size_t i = 0; i < 3; ++i) { + if (event->button % 3 == i) { + sample->tool_color[i] = 0; + } else { + sample->tool_color[i] = 1; + } + } + } +} + +static void tablet_pad_button_notify(struct wl_listener *listener, void *data) { + struct tablet_pad_state *pstate = wl_container_of(listener, pstate, button); + struct wlr_tablet_pad_button_event *event = data; + struct sample_state *sample = pstate->sample; + float default_color[4] = { 0.5, 0.5, 0.5, 1.0 }; + if (event->state == WLR_BUTTON_RELEASED) { + memcpy(sample->pad_color, default_color, sizeof(default_color)); + } else { + for (size_t i = 0; i < 3; ++i) { + if (event->button % 3 == i) { + sample->pad_color[i] = 0; + } else { + sample->pad_color[i] = 1; + } + } + } +} + +static void tablet_pad_ring_notify(struct wl_listener *listener, void *data) { + struct tablet_pad_state *pstate = wl_container_of(listener, pstate, ring); + struct wlr_tablet_pad_ring_event *event = data; + struct sample_state *sample = pstate->sample; + if (event->position != -1) { + sample->ring = -(event->position * (M_PI / 180.0)); + } +} + +static void tablet_tool_destroy_notify(struct wl_listener *listener, void *data) { + struct tablet_tool_state *tstate = wl_container_of(listener, tstate, destroy); + wl_list_remove(&tstate->link); + wl_list_remove(&tstate->destroy.link); + wl_list_remove(&tstate->axis.link); + wl_list_remove(&tstate->proximity.link); + wl_list_remove(&tstate->button.link); + free(tstate); +} + +static void tablet_pad_destroy_notify(struct wl_listener *listener, void *data) { + struct tablet_pad_state *pstate = wl_container_of(listener, pstate, destroy); + wl_list_remove(&pstate->link); + wl_list_remove(&pstate->destroy.link); + wl_list_remove(&pstate->ring.link); + wl_list_remove(&pstate->button.link); + free(pstate); +} + +static void output_remove_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy); + wl_list_remove(&sample_output->frame.link); + wl_list_remove(&sample_output->destroy.link); + free(sample_output); +} + +static void new_output_notify(struct wl_listener *listener, void *data) { + struct wlr_output *output = data; + struct sample_state *sample = wl_container_of(listener, sample, new_output); + + wlr_output_init_render(output, sample->allocator, sample->renderer); + + struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); + sample_output->output = output; + sample_output->sample = sample; + wl_signal_add(&output->events.frame, &sample_output->frame); + sample_output->frame.notify = output_frame_notify; + wl_signal_add(&output->events.destroy, &sample_output->destroy); + sample_output->destroy.notify = output_remove_notify; + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + struct wlr_output_mode *mode = wlr_output_preferred_mode(output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state(output, &state); + wlr_output_state_finish(&state); +} + +static void keyboard_key_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key); + struct sample_state *sample = keyboard->sample; + struct wlr_keyboard_key_event *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state, + keycode, &syms); + for (int i = 0; i < nsyms; i++) { + xkb_keysym_t sym = syms[i]; + if (sym == XKB_KEY_Escape) { + wl_display_terminate(sample->display); + } + } +} + +static void keyboard_destroy_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, destroy); + wl_list_remove(&keyboard->destroy.link); + wl_list_remove(&keyboard->key.link); + free(keyboard); +} + +static void new_input_notify(struct wl_listener *listener, void *data) { + struct wlr_input_device *device = data; + struct sample_state *sample = wl_container_of(listener, sample, new_input); + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD:; + struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard)); + keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device); + keyboard->sample = sample; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) { + wlr_log(WLR_ERROR, "Failed to create XKB context"); + exit(1); + } + struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + break; + case WLR_INPUT_DEVICE_TABLET_PAD:; + struct tablet_pad_state *pstate = calloc(1, sizeof(*pstate)); + pstate->wlr_tablet_pad = wlr_tablet_pad_from_input_device(device); + pstate->sample = sample; + pstate->destroy.notify = tablet_pad_destroy_notify; + wl_signal_add(&device->events.destroy, &pstate->destroy); + pstate->button.notify = tablet_pad_button_notify; + wl_signal_add(&pstate->wlr_tablet_pad->events.button, &pstate->button); + pstate->ring.notify = tablet_pad_ring_notify; + wl_signal_add(&pstate->wlr_tablet_pad->events.ring, &pstate->ring); + wl_list_insert(&sample->tablet_pads, &pstate->link); + break; + case WLR_INPUT_DEVICE_TABLET:; + struct wlr_tablet *tablet = wlr_tablet_from_input_device(device); + sample->width_mm = tablet->width_mm == 0 ? + 20 : tablet->width_mm; + sample->height_mm = tablet->height_mm == 0 ? + 10 : tablet->height_mm; + + struct tablet_tool_state *tstate = calloc(1, sizeof(*tstate)); + tstate->wlr_tablet = tablet; + tstate->sample = sample; + tstate->destroy.notify = tablet_tool_destroy_notify; + wl_signal_add(&device->events.destroy, &tstate->destroy); + tstate->axis.notify = tablet_tool_axis_notify; + wl_signal_add(&tablet->events.axis, &tstate->axis); + tstate->proximity.notify = tablet_tool_proximity_notify; + wl_signal_add(&tablet->events.proximity, &tstate->proximity); + tstate->button.notify = tablet_tool_button_notify; + wl_signal_add(&tablet->events.button, &tstate->button); + wl_list_insert(&sample->tablet_tools, &tstate->link); + break; + default: + break; + } +} + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + struct wl_display *display = wl_display_create(); + struct sample_state state = { + .display = display, + .tool_color = { 1, 1, 1, 1 }, + .pad_color = { 0.5, 0.5, 0.5, 1.0 } + }; + wl_list_init(&state.tablet_pads); + wl_list_init(&state.tablet_tools); + struct wlr_backend *wlr = wlr_backend_autocreate(wl_display_get_event_loop(display), NULL); + if (!wlr) { + exit(1); + } + + wl_signal_add(&wlr->events.new_output, &state.new_output); + state.new_output.notify = new_output_notify; + wl_signal_add(&wlr->events.new_input, &state.new_input); + state.new_input.notify = new_input_notify; + clock_gettime(CLOCK_MONOTONIC, &state.last_frame); + + state.renderer = wlr_renderer_autocreate(wlr); + if (!state.renderer) { + wlr_log(WLR_ERROR, "Could not start compositor, OOM"); + exit(EXIT_FAILURE); + } + + state.allocator = wlr_allocator_autocreate(wlr, state.renderer); + + if (!wlr_backend_start(wlr)) { + wlr_log(WLR_ERROR, "Failed to start backend"); + wlr_backend_destroy(wlr); + exit(1); + } + wl_display_run(display); + + wl_display_destroy(display); +} diff --git a/examples/touch.c b/examples/touch.c new file mode 100644 index 0000000..e1bd738 --- /dev/null +++ b/examples/touch.c @@ -0,0 +1,311 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cat.h" + +struct sample_state { + struct wl_display *display; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + struct wlr_texture *cat_texture; + struct wl_list touch_points; + struct timespec last_frame; + struct wl_listener new_output; + struct wl_listener new_input; + struct wl_list touch; +}; + +struct touch_point { + int32_t touch_id; + double x, y; + struct wl_list link; +}; + +struct touch_state { + struct sample_state *sample; + struct wlr_touch *wlr_touch; + struct wl_listener destroy; + struct wl_listener down; + struct wl_listener up; + struct wl_listener motion; + struct wl_listener cancel; + struct wl_list link; + void *data; +}; + +struct sample_output { + struct sample_state *sample; + struct wlr_output *output; + struct wl_listener frame; + struct wl_listener destroy; +}; + +struct sample_keyboard { + struct sample_state *sample; + struct wlr_keyboard *wlr_keyboard; + struct wl_listener key; + struct wl_listener destroy; +}; + +static void output_frame_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = wl_container_of(listener, sample_output, frame); + struct sample_state *sample = sample_output->sample; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + struct wlr_output *wlr_output = sample_output->output; + + int32_t width, height; + wlr_output_effective_resolution(wlr_output, &width, &height); + + struct wlr_output_state output_state; + wlr_output_state_init(&output_state); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL); + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .box = { .width = width, .height = height }, + .color = { 0.25, 0.25, 0.25, 1 }, + }); + + struct touch_point *p; + wl_list_for_each(p, &sample->touch_points, link) { + int x = (int)(p->x * width) - sample->cat_texture->width / 2; + int y = (int)(p->y * height) - sample->cat_texture->height / 2; + wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){ + .texture = sample->cat_texture, + .dst_box = { .x = x, .y = y }, + }); + } + + wlr_render_pass_submit(pass); + wlr_output_commit_state(wlr_output, &output_state); + wlr_output_state_finish(&output_state); + sample->last_frame = now; +} + +static void touch_down_notify(struct wl_listener *listener, void *data) { + struct wlr_touch_down_event *event = data; + struct touch_state *tstate = wl_container_of(listener, tstate, down); + struct sample_state *sample = tstate->sample; + struct touch_point *point = calloc(1, sizeof(*point)); + point->touch_id = event->touch_id; + point->x = event->x; + point->y = event->y; + wl_list_insert(&sample->touch_points, &point->link); +} + +static void touch_up_notify(struct wl_listener *listener, void *data ) { + struct wlr_touch_up_event *event = data; + struct touch_state *tstate = wl_container_of(listener, tstate, up); + struct sample_state *sample = tstate->sample; + struct touch_point *point, *tmp; + wl_list_for_each_safe(point, tmp, &sample->touch_points, link) { + if (point->touch_id == event->touch_id) { + wl_list_remove(&point->link); + break; + } + } +} + +static void touch_motion_notify(struct wl_listener *listener, void *data) { + struct wlr_touch_motion_event *event = data; + struct touch_state *tstate = wl_container_of(listener, tstate, motion); + struct sample_state *sample = tstate->sample; + struct touch_point *point; + wl_list_for_each(point, &sample->touch_points, link) { + if (point->touch_id == event->touch_id) { + point->x = event->x; + point->y = event->y; + break; + } + } +} + +static void touch_cancel_notify(struct wl_listener *listener, void *data) { + struct wlr_touch_cancel_event *event = data; + struct touch_state *tstate = wl_container_of(listener, tstate, cancel); + struct sample_state *sample = tstate->sample; + struct touch_point *point, *tmp; + wl_list_for_each_safe(point, tmp, &sample->touch_points, link) { + if (point->touch_id == event->touch_id) { + wl_list_remove(&point->link); + break; + } + } +} + +static void touch_destroy_notify(struct wl_listener *listener, void *data) { + struct touch_state *tstate = wl_container_of(listener, tstate, destroy); + wl_list_remove(&tstate->link); + wl_list_remove(&tstate->destroy.link); + wl_list_remove(&tstate->down.link); + wl_list_remove(&tstate->up.link); + wl_list_remove(&tstate->motion.link); + wl_list_remove(&tstate->cancel.link); + free(tstate); +} + +static void output_remove_notify(struct wl_listener *listener, void *data) { + struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy); + wl_list_remove(&sample_output->frame.link); + wl_list_remove(&sample_output->destroy.link); + free(sample_output); +} + +static void new_output_notify(struct wl_listener *listener, void *data) { + struct wlr_output *output = data; + struct sample_state *sample = wl_container_of(listener, sample, new_output); + + wlr_output_init_render(output, sample->allocator, sample->renderer); + + struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); + sample_output->output = output; + sample_output->sample = sample; + wl_signal_add(&output->events.frame, &sample_output->frame); + sample_output->frame.notify = output_frame_notify; + wl_signal_add(&output->events.destroy, &sample_output->destroy); + sample_output->destroy.notify = output_remove_notify; + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + struct wlr_output_mode *mode = wlr_output_preferred_mode(output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + wlr_output_commit_state(output, &state); + wlr_output_state_finish(&state); +} + +static void keyboard_key_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key); + struct sample_state *sample = keyboard->sample; + struct wlr_keyboard_key_event *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state, + keycode, &syms); + for (int i = 0; i < nsyms; i++) { + xkb_keysym_t sym = syms[i]; + if (sym == XKB_KEY_Escape) { + wl_display_terminate(sample->display); + } + } +} + +static void keyboard_destroy_notify(struct wl_listener *listener, void *data) { + struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, destroy); + wl_list_remove(&keyboard->destroy.link); + wl_list_remove(&keyboard->key.link); + free(keyboard); +} + +static void new_input_notify(struct wl_listener *listener, void *data) { + struct wlr_input_device *device = data; + struct sample_state *sample = wl_container_of(listener, sample, new_input); + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD:; + struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard)); + keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device); + keyboard->sample = sample; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) { + wlr_log(WLR_ERROR, "Failed to create XKB context"); + exit(1); + } + struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + break; + case WLR_INPUT_DEVICE_TOUCH:; + struct touch_state *tstate = calloc(1, sizeof(*tstate)); + tstate->wlr_touch = wlr_touch_from_input_device(device); + tstate->sample = sample; + tstate->destroy.notify = touch_destroy_notify; + wl_signal_add(&device->events.destroy, &tstate->destroy); + tstate->down.notify = touch_down_notify; + wl_signal_add(&tstate->wlr_touch->events.down, &tstate->down); + tstate->motion.notify = touch_motion_notify; + wl_signal_add(&tstate->wlr_touch->events.motion, &tstate->motion); + tstate->up.notify = touch_up_notify; + wl_signal_add(&tstate->wlr_touch->events.up, &tstate->up); + tstate->cancel.notify = touch_cancel_notify; + wl_signal_add(&tstate->wlr_touch->events.cancel, &tstate->cancel); + wl_list_insert(&sample->touch, &tstate->link); + break; + default: + break; + } +} + + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + struct wl_display *display = wl_display_create(); + struct sample_state state = { + .display = display + }; + wl_list_init(&state.touch_points); + wl_list_init(&state.touch); + + struct wlr_backend *wlr = wlr_backend_autocreate(wl_display_get_event_loop(display), NULL); + if (!wlr) { + exit(1); + } + + wl_signal_add(&wlr->events.new_output, &state.new_output); + state.new_output.notify = new_output_notify; + wl_signal_add(&wlr->events.new_input, &state.new_input); + state.new_input.notify = new_input_notify; + clock_gettime(CLOCK_MONOTONIC, &state.last_frame); + + state.renderer = wlr_renderer_autocreate(wlr); + if (!state.renderer) { + wlr_log(WLR_ERROR, "Could not start compositor, OOM"); + exit(EXIT_FAILURE); + } + state.cat_texture = wlr_texture_from_pixels(state.renderer, + DRM_FORMAT_ARGB8888, cat_tex.width * 4, cat_tex.width, cat_tex.height, + cat_tex.pixel_data); + if (!state.cat_texture) { + wlr_log(WLR_ERROR, "Could not start compositor, OOM"); + exit(EXIT_FAILURE); + } + + state.allocator = wlr_allocator_autocreate(wlr, state.renderer); + + if (!wlr_backend_start(wlr)) { + wlr_log(WLR_ERROR, "Failed to start backend"); + wlr_backend_destroy(wlr); + exit(1); + } + wl_display_run(display); + + wlr_texture_destroy(state.cat_texture); + wl_display_destroy(display); +} diff --git a/include/backend/backend.h b/include/backend/backend.h new file mode 100644 index 0000000..8c7440c --- /dev/null +++ b/include/backend/backend.h @@ -0,0 +1,13 @@ +#ifndef BACKEND_WLR_BACKEND_H +#define BACKEND_WLR_BACKEND_H + +#include + +/** + * Get the supported buffer capabilities. + * + * This functions returns a bitfield of supported wlr_buffer_cap. + */ +uint32_t backend_get_buffer_caps(struct wlr_backend *backend); + +#endif diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h new file mode 100644 index 0000000..675c5db --- /dev/null +++ b/include/backend/drm/drm.h @@ -0,0 +1,208 @@ +#ifndef BACKEND_DRM_DRM_H +#define BACKEND_DRM_DRM_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/drm/iface.h" +#include "backend/drm/properties.h" +#include "backend/drm/renderer.h" + +struct wlr_drm_plane { + uint32_t type; + uint32_t id; + + /* Only initialized on multi-GPU setups */ + struct wlr_drm_surface mgpu_surf; + + /* Buffer submitted to the kernel, will be presented on next vblank */ + struct wlr_drm_fb *queued_fb; + /* Buffer currently displayed on screen */ + struct wlr_drm_fb *current_fb; + + struct wlr_drm_format_set formats; + + union wlr_drm_plane_props props; + + uint32_t initial_crtc_id; + struct liftoff_plane *liftoff; + struct liftoff_layer *liftoff_layer; +}; + +struct wlr_drm_layer { + struct wlr_output_layer *wlr; + struct liftoff_layer *liftoff; + struct wlr_addon addon; // wlr_output_layer.addons + struct wl_list link; // wlr_drm_crtc.layers + + /* Buffer to be submitted to the kernel on the next page-flip */ + struct wlr_drm_fb *pending_fb; + /* Buffer submitted to the kernel, will be presented on next vblank */ + struct wlr_drm_fb *queued_fb; + /* Buffer currently displayed on screen */ + struct wlr_drm_fb *current_fb; + + // One entry per wlr_drm_backend.planes + bool *candidate_planes; +}; + +struct wlr_drm_crtc { + uint32_t id; + struct wlr_drm_lease *lease; + struct liftoff_output *liftoff; + struct liftoff_layer *liftoff_composition_layer; + struct wl_list layers; // wlr_drm_layer.link + + // Atomic modesetting only + bool own_mode_id; + uint32_t mode_id; + uint32_t gamma_lut; + + // Legacy only + int legacy_gamma_size; + + struct wlr_drm_plane *primary; + struct wlr_drm_plane *cursor; + + union wlr_drm_crtc_props props; +}; + +struct wlr_drm_backend { + struct wlr_backend backend; + + struct wlr_drm_backend *parent; + const struct wlr_drm_interface *iface; + bool addfb2_modifiers; + + int fd; + char *name; + struct wlr_device *dev; + struct liftoff_device *liftoff; + + size_t num_crtcs; + struct wlr_drm_crtc *crtcs; + + size_t num_planes; + struct wlr_drm_plane *planes; + + struct wl_event_source *drm_event; + + struct wl_listener session_destroy; + struct wl_listener session_active; + struct wl_listener parent_destroy; + struct wl_listener dev_change; + struct wl_listener dev_remove; + + struct wl_list fbs; // wlr_drm_fb.link + struct wl_list connectors; // wlr_drm_connector.link + + struct wl_list page_flips; // wlr_drm_page_flip.link + + /* Only initialized on multi-GPU setups */ + struct wlr_drm_renderer mgpu_renderer; + + struct wlr_session *session; + + uint64_t cursor_width, cursor_height; + + struct wlr_drm_format_set mgpu_formats; + + bool supports_tearing_page_flips; +}; + +struct wlr_drm_mode { + struct wlr_output_mode wlr_mode; + drmModeModeInfo drm_mode; +}; + +struct wlr_drm_connector_state { + const struct wlr_output_state *base; + bool modeset; + bool nonblock; + bool active; + drmModeModeInfo mode; + struct wlr_drm_fb *primary_fb; + struct wlr_drm_fb *cursor_fb; +}; + +/** + * Per-page-flip tracking struct. + * + * We've asked for a state change in the kernel, and yet to receive a + * notification for its completion. Currently, the kernel only has a queue + * length of 1, and no way to modify your submissions after they're sent. + * + * However, we might have multiple in-flight page-flip events, for instance + * when performing a non-blocking commit followed by a blocking commit. In + * that case, conn will be set to NULL on the non-blocking commit to indicate + * that it's been superseded. + */ +struct wlr_drm_page_flip { + struct wl_list link; // wlr_drm_connector.page_flips + struct wlr_drm_connector *conn; +}; + +struct wlr_drm_connector { + struct wlr_output output; // only valid if status != DISCONNECTED + + struct wlr_drm_backend *backend; + char name[24]; + drmModeConnection status; + uint32_t id; + uint64_t max_bpc_bounds[2]; + struct wlr_drm_lease *lease; + + struct wlr_drm_crtc *crtc; + uint32_t possible_crtcs; + + union wlr_drm_connector_props props; + + bool cursor_enabled; + int cursor_x, cursor_y; + int cursor_width, cursor_height; + int cursor_hotspot_x, cursor_hotspot_y; + /* Buffer to be submitted to the kernel on the next page-flip */ + struct wlr_drm_fb *cursor_pending_fb; + + struct wl_list link; // wlr_drm_backend.connectors + + // Last committed page-flip + struct wlr_drm_page_flip *pending_page_flip; + + int32_t refresh; +}; + +struct wlr_drm_backend *get_drm_backend_from_backend( + struct wlr_backend *wlr_backend); +bool check_drm_features(struct wlr_drm_backend *drm); +bool init_drm_resources(struct wlr_drm_backend *drm); +void finish_drm_resources(struct wlr_drm_backend *drm); +void scan_drm_connectors(struct wlr_drm_backend *state, + struct wlr_device_hotplug_event *event); +void scan_drm_leases(struct wlr_drm_backend *drm); +void restore_drm_device(struct wlr_drm_backend *drm); +int handle_drm_event(int fd, uint32_t mask, void *data); +void destroy_drm_connector(struct wlr_drm_connector *conn); +bool drm_connector_is_cursor_visible(struct wlr_drm_connector *conn); +bool drm_connector_supports_vrr(struct wlr_drm_connector *conn); +size_t drm_crtc_get_gamma_lut_size(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc); +void drm_lease_destroy(struct wlr_drm_lease *lease); +void drm_page_flip_destroy(struct wlr_drm_page_flip *page_flip); + +struct wlr_drm_layer *get_drm_layer(struct wlr_drm_backend *drm, + struct wlr_output_layer *layer); + +#define wlr_drm_conn_log(conn, verb, fmt, ...) \ + wlr_log(verb, "connector %s: " fmt, conn->name, ##__VA_ARGS__) +#define wlr_drm_conn_log_errno(conn, verb, fmt, ...) \ + wlr_log_errno(verb, "connector %s: " fmt, conn->name, ##__VA_ARGS__) + +#endif diff --git a/include/backend/drm/fb.h b/include/backend/drm/fb.h new file mode 100644 index 0000000..91d0fff --- /dev/null +++ b/include/backend/drm/fb.h @@ -0,0 +1,24 @@ +#ifndef BACKEND_DRM_FB_H +#define BACKEND_DRM_FB_H + +#include + +struct wlr_drm_fb { + struct wlr_buffer *wlr_buf; + struct wlr_addon addon; + struct wlr_drm_backend *backend; + struct wl_list link; // wlr_drm_backend.fbs + + uint32_t id; +}; + +bool drm_fb_import(struct wlr_drm_fb **fb, struct wlr_drm_backend *drm, + struct wlr_buffer *buf, const struct wlr_drm_format_set *formats); +void drm_fb_destroy(struct wlr_drm_fb *fb); + +void drm_fb_clear(struct wlr_drm_fb **fb); +void drm_fb_copy(struct wlr_drm_fb **new, struct wlr_drm_fb *old); +void drm_fb_move(struct wlr_drm_fb **new, struct wlr_drm_fb **old); +struct wlr_drm_fb *drm_fb_lock(struct wlr_drm_fb *fb); + +#endif diff --git a/include/backend/drm/iface.h b/include/backend/drm/iface.h new file mode 100644 index 0000000..6408c44 --- /dev/null +++ b/include/backend/drm/iface.h @@ -0,0 +1,44 @@ +#ifndef BACKEND_DRM_IFACE_H +#define BACKEND_DRM_IFACE_H + +#include +#include +#include +#include +#include + +struct wlr_drm_backend; +struct wlr_drm_connector; +struct wlr_drm_crtc; +struct wlr_drm_connector_state; +struct wlr_drm_fb; +struct wlr_drm_page_flip; + +// Used to provide atomic or legacy DRM functions +struct wlr_drm_interface { + bool (*init)(struct wlr_drm_backend *drm); + void (*finish)(struct wlr_drm_backend *drm); + // Commit all pending changes on a CRTC. + bool (*crtc_commit)(struct wlr_drm_connector *conn, + const struct wlr_drm_connector_state *state, + struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only); + // Turn off everything + bool (*reset)(struct wlr_drm_backend *drm); +}; + +extern const struct wlr_drm_interface atomic_iface; +extern const struct wlr_drm_interface legacy_iface; +extern const struct wlr_drm_interface liftoff_iface; + +bool drm_legacy_crtc_set_gamma(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, size_t size, uint16_t *lut); + +bool create_mode_blob(struct wlr_drm_connector *conn, + const struct wlr_drm_connector_state *state, uint32_t *blob_id); +bool create_gamma_lut_blob(struct wlr_drm_backend *drm, + size_t size, const uint16_t *lut, uint32_t *blob_id); +bool create_fb_damage_clips_blob(struct wlr_drm_backend *drm, + int width, int height, const pixman_region32_t *damage, uint32_t *blob_id); +bool drm_atomic_reset(struct wlr_drm_backend *drm); + +#endif diff --git a/include/backend/drm/monitor.h b/include/backend/drm/monitor.h new file mode 100644 index 0000000..5181719 --- /dev/null +++ b/include/backend/drm/monitor.h @@ -0,0 +1,24 @@ +#ifndef BACKEND_DRM_MONITOR_H +#define BACKEND_DRM_MONITOR_H + +#include + +/** + * Helper to create new DRM sub-backends on GPU hotplug. + */ +struct wlr_drm_backend_monitor { + struct wlr_backend *multi; + struct wlr_backend *primary_drm; + struct wlr_session *session; + + struct wl_listener multi_destroy; + struct wl_listener primary_drm_destroy; + struct wl_listener session_destroy; + struct wl_listener session_add_drm_card; +}; + +struct wlr_drm_backend_monitor *drm_backend_monitor_create( + struct wlr_backend *multi, struct wlr_backend *primary_drm, + struct wlr_session *session); + +#endif diff --git a/include/backend/drm/properties.h b/include/backend/drm/properties.h new file mode 100644 index 0000000..4ce9576 --- /dev/null +++ b/include/backend/drm/properties.h @@ -0,0 +1,84 @@ +#ifndef BACKEND_DRM_PROPERTIES_H +#define BACKEND_DRM_PROPERTIES_H + +#include +#include +#include + +/* + * These types contain the property ids for several DRM objects. + * For more details, see: + * https://dri.freedesktop.org/docs/drm/gpu/drm-kms.html#kms-properties + */ + +union wlr_drm_connector_props { + struct { + uint32_t edid; + uint32_t dpms; + uint32_t link_status; // not guaranteed to exist + uint32_t path; + uint32_t vrr_capable; // not guaranteed to exist + uint32_t subconnector; // not guaranteed to exist + uint32_t non_desktop; + uint32_t panel_orientation; // not guaranteed to exist + uint32_t content_type; // not guaranteed to exist + uint32_t max_bpc; // not guaranteed to exist + + // atomic-modesetting only + + uint32_t crtc_id; + }; + uint32_t props[4]; +}; + +union wlr_drm_crtc_props { + struct { + // Neither of these are guaranteed to exist + uint32_t vrr_enabled; + uint32_t gamma_lut; + uint32_t gamma_lut_size; + + // atomic-modesetting only + + uint32_t active; + uint32_t mode_id; + }; + uint32_t props[6]; +}; + +union wlr_drm_plane_props { + struct { + uint32_t type; + uint32_t rotation; // Not guaranteed to exist + uint32_t in_formats; // Not guaranteed to exist + + // atomic-modesetting only + + uint32_t src_x; + uint32_t src_y; + uint32_t src_w; + uint32_t src_h; + uint32_t crtc_x; + uint32_t crtc_y; + uint32_t crtc_w; + uint32_t crtc_h; + uint32_t fb_id; + uint32_t crtc_id; + uint32_t fb_damage_clips; + }; + uint32_t props[14]; +}; + +bool get_drm_connector_props(int fd, uint32_t id, + union wlr_drm_connector_props *out); +bool get_drm_crtc_props(int fd, uint32_t id, union wlr_drm_crtc_props *out); +bool get_drm_plane_props(int fd, uint32_t id, union wlr_drm_plane_props *out); + +bool get_drm_prop(int fd, uint32_t obj, uint32_t prop, uint64_t *ret); +void *get_drm_prop_blob(int fd, uint32_t obj, uint32_t prop, size_t *ret_len); +char *get_drm_prop_enum(int fd, uint32_t obj, uint32_t prop); + +bool introspect_drm_prop_range(int fd, uint32_t prop_id, + uint64_t *min, uint64_t *max); + +#endif diff --git a/include/backend/drm/renderer.h b/include/backend/drm/renderer.h new file mode 100644 index 0000000..f53f720 --- /dev/null +++ b/include/backend/drm/renderer.h @@ -0,0 +1,40 @@ +#ifndef BACKEND_DRM_RENDERER_H +#define BACKEND_DRM_RENDERER_H + +#include +#include +#include +#include +#include + +struct wlr_drm_backend; +struct wlr_drm_format; +struct wlr_drm_plane; +struct wlr_buffer; + +struct wlr_drm_renderer { + struct wlr_renderer *wlr_rend; + struct wlr_allocator *allocator; +}; + +struct wlr_drm_surface { + struct wlr_drm_renderer *renderer; + struct wlr_swapchain *swapchain; +}; + +bool init_drm_renderer(struct wlr_drm_backend *drm, + struct wlr_drm_renderer *renderer); +void finish_drm_renderer(struct wlr_drm_renderer *renderer); + +bool init_drm_surface(struct wlr_drm_surface *surf, + struct wlr_drm_renderer *renderer, int width, int height, + const struct wlr_drm_format *drm_format); +void finish_drm_surface(struct wlr_drm_surface *surf); + +struct wlr_buffer *drm_surface_blit(struct wlr_drm_surface *surf, + struct wlr_buffer *buffer); + +bool drm_plane_pick_render_format(struct wlr_drm_plane *plane, + struct wlr_drm_format *fmt, struct wlr_drm_renderer *renderer); + +#endif diff --git a/include/backend/drm/util.h b/include/backend/drm/util.h new file mode 100644 index 0000000..254f774 --- /dev/null +++ b/include/backend/drm/util.h @@ -0,0 +1,43 @@ +#ifndef BACKEND_DRM_UTIL_H +#define BACKEND_DRM_UTIL_H + +#include +#include +#include + +struct wlr_drm_connector; + +// Calculates a more accurate refresh rate (mHz) than what mode itself provides +int32_t calculate_refresh_rate(const drmModeModeInfo *mode); +enum wlr_output_mode_aspect_ratio get_picture_aspect_ratio(const drmModeModeInfo *mode); +// Returns manufacturer based on pnp id +const char *get_pnp_manufacturer(const char code[static 3]); +// Populates the make/model/phys_{width,height} of output from the edid data +void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data); +const char *drm_connector_status_str(drmModeConnection status); +void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay, + float vrefresh); + +// Part of match_obj +enum { + UNMATCHED = (uint32_t)-1, + SKIP = (uint32_t)-2, +}; + +/* + * Tries to match some DRM objects with some other DRM resource. + * e.g. Match CRTCs with Encoders, CRTCs with Planes. + * + * objs contains a bit array which resources it can be matched with. + * e.g. Bit 0 set means can be matched with res[0] + * + * res contains an index of which objs it is matched with or UNMATCHED. + * + * This solution is left in out. + * Returns the total number of matched solutions. + */ +size_t match_obj(size_t num_objs, const uint32_t objs[static restrict num_objs], + size_t num_res, const uint32_t res[static restrict num_res], + uint32_t out[static restrict num_res]); + +#endif diff --git a/include/backend/headless.h b/include/backend/headless.h new file mode 100644 index 0000000..21df86e --- /dev/null +++ b/include/backend/headless.h @@ -0,0 +1,30 @@ +#ifndef BACKEND_HEADLESS_H +#define BACKEND_HEADLESS_H + +#include +#include + +#define HEADLESS_DEFAULT_REFRESH (60 * 1000) // 60 Hz + +struct wlr_headless_backend { + struct wlr_backend backend; + struct wl_event_loop *event_loop; + struct wl_list outputs; + struct wl_listener event_loop_destroy; + bool started; +}; + +struct wlr_headless_output { + struct wlr_output wlr_output; + + struct wlr_headless_backend *backend; + struct wl_list link; + + struct wl_event_source *frame_timer; + int frame_delay; // ms +}; + +struct wlr_headless_backend *headless_backend_from_backend( + struct wlr_backend *wlr_backend); + +#endif diff --git a/include/backend/libinput.h b/include/backend/libinput.h new file mode 100644 index 0000000..bf5a306 --- /dev/null +++ b/include/backend/libinput.h @@ -0,0 +1,139 @@ +#ifndef BACKEND_LIBINPUT_H +#define BACKEND_LIBINPUT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +struct wlr_libinput_backend { + struct wlr_backend backend; + + struct wlr_session *session; + + struct libinput *libinput_context; + struct wl_event_source *input_event; + + struct wl_listener session_destroy; + struct wl_listener session_signal; + + struct wl_list devices; // wlr_libinput_device.link +}; + +struct wlr_libinput_input_device { + struct libinput_device *handle; + + struct wlr_keyboard keyboard; + struct wlr_pointer pointer; + struct wlr_switch switch_device; + struct wlr_touch touch; + struct wlr_tablet tablet; + struct wl_list tablet_tools; // see backend/libinput/tablet_tool.c + struct wlr_tablet_pad tablet_pad; + + struct wl_list link; +}; + +uint32_t usec_to_msec(uint64_t usec); + +void handle_libinput_event(struct wlr_libinput_backend *state, + struct libinput_event *event); + +void destroy_libinput_input_device(struct wlr_libinput_input_device *dev); +const char *get_libinput_device_name(struct libinput_device *device); + +extern const struct wlr_keyboard_impl libinput_keyboard_impl; +extern const struct wlr_pointer_impl libinput_pointer_impl; +extern const struct wlr_switch_impl libinput_switch_impl; +extern const struct wlr_tablet_impl libinput_tablet_impl; +extern const struct wlr_tablet_pad_impl libinput_tablet_pad_impl; +extern const struct wlr_touch_impl libinput_touch_impl; + +void init_device_keyboard(struct wlr_libinput_input_device *dev); +struct wlr_libinput_input_device *device_from_keyboard(struct wlr_keyboard *kb); +void handle_keyboard_key(struct libinput_event *event, struct wlr_keyboard *kb); + +void init_device_pointer(struct wlr_libinput_input_device *dev); +struct wlr_libinput_input_device *device_from_pointer(struct wlr_pointer *kb); +void handle_pointer_motion(struct libinput_event *event, + struct wlr_pointer *pointer); +void handle_pointer_motion_abs(struct libinput_event *event, + struct wlr_pointer *pointer); +void handle_pointer_button(struct libinput_event *event, + struct wlr_pointer *pointer); +void handle_pointer_axis(struct libinput_event *event, + struct wlr_pointer *pointer); +#if HAVE_LIBINPUT_SCROLL_VALUE120 +void handle_pointer_axis_value120(struct libinput_event *event, + struct wlr_pointer *pointer, enum wl_pointer_axis_source source); +#endif +void handle_pointer_swipe_begin(struct libinput_event *event, + struct wlr_pointer *pointer); +void handle_pointer_swipe_update(struct libinput_event *event, + struct wlr_pointer *pointer); +void handle_pointer_swipe_end(struct libinput_event *event, + struct wlr_pointer *pointer); +void handle_pointer_pinch_begin(struct libinput_event *event, + struct wlr_pointer *pointer); +void handle_pointer_pinch_update(struct libinput_event *event, + struct wlr_pointer *pointer); +void handle_pointer_pinch_end(struct libinput_event *event, + struct wlr_pointer *pointer); +void handle_pointer_hold_begin(struct libinput_event *event, + struct wlr_pointer *pointer); +void handle_pointer_hold_end(struct libinput_event *event, + struct wlr_pointer *pointer); + +void init_device_switch(struct wlr_libinput_input_device *dev); +struct wlr_libinput_input_device *device_from_switch( + struct wlr_switch *switch_device); +void handle_switch_toggle(struct libinput_event *event, + struct wlr_switch *switch_device); + +void init_device_touch(struct wlr_libinput_input_device *dev); +struct wlr_libinput_input_device *device_from_touch( + struct wlr_touch *touch); +void handle_touch_down(struct libinput_event *event, + struct wlr_touch *touch); +void handle_touch_up(struct libinput_event *event, + struct wlr_touch *touch); +void handle_touch_motion(struct libinput_event *event, + struct wlr_touch *touch); +void handle_touch_cancel(struct libinput_event *event, + struct wlr_touch *touch); +void handle_touch_frame(struct libinput_event *event, + struct wlr_touch *touch); + +void init_device_tablet(struct wlr_libinput_input_device *dev); +void finish_device_tablet(struct wlr_libinput_input_device *dev); +struct wlr_libinput_input_device *device_from_tablet( + struct wlr_tablet *tablet); +void handle_tablet_tool_axis(struct libinput_event *event, + struct wlr_tablet *tablet); +void handle_tablet_tool_proximity(struct libinput_event *event, + struct wlr_tablet *tablet); +void handle_tablet_tool_tip(struct libinput_event *event, + struct wlr_tablet *tablet); +void handle_tablet_tool_button(struct libinput_event *event, + struct wlr_tablet *tablet); + +void init_device_tablet_pad(struct wlr_libinput_input_device *dev); +void finish_device_tablet_pad(struct wlr_libinput_input_device *dev); +struct wlr_libinput_input_device *device_from_tablet_pad( + struct wlr_tablet_pad *tablet_pad); +void handle_tablet_pad_button(struct libinput_event *event, + struct wlr_tablet_pad *tablet_pad); +void handle_tablet_pad_ring(struct libinput_event *event, + struct wlr_tablet_pad *tablet_pad); +void handle_tablet_pad_strip(struct libinput_event *event, + struct wlr_tablet_pad *tablet_pad); + +#endif diff --git a/include/backend/multi.h b/include/backend/multi.h new file mode 100644 index 0000000..3ffd814 --- /dev/null +++ b/include/backend/multi.h @@ -0,0 +1,21 @@ +#ifndef BACKEND_MULTI_H +#define BACKEND_MULTI_H + +#include +#include +#include + +struct wlr_multi_backend { + struct wlr_backend backend; + + struct wl_list backends; + + struct wl_listener event_loop_destroy; + + struct { + struct wl_signal backend_add; + struct wl_signal backend_remove; + } events; +}; + +#endif diff --git a/include/backend/session/session.h b/include/backend/session/session.h new file mode 100644 index 0000000..0275f69 --- /dev/null +++ b/include/backend/session/session.h @@ -0,0 +1,20 @@ +#ifndef BACKEND_SESSION_SESSION_H +#define BACKEND_SESSION_SESSION_H + +#include + +struct wl_display; +struct wlr_session; + +struct wlr_session *libseat_session_create(struct wl_display *disp); +void libseat_session_destroy(struct wlr_session *base); +int libseat_session_open_device(struct wlr_session *base, const char *path); +void libseat_session_close_device(struct wlr_session *base, int fd); +bool libseat_change_vt(struct wlr_session *base, unsigned vt); + +void session_init(struct wlr_session *session); + +struct wlr_device *session_open_if_kms(struct wlr_session *restrict session, + const char *restrict path); + +#endif diff --git a/include/backend/wayland.h b/include/backend/wayland.h new file mode 100644 index 0000000..a0bffcd --- /dev/null +++ b/include/backend/wayland.h @@ -0,0 +1,183 @@ +#ifndef BACKEND_WAYLAND_H +#define BACKEND_WAYLAND_H + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +struct wlr_wl_backend { + struct wlr_backend backend; + + /* local state */ + bool started; + struct wl_event_loop *event_loop; + struct wl_list outputs; + int drm_fd; + struct wl_list buffers; // wlr_wl_buffer.link + size_t requested_outputs; + struct wl_listener event_loop_destroy; + char *activation_token; + + /* remote state */ + struct wl_display *remote_display; + bool own_remote_display; + struct wl_event_source *remote_display_src; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct xdg_wm_base *xdg_wm_base; + struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1; + struct zwp_pointer_gestures_v1 *zwp_pointer_gestures_v1; + struct wp_presentation *presentation; + struct wl_shm *shm; + struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1; + struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1; + struct wl_list seats; // wlr_wl_seat.link + struct zwp_tablet_manager_v2 *tablet_manager; + struct wlr_drm_format_set shm_formats; + struct wlr_drm_format_set linux_dmabuf_v1_formats; + struct wl_drm *legacy_drm; + struct xdg_activation_v1 *activation_v1; + struct wl_subcompositor *subcompositor; + struct wp_viewporter *viewporter; + char *drm_render_name; +}; + +struct wlr_wl_buffer { + struct wlr_buffer *buffer; + struct wl_buffer *wl_buffer; + bool released; + struct wl_list link; // wlr_wl_backend.buffers + struct wl_listener buffer_destroy; +}; + +struct wlr_wl_presentation_feedback { + struct wlr_wl_output *output; + struct wl_list link; + struct wp_presentation_feedback *feedback; + uint32_t commit_seq; +}; + +struct wlr_wl_output_layer { + struct wlr_addon addon; + + struct wl_surface *surface; + struct wl_subsurface *subsurface; + struct wp_viewport *viewport; + bool mapped; +}; + +struct wlr_wl_output { + struct wlr_output wlr_output; + + struct wlr_wl_backend *backend; + struct wl_list link; + + struct wl_surface *surface; + bool own_surface; + struct wl_callback *frame_callback; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1; + struct wl_list presentation_feedbacks; + + bool configured; + uint32_t enter_serial; + + struct { + struct wlr_wl_pointer *pointer; + struct wl_surface *surface; + int32_t hotspot_x, hotspot_y; + } cursor; +}; + +struct wlr_wl_pointer { + struct wlr_pointer wlr_pointer; + + struct wlr_wl_seat *seat; + struct wlr_wl_output *output; + + enum wl_pointer_axis_source axis_source; + int32_t axis_discrete; + uint32_t fingers; // trackpad gesture + enum wl_pointer_axis_relative_direction axis_relative_direction; + + struct wl_listener output_destroy; + + struct wl_list link; +}; + +struct wlr_wl_touch_points { + int32_t ids[64]; + size_t len; +}; + +struct wlr_wl_seat { + char *name; + struct wl_seat *wl_seat; + uint32_t global_name; + + struct wlr_wl_backend *backend; + + struct wl_keyboard *wl_keyboard; + struct wlr_keyboard wlr_keyboard; + + struct wl_pointer *wl_pointer; + struct wlr_wl_pointer *active_pointer; + struct wl_list pointers; // wlr_wl_pointer.link + + struct zwp_pointer_gesture_swipe_v1 *gesture_swipe; + struct zwp_pointer_gesture_pinch_v1 *gesture_pinch; + struct zwp_pointer_gesture_hold_v1 *gesture_hold; + struct zwp_relative_pointer_v1 *relative_pointer; + + struct wl_touch *wl_touch; + struct wlr_touch wlr_touch; + struct wlr_wl_touch_points touch_points; + + struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2; + struct zwp_tablet_v2 *zwp_tablet_v2; + struct wlr_tablet wlr_tablet; + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2; + struct wlr_tablet_tool wlr_tablet_tool; + struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2; + struct wlr_tablet_pad wlr_tablet_pad; + + struct wl_list link; // wlr_wl_backend.seats +}; + +struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *backend); +struct wlr_wl_output *get_wl_output_from_surface(struct wlr_wl_backend *wl, + struct wl_surface *surface); +void update_wl_output_cursor(struct wlr_wl_output *output); + +void init_seat_keyboard(struct wlr_wl_seat *seat); + +void init_seat_pointer(struct wlr_wl_seat *seat); +void finish_seat_pointer(struct wlr_wl_seat *seat); +void create_pointer(struct wlr_wl_seat *seat, struct wlr_wl_output *output); + +void init_seat_touch(struct wlr_wl_seat *seat); + +void init_seat_tablet(struct wlr_wl_seat *seat); +void finish_seat_tablet(struct wlr_wl_seat *seat); + +bool create_wl_seat(struct wl_seat *wl_seat, struct wlr_wl_backend *wl, + uint32_t global_name); +void destroy_wl_seat(struct wlr_wl_seat *seat); +void destroy_wl_buffer(struct wlr_wl_buffer *buffer); + +extern const struct wlr_pointer_impl wl_pointer_impl; +extern const struct wlr_tablet_pad_impl wl_tablet_pad_impl; +extern const struct wlr_tablet_impl wl_tablet_impl; + +#endif diff --git a/include/backend/x11.h b/include/backend/x11.h new file mode 100644 index 0000000..fcea4d2 --- /dev/null +++ b/include/backend/x11.h @@ -0,0 +1,147 @@ +#ifndef BACKEND_X11_H +#define BACKEND_X11_H + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#if HAVE_XCB_ERRORS +#include +#endif + +#define XCB_EVENT_RESPONSE_TYPE_MASK 0x7f + +struct wlr_x11_backend; + +struct wlr_x11_output { + struct wlr_output wlr_output; + struct wlr_x11_backend *x11; + struct wl_list link; // wlr_x11_backend.outputs + + xcb_window_t win; + xcb_present_event_t present_event_id; + + int32_t win_width, win_height; + + struct wlr_pointer pointer; + + struct wlr_touch touch; + struct wl_list touchpoints; // wlr_x11_touchpoint.link + + struct wl_list buffers; // wlr_x11_buffer.link + + pixman_region32_t exposed; + + uint64_t last_msc; + + struct { + struct wlr_swapchain *swapchain; + xcb_render_picture_t pic; + } cursor; +}; + +struct wlr_x11_touchpoint { + uint32_t x11_id; + int wayland_id; + struct wl_list link; // wlr_x11_output.touch_points +}; + +struct wlr_x11_backend { + struct wlr_backend backend; + struct wl_event_loop *event_loop; + bool started; + + xcb_connection_t *xcb; + xcb_screen_t *screen; + xcb_depth_t *depth; + xcb_visualid_t visualid; + xcb_colormap_t colormap; + xcb_cursor_t transparent_cursor; + xcb_render_pictformat_t argb32; + + bool have_shm; + bool have_dri3; + uint32_t dri3_major_version, dri3_minor_version; + + size_t requested_outputs; + struct wl_list outputs; // wlr_x11_output.link + + struct wlr_keyboard keyboard; + + int drm_fd; + struct wlr_drm_format_set dri3_formats; + struct wlr_drm_format_set shm_formats; + const struct wlr_x11_format *x11_format; + struct wlr_drm_format_set primary_dri3_formats; + struct wlr_drm_format_set primary_shm_formats; + struct wl_event_source *event_source; + + struct { + xcb_atom_t wm_protocols; + xcb_atom_t wm_delete_window; + xcb_atom_t net_wm_name; + xcb_atom_t utf8_string; + xcb_atom_t variable_refresh; + } atoms; + + // The time we last received an event + xcb_timestamp_t time; + +#if HAVE_XCB_ERRORS + xcb_errors_context_t *errors_context; +#endif + + uint8_t present_opcode; + uint8_t xinput_opcode; + + struct wl_listener event_loop_destroy; +}; + +struct wlr_x11_buffer { + struct wlr_x11_backend *x11; + struct wlr_buffer *buffer; + xcb_pixmap_t pixmap; + struct wl_list link; // wlr_x11_output.buffers + struct wl_listener buffer_destroy; + size_t n_busy; +}; + +struct wlr_x11_format { + uint32_t drm; + uint8_t depth, bpp; +}; + +struct wlr_x11_backend *get_x11_backend_from_backend( + struct wlr_backend *wlr_backend); +struct wlr_x11_output *get_x11_output_from_window_id( + struct wlr_x11_backend *x11, xcb_window_t window); + +extern const struct wlr_keyboard_impl x11_keyboard_impl; +extern const struct wlr_pointer_impl x11_pointer_impl; +extern const struct wlr_touch_impl x11_touch_impl; + +void handle_x11_xinput_event(struct wlr_x11_backend *x11, + xcb_ge_generic_event_t *event); +void update_x11_pointer_position(struct wlr_x11_output *output, + xcb_timestamp_t time); + +void handle_x11_configure_notify(struct wlr_x11_output *output, + xcb_configure_notify_event_t *event); +void handle_x11_present_event(struct wlr_x11_backend *x11, + xcb_ge_generic_event_t *event); + +#endif diff --git a/include/interfaces/wlr_input_device.h b/include/interfaces/wlr_input_device.h new file mode 100644 index 0000000..c24b536 --- /dev/null +++ b/include/interfaces/wlr_input_device.h @@ -0,0 +1,20 @@ +#ifndef INTERFACES_INPUT_DEVICE_H +#define INTERFACES_INPUT_DEVICE_H + +#include + +/** + * Initializes a given wlr_input_device. Allocates memory for the name and sets + * its vendor and product id to 0. + * wlr_device must be non-NULL. + */ +void wlr_input_device_init(struct wlr_input_device *wlr_device, + enum wlr_input_device_type type, const char *name); + +/** + * Cleans up all the memory owned by a given wlr_input_device and signals + * the destroy event. + */ +void wlr_input_device_finish(struct wlr_input_device *wlr_device); + +#endif diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 0000000..e669800 --- /dev/null +++ b/include/meson.build @@ -0,0 +1,38 @@ +subdir('wlr') + +exclude_files = ['meson.build', 'config.h.in', 'version.h.in'] +if not features.get('drm-backend') + exclude_files += 'backend/drm.h' + exclude_files += 'types/wlr_drm_lease_v1.h' +endif +if not features.get('libinput-backend') + exclude_files += 'backend/libinput.h' +endif +if not features.get('x11-backend') + exclude_files += 'backend/x11.h' +endif +if not features.get('xwayland') + exclude_files += 'xwayland.h' +endif +if not features.get('gles2-renderer') + exclude_files += ['render/egl.h', 'render/gles2.h'] +endif +if not features.get('vulkan-renderer') + exclude_files += 'render/vulkan.h' +endif +if not features.get('session') + exclude_files += 'backend/session.h' +endif + +install_subdir('wlr', + install_dir: get_option('includedir'), + exclude_files: exclude_files, +) + +foreach name, have : internal_features + internal_config.set10('HAVE_' + name.underscorify().to_upper(), have) +endforeach +wlr_files += configure_file( + output: 'config.h', + configuration: internal_config, +) diff --git a/include/render/allocator/allocator.h b/include/render/allocator/allocator.h new file mode 100644 index 0000000..5f8bd2a --- /dev/null +++ b/include/render/allocator/allocator.h @@ -0,0 +1,9 @@ +#ifndef RENDER_ALLOCATOR_ALLOCATOR_H +#define RENDER_ALLOCATOR_ALLOCATOR_H + +#include + +struct wlr_allocator *allocator_autocreate_with_drm_fd( + uint32_t backend_caps, struct wlr_renderer *renderer, int drm_fd); + +#endif diff --git a/include/render/allocator/drm_dumb.h b/include/render/allocator/drm_dumb.h new file mode 100644 index 0000000..23c150b --- /dev/null +++ b/include/render/allocator/drm_dumb.h @@ -0,0 +1,37 @@ +#ifndef RENDER_ALLOCATOR_DRM_DUMB_H +#define RENDER_ALLOCATOR_DRM_DUMB_H + +#include +#include +#include "render/allocator/allocator.h" + +struct wlr_drm_dumb_buffer { + struct wlr_buffer base; + struct wl_list link; // wlr_drm_dumb_allocator.buffers + + int drm_fd; // -1 if the allocator has been destroyed + struct wlr_dmabuf_attributes dmabuf; + + uint32_t format; + uint32_t handle; + uint32_t stride; + uint32_t width, height; + + uint64_t size; + void *data; +}; + +struct wlr_drm_dumb_allocator { + struct wlr_allocator base; + struct wl_list buffers; // wlr_drm_dumb_buffer.link + int drm_fd; +}; + +/** + * Creates a new drm dumb allocator from a DRM FD. + * + * Does not take ownership over the FD. + */ +struct wlr_allocator *wlr_drm_dumb_allocator_create(int fd); + +#endif diff --git a/include/render/allocator/gbm.h b/include/render/allocator/gbm.h new file mode 100644 index 0000000..7e043fa --- /dev/null +++ b/include/render/allocator/gbm.h @@ -0,0 +1,34 @@ +#ifndef RENDER_ALLOCATOR_GBM_H +#define RENDER_ALLOCATOR_GBM_H + +#include +#include +#include +#include "render/allocator/allocator.h" + +struct wlr_gbm_buffer { + struct wlr_buffer base; + + struct wl_list link; // wlr_gbm_allocator.buffers + + struct gbm_bo *gbm_bo; // NULL if the gbm_device has been destroyed + struct wlr_dmabuf_attributes dmabuf; +}; + +struct wlr_gbm_allocator { + struct wlr_allocator base; + + int fd; + struct gbm_device *gbm_device; + + struct wl_list buffers; // wlr_gbm_buffer.link +}; + +/** + * Creates a new GBM allocator from a DRM FD. + * + * Takes ownership over the FD. + */ +struct wlr_allocator *wlr_gbm_allocator_create(int drm_fd); + +#endif diff --git a/include/render/allocator/shm.h b/include/render/allocator/shm.h new file mode 100644 index 0000000..4b80e47 --- /dev/null +++ b/include/render/allocator/shm.h @@ -0,0 +1,23 @@ +#ifndef RENDER_ALLOCATOR_SHM_H +#define RENDER_ALLOCATOR_SHM_H + +#include +#include "render/allocator/allocator.h" + +struct wlr_shm_buffer { + struct wlr_buffer base; + struct wlr_shm_attributes shm; + void *data; + size_t size; +}; + +struct wlr_shm_allocator { + struct wlr_allocator base; +}; + +/** + * Creates a new shared memory allocator. + */ +struct wlr_allocator *wlr_shm_allocator_create(void); + +#endif diff --git a/include/render/dmabuf.h b/include/render/dmabuf.h new file mode 100644 index 0000000..3d905ce --- /dev/null +++ b/include/render/dmabuf.h @@ -0,0 +1,35 @@ +#ifndef RENDER_DMABUF_H +#define RENDER_DMABUF_H + +#include +#include + +// Copied from to avoid #ifdef soup +#define DMA_BUF_SYNC_READ (1 << 0) +#define DMA_BUF_SYNC_WRITE (2 << 0) +#define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE) + +/** + * Check whether DMA-BUF import/export from/to sync_file is available. + * + * If this function returns true, dmabuf_import_sync_file() is supported. + */ +bool dmabuf_check_sync_file_import_export(void); + +/** + * Import a sync_file into a DMA-BUF with DMA_BUF_IOCTL_IMPORT_SYNC_FILE. + * + * This can be used to make explicit sync interoperate with implicit sync. + */ +bool dmabuf_import_sync_file(int dmabuf_fd, uint32_t flags, int sync_file_fd); + +/** + * Export a sync_file from a DMA-BUF with DMA_BUF_IOCTL_EXPORT_SYNC_FILE. + * + * The sync_file FD is returned on success, -1 is returned on error. + * + * This can be used to make explicit sync interoperate with implicit sync. + */ +int dmabuf_export_sync_file(int dmabuf_fd, uint32_t flags); + +#endif diff --git a/include/render/drm_format_set.h b/include/render/drm_format_set.h new file mode 100644 index 0000000..ea192a5 --- /dev/null +++ b/include/render/drm_format_set.h @@ -0,0 +1,23 @@ +#ifndef RENDER_DRM_FORMAT_SET_H +#define RENDER_DRM_FORMAT_SET_H + +#include + +void wlr_drm_format_init(struct wlr_drm_format *fmt, uint32_t format); +bool wlr_drm_format_has(const struct wlr_drm_format *fmt, uint64_t modifier); +bool wlr_drm_format_add(struct wlr_drm_format *fmt, uint64_t modifier); +bool wlr_drm_format_copy(struct wlr_drm_format *dst, const struct wlr_drm_format *src); +/** + * Intersect modifiers for two DRM formats. The `dst` must be zeroed or initialized + * with other state being replaced. + * + * Both arguments must have the same format field. If the formats aren't + * compatible, NULL is returned. If either format doesn't support any modifier, + * a format that doesn't support any modifier is returned. + */ +bool wlr_drm_format_intersect(struct wlr_drm_format *dst, + const struct wlr_drm_format *a, const struct wlr_drm_format *b); + +bool wlr_drm_format_set_copy(struct wlr_drm_format_set *dst, const struct wlr_drm_format_set *src); + +#endif diff --git a/include/render/egl.h b/include/render/egl.h new file mode 100644 index 0000000..0765cce --- /dev/null +++ b/include/render/egl.h @@ -0,0 +1,108 @@ +#ifndef RENDER_EGL_H +#define RENDER_EGL_H + +#include + +struct wlr_egl { + EGLDisplay display; + EGLContext context; + EGLDeviceEXT device; // may be EGL_NO_DEVICE_EXT + struct gbm_device *gbm_device; + + struct { + // Display extensions + bool KHR_image_base; + bool EXT_image_dma_buf_import; + bool EXT_image_dma_buf_import_modifiers; + bool IMG_context_priority; + bool EXT_create_context_robustness; + + // Device extensions + bool EXT_device_drm; + bool EXT_device_drm_render_node; + + // Client extensions + bool EXT_device_query; + bool KHR_platform_gbm; + bool EXT_platform_device; + bool KHR_display_reference; + } exts; + + struct { + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT; + PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; + PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; + PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT; + PFNEGLDEBUGMESSAGECONTROLKHRPROC eglDebugMessageControlKHR; + PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT; + PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT; + PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT; + } procs; + + bool has_modifiers; + struct wlr_drm_format_set dmabuf_texture_formats; + struct wlr_drm_format_set dmabuf_render_formats; +}; + +struct wlr_egl_context { + EGLDisplay display; + EGLContext context; + EGLSurface draw_surface; + EGLSurface read_surface; +}; + +/** + * Initializes an EGL context for the given DRM FD. + * + * Will attempt to load all possibly required API functions. + */ +struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd); + +/** + * Frees all related EGL resources, makes the context not-current and + * unbinds a bound wayland display. + */ +void wlr_egl_destroy(struct wlr_egl *egl); + +/** + * Creates an EGL image from the given dmabuf attributes. Check usability + * of the dmabuf with wlr_egl_check_import_dmabuf once first. + */ +EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl, + struct wlr_dmabuf_attributes *attributes, bool *external_only); + +/** + * Get DMA-BUF formats suitable for sampling usage. + */ +const struct wlr_drm_format_set *wlr_egl_get_dmabuf_texture_formats( + struct wlr_egl *egl); +/** + * Get DMA-BUF formats suitable for rendering usage. + */ +const struct wlr_drm_format_set *wlr_egl_get_dmabuf_render_formats( + struct wlr_egl *egl); + +/** + * Destroys an EGL image created with the given wlr_egl. + */ +bool wlr_egl_destroy_image(struct wlr_egl *egl, EGLImageKHR image); + +int wlr_egl_dup_drm_fd(struct wlr_egl *egl); + +/** + * Restore EGL context that was previously saved using wlr_egl_save_current(). + */ +bool wlr_egl_restore_context(struct wlr_egl_context *context); + +/** + * Make the EGL context current. + * + * The old EGL context is saved. Callers are expected to clear the current + * context when they are done by calling wlr_egl_restore_context(). + */ +bool wlr_egl_make_current(struct wlr_egl *egl, struct wlr_egl_context *save_context); + +bool wlr_egl_unset_current(struct wlr_egl *egl); + +#endif diff --git a/include/render/gles2.h b/include/render/gles2.h new file mode 100644 index 0000000..db301c6 --- /dev/null +++ b/include/render/gles2.h @@ -0,0 +1,172 @@ +#ifndef RENDER_GLES2_H +#define RENDER_GLES2_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "render/egl.h" + +// mesa ships old GL headers that don't include this type, so for distros that use headers from +// mesa we need to def it ourselves until they update. +// https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/23144 +typedef void (GL_APIENTRYP PFNGLGETINTEGER64VEXTPROC) (GLenum pname, GLint64 *data); + +struct wlr_gles2_pixel_format { + uint32_t drm_format; + // optional field, if empty then internalformat = format + GLint gl_internalformat; + GLint gl_format, gl_type; +}; + +struct wlr_gles2_tex_shader { + GLuint program; + GLint proj; + GLint tex_proj; + GLint tex; + GLint alpha; + GLint pos_attrib; +}; + +struct wlr_gles2_renderer { + struct wlr_renderer wlr_renderer; + + struct wlr_egl *egl; + int drm_fd; + + const char *exts_str; + struct { + bool EXT_read_format_bgra; + bool KHR_debug; + bool OES_egl_image_external; + bool OES_egl_image; + bool EXT_texture_type_2_10_10_10_REV; + bool OES_texture_half_float_linear; + bool EXT_texture_norm16; + bool EXT_disjoint_timer_query; + } exts; + + struct { + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; + PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR; + PFNGLDEBUGMESSAGECONTROLKHRPROC glDebugMessageControlKHR; + PFNGLPOPDEBUGGROUPKHRPROC glPopDebugGroupKHR; + PFNGLPUSHDEBUGGROUPKHRPROC glPushDebugGroupKHR; + PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES; + PFNGLGETGRAPHICSRESETSTATUSKHRPROC glGetGraphicsResetStatusKHR; + PFNGLGENQUERIESEXTPROC glGenQueriesEXT; + PFNGLDELETEQUERIESEXTPROC glDeleteQueriesEXT; + PFNGLQUERYCOUNTEREXTPROC glQueryCounterEXT; + PFNGLGETQUERYOBJECTIVEXTPROC glGetQueryObjectivEXT; + PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64vEXT; + PFNGLGETINTEGER64VEXTPROC glGetInteger64vEXT; + } procs; + + struct { + struct { + GLuint program; + GLint proj; + GLint color; + GLint pos_attrib; + } quad; + struct wlr_gles2_tex_shader tex_rgba; + struct wlr_gles2_tex_shader tex_rgbx; + struct wlr_gles2_tex_shader tex_ext; + } shaders; + + struct wl_list buffers; // wlr_gles2_buffer.link + struct wl_list textures; // wlr_gles2_texture.link +}; + +struct wlr_gles2_render_timer { + struct wlr_render_timer base; + struct wlr_gles2_renderer *renderer; + struct timespec cpu_start; + struct timespec cpu_end; + GLuint id; + GLint64 gl_cpu_end; +}; + +struct wlr_gles2_buffer { + struct wlr_buffer *buffer; + struct wlr_gles2_renderer *renderer; + struct wl_list link; // wlr_gles2_renderer.buffers + bool external_only; + + EGLImageKHR image; + GLuint rbo; + GLuint fbo; + GLuint tex; + + struct wlr_addon addon; +}; + +struct wlr_gles2_texture { + struct wlr_texture wlr_texture; + struct wlr_gles2_renderer *renderer; + struct wl_list link; // wlr_gles2_renderer.textures + + GLenum target; + + // If this texture is imported from a buffer, the texture is does not own + // these states. These cannot be destroyed along with the texture in this + // case. + GLuint tex; + GLuint fbo; + + bool has_alpha; + + uint32_t drm_format; // for mutable textures only, used to interpret upload data + struct wlr_gles2_buffer *buffer; // for DMA-BUF imports only +}; + +struct wlr_gles2_render_pass { + struct wlr_render_pass base; + struct wlr_gles2_buffer *buffer; + float projection_matrix[9]; + struct wlr_egl_context prev_ctx; + struct wlr_gles2_render_timer *timer; +}; + +bool is_gles2_pixel_format_supported(const struct wlr_gles2_renderer *renderer, + const struct wlr_gles2_pixel_format *format); +const struct wlr_gles2_pixel_format *get_gles2_format_from_drm(uint32_t fmt); +const struct wlr_gles2_pixel_format *get_gles2_format_from_gl( + GLint gl_format, GLint gl_type, bool alpha); +const uint32_t *get_gles2_shm_formats(const struct wlr_gles2_renderer *renderer, + size_t *len); + +GLuint gles2_buffer_get_fbo(struct wlr_gles2_buffer *buffer); + +struct wlr_gles2_renderer *gles2_get_renderer( + struct wlr_renderer *wlr_renderer); +struct wlr_gles2_render_timer *gles2_get_render_timer( + struct wlr_render_timer *timer); +struct wlr_gles2_texture *gles2_get_texture( + struct wlr_texture *wlr_texture); +struct wlr_gles2_buffer *gles2_buffer_get_or_create(struct wlr_gles2_renderer *renderer, + struct wlr_buffer *wlr_buffer); + +struct wlr_texture *gles2_texture_from_buffer(struct wlr_renderer *wlr_renderer, + struct wlr_buffer *buffer); +void gles2_texture_destroy(struct wlr_gles2_texture *texture); + +void push_gles2_debug_(struct wlr_gles2_renderer *renderer, + const char *file, const char *func); +#define push_gles2_debug(renderer) push_gles2_debug_(renderer, _WLR_FILENAME, __func__) +void pop_gles2_debug(struct wlr_gles2_renderer *renderer); + +struct wlr_gles2_render_pass *begin_gles2_buffer_pass(struct wlr_gles2_buffer *buffer, + struct wlr_egl_context *prev_ctx, struct wlr_gles2_render_timer *timer); + +#endif diff --git a/include/render/pixel_format.h b/include/render/pixel_format.h new file mode 100644 index 0000000..e0b500c --- /dev/null +++ b/include/render/pixel_format.h @@ -0,0 +1,66 @@ +#ifndef RENDER_PIXEL_FORMAT_H +#define RENDER_PIXEL_FORMAT_H + +#include + +/** + * Information about a pixel format. + * + * A pixel format is identified via its DRM four character code (see ). + * + * Simple formats have a block size of 1×1 pixels and bytes_per_block contains + * the number of bytes per pixel (including padding). + * + * Tiled formats (e.g. sub-sampled YCbCr) are described with a block size + * greater than 1×1 pixels. A block is a rectangle of pixels which are stored + * next to each other in a byte-aligned memory region. + */ +struct wlr_pixel_format_info { + uint32_t drm_format; + + /* Equivalent of the format if it has an alpha channel, + * DRM_FORMAT_INVALID (0) if NA + */ + uint32_t opaque_substitute; + + /* Bytes per block (including padding) */ + uint32_t bytes_per_block; + /* Size of a block in pixels (zero for 1×1) */ + uint32_t block_width, block_height; +}; + +/** + * Get pixel format information from a DRM FourCC. + * + * NULL is returned if the pixel format is unknown. + */ +const struct wlr_pixel_format_info *drm_get_pixel_format_info(uint32_t fmt); +/** + * Get the number of pixels per block for a pixel format. + */ +uint32_t pixel_format_info_pixels_per_block(const struct wlr_pixel_format_info *info); +/** + * Get the minimum stride for a given pixel format and width. + */ +int32_t pixel_format_info_min_stride(const struct wlr_pixel_format_info *info, int32_t width); +/** + * Check whether a stride is large enough for a given pixel format and width. + */ +bool pixel_format_info_check_stride(const struct wlr_pixel_format_info *info, + int32_t stride, int32_t width); + +/** + * Convert an enum wl_shm_format to a DRM FourCC. + */ +uint32_t convert_wl_shm_format_to_drm(enum wl_shm_format fmt); +/** + * Convert a DRM FourCC to an enum wl_shm_format. + */ +enum wl_shm_format convert_drm_format_to_wl_shm(uint32_t fmt); + +/** + * Return true if the DRM FourCC fmt has an alpha channel, false otherwise. + */ +bool pixel_format_has_alpha(uint32_t fmt); + +#endif diff --git a/include/render/pixman.h b/include/render/pixman.h new file mode 100644 index 0000000..0984214 --- /dev/null +++ b/include/render/pixman.h @@ -0,0 +1,64 @@ +#ifndef RENDER_PIXMAN_H +#define RENDER_PIXMAN_H + +#include +#include +#include +#include +#include "render/pixel_format.h" + +struct wlr_pixman_pixel_format { + uint32_t drm_format; + pixman_format_code_t pixman_format; +}; + +struct wlr_pixman_buffer; + +struct wlr_pixman_renderer { + struct wlr_renderer wlr_renderer; + + struct wl_list buffers; // wlr_pixman_buffer.link + struct wl_list textures; // wlr_pixman_texture.link + + struct wlr_drm_format_set drm_formats; +}; + +struct wlr_pixman_buffer { + struct wlr_buffer *buffer; + struct wlr_pixman_renderer *renderer; + + pixman_image_t *image; + + struct wl_listener buffer_destroy; + struct wl_list link; // wlr_pixman_renderer.buffers +}; + +struct wlr_pixman_texture { + struct wlr_texture wlr_texture; + struct wlr_pixman_renderer *renderer; + struct wl_list link; // wlr_pixman_renderer.textures + + pixman_image_t *image; + pixman_format_code_t format; + const struct wlr_pixel_format_info *format_info; + + void *data; // if created via texture_from_pixels + struct wlr_buffer *buffer; // if created via texture_from_buffer +}; + +struct wlr_pixman_render_pass { + struct wlr_render_pass base; + struct wlr_pixman_buffer *buffer; +}; + +pixman_format_code_t get_pixman_format_from_drm(uint32_t fmt); +uint32_t get_drm_format_from_pixman(pixman_format_code_t fmt); +const uint32_t *get_pixman_drm_formats(size_t *len); + +bool begin_pixman_data_ptr_access(struct wlr_buffer *buffer, pixman_image_t **image_ptr, + uint32_t flags); + +struct wlr_pixman_render_pass *begin_pixman_render_pass( + struct wlr_pixman_buffer *buffer); + +#endif diff --git a/include/render/vulkan.h b/include/render/vulkan.h new file mode 100644 index 0000000..3a3daed --- /dev/null +++ b/include/render/vulkan.h @@ -0,0 +1,456 @@ +#ifndef RENDER_VULKAN_H +#define RENDER_VULKAN_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "util/rect_union.h" + +struct wlr_vk_descriptor_pool; +struct wlr_vk_texture; + +struct wlr_vk_instance { + VkInstance instance; + VkDebugUtilsMessengerEXT messenger; + + struct { + PFN_vkCreateDebugUtilsMessengerEXT createDebugUtilsMessengerEXT; + PFN_vkDestroyDebugUtilsMessengerEXT destroyDebugUtilsMessengerEXT; + } api; +}; + +// Creates and initializes a vulkan instance. +// The debug parameter determines if validation layers are enabled and a +// debug messenger created. +struct wlr_vk_instance *vulkan_instance_create(bool debug); +void vulkan_instance_destroy(struct wlr_vk_instance *ini); + +// Logical vulkan device state. +struct wlr_vk_device { + struct wlr_vk_instance *instance; + + VkPhysicalDevice phdev; + VkDevice dev; + + int drm_fd; + + bool implicit_sync_interop; + bool sampler_ycbcr_conversion; + + // we only ever need one queue for rendering and transfer commands + uint32_t queue_family; + VkQueue queue; + + struct { + PFN_vkGetMemoryFdPropertiesKHR vkGetMemoryFdPropertiesKHR; + PFN_vkWaitSemaphoresKHR vkWaitSemaphoresKHR; + PFN_vkGetSemaphoreCounterValueKHR vkGetSemaphoreCounterValueKHR; + PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR; + PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR; + PFN_vkQueueSubmit2KHR vkQueueSubmit2KHR; + } api; + + uint32_t format_prop_count; + struct wlr_vk_format_props *format_props; + struct wlr_drm_format_set dmabuf_render_formats; + struct wlr_drm_format_set dmabuf_texture_formats; + + // supported formats for textures (contains only those formats + // that support everything we need for textures) + uint32_t shm_format_count; + uint32_t *shm_formats; // to implement vulkan_get_shm_texture_formats +}; + +// Tries to find the VkPhysicalDevice for the given drm fd. +// Might find none and return VK_NULL_HANDLE. +VkPhysicalDevice vulkan_find_drm_phdev(struct wlr_vk_instance *ini, int drm_fd); +int vulkan_open_phdev_drm_fd(VkPhysicalDevice phdev); + +// Creates a device for the given instance and physical device. +struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, + VkPhysicalDevice phdev); +void vulkan_device_destroy(struct wlr_vk_device *dev); + +// Tries to find any memory bit for the given vulkan device that +// supports the given flags and is set in req_bits (e.g. if memory +// type 2 is ok, (req_bits & (1 << 2)) must not be 0. +// Set req_bits to 0xFFFFFFFF to allow all types. +int vulkan_find_mem_type(struct wlr_vk_device *device, + VkMemoryPropertyFlags flags, uint32_t req_bits); + +struct wlr_vk_format { + uint32_t drm; + VkFormat vk; + VkFormat vk_srgb; // sRGB version of the format, or 0 if nonexistent + bool is_ycbcr; +}; + +extern const VkImageUsageFlags vulkan_render_usage, vulkan_shm_tex_usage, vulkan_dma_tex_usage; + +// Returns all known format mappings. +// Might not be supported for gpu/usecase. +const struct wlr_vk_format *vulkan_get_format_list(size_t *len); +const struct wlr_vk_format *vulkan_get_format_from_drm(uint32_t drm_format); + +struct wlr_vk_format_modifier_props { + VkDrmFormatModifierPropertiesEXT props; + VkExtent2D max_extent; + bool has_mutable_srgb; +}; + +struct wlr_vk_format_props { + struct wlr_vk_format format; + + struct { + VkExtent2D max_extent; + VkFormatFeatureFlags features; + bool has_mutable_srgb; + } shm; + + struct { + uint32_t render_mod_count; + struct wlr_vk_format_modifier_props *render_mods; + + uint32_t texture_mod_count; + struct wlr_vk_format_modifier_props *texture_mods; + } dmabuf; +}; + +void vulkan_format_props_query(struct wlr_vk_device *dev, + const struct wlr_vk_format *format); +const struct wlr_vk_format_modifier_props *vulkan_format_props_find_modifier( + const struct wlr_vk_format_props *props, uint64_t mod, bool render); +void vulkan_format_props_finish(struct wlr_vk_format_props *props); + +struct wlr_vk_pipeline_layout_key { + const struct wlr_vk_format *ycbcr_format; + enum wlr_scale_filter_mode filter_mode; +}; + +struct wlr_vk_pipeline_layout { + struct wlr_vk_pipeline_layout_key key; + + VkPipelineLayout vk; + VkDescriptorSetLayout ds; + VkSampler sampler; + + // for YCbCr pipelines only + struct { + VkSamplerYcbcrConversion conversion; + VkFormat format; + } ycbcr; + + struct wl_list link; // struct wlr_vk_renderer.pipeline_layouts +}; + +// Constants used to pick the color transform for the texture drawing +// fragment shader. Must match those in shaders/texture.frag +enum wlr_vk_texture_transform { + WLR_VK_TEXTURE_TRANSFORM_IDENTITY = 0, + WLR_VK_TEXTURE_TRANSFORM_SRGB = 1, +}; + +enum wlr_vk_shader_source { + WLR_VK_SHADER_SOURCE_TEXTURE, + WLR_VK_SHADER_SOURCE_SINGLE_COLOR, +}; + +struct wlr_vk_pipeline_key { + struct wlr_vk_pipeline_layout_key layout; + enum wlr_vk_shader_source source; + enum wlr_render_blend_mode blend_mode; + + // only used if source is texture + enum wlr_vk_texture_transform texture_transform; +}; + +struct wlr_vk_pipeline { + struct wlr_vk_pipeline_key key; + + VkPipeline vk; + const struct wlr_vk_pipeline_layout *layout; + struct wlr_vk_render_format_setup *setup; + struct wl_list link; // struct wlr_vk_render_format_setup +}; + +// For each format we want to render, we need a separate renderpass +// and therefore also separate pipelines. +struct wlr_vk_render_format_setup { + struct wl_list link; // wlr_vk_renderer.render_format_setups + const struct wlr_vk_format *render_format; // used in renderpass + VkRenderPass render_pass; + + VkPipeline output_pipe; + + struct wlr_vk_renderer *renderer; + struct wl_list pipelines; // struct wlr_vk_pipeline.link +}; + +// Renderer-internal represenation of an wlr_buffer imported for rendering. +struct wlr_vk_render_buffer { + struct wlr_buffer *wlr_buffer; + struct wlr_addon addon; + struct wlr_vk_renderer *renderer; + struct wlr_vk_render_format_setup *render_setup; + struct wl_list link; // wlr_vk_renderer.buffers + + VkImage image; + VkImageView image_view; + VkFramebuffer framebuffer; + uint32_t mem_count; + VkDeviceMemory memories[WLR_DMABUF_MAX_PLANES]; + bool transitioned; + + VkImage blend_image; + VkImageView blend_image_view; + VkDeviceMemory blend_memory; + VkDescriptorSet blend_descriptor_set; + struct wlr_vk_descriptor_pool *blend_attachment_pool; + bool blend_transitioned; +}; + +struct wlr_vk_command_buffer { + VkCommandBuffer vk; + bool recording; + uint64_t timeline_point; + // Textures to destroy after the command buffer completes + struct wl_list destroy_textures; // wlr_vk_texture.destroy_link + // Staging shared buffers to release after the command buffer completes + struct wl_list stage_buffers; // wlr_vk_shared_buffer.link + + // For DMA-BUF implicit sync interop, may be NULL + VkSemaphore binary_semaphore; +}; + +#define VULKAN_COMMAND_BUFFERS_CAP 64 + +// Vulkan wlr_renderer implementation on top of a wlr_vk_device. +struct wlr_vk_renderer { + struct wlr_renderer wlr_renderer; + struct wlr_backend *backend; + struct wlr_vk_device *dev; + + VkCommandPool command_pool; + + VkShaderModule vert_module; + VkShaderModule tex_frag_module; + VkShaderModule quad_frag_module; + VkShaderModule output_module; + + struct wl_list pipeline_layouts; // struct wlr_vk_pipeline_layout.link + + // for blend->output subpass + VkPipelineLayout output_pipe_layout; + VkDescriptorSetLayout output_ds_layout; + size_t last_output_pool_size; + struct wl_list output_descriptor_pools; // wlr_vk_descriptor_pool.link + + VkSemaphore timeline_semaphore; + uint64_t timeline_point; + + size_t last_pool_size; + struct wl_list descriptor_pools; // wlr_vk_descriptor_pool.link + struct wl_list render_format_setups; // wlr_vk_render_format_setup.link + + + struct wl_list textures; // wlr_vk_texture.link + // Textures to return to foreign queue + struct wl_list foreign_textures; // wlr_vk_texture.foreign_link + + struct wl_list render_buffers; // wlr_vk_render_buffer.link + + // Pool of command buffers + struct wlr_vk_command_buffer command_buffers[VULKAN_COMMAND_BUFFERS_CAP]; + + struct { + struct wlr_vk_command_buffer *cb; + uint64_t last_timeline_point; + struct wl_list buffers; // wlr_vk_shared_buffer.link + } stage; + + struct { + bool initialized; + uint32_t drm_format; + uint32_t width, height; + VkImage dst_image; + VkDeviceMemory dst_img_memory; + } read_pixels_cache; +}; + +// vertex shader push constant range data +struct wlr_vk_vert_pcr_data { + float mat4[4][4]; + float uv_off[2]; + float uv_size[2]; +}; + +struct wlr_vk_texture_view { + struct wl_list link; // struct wlr_vk_texture.views + const struct wlr_vk_pipeline_layout *layout; + + VkDescriptorSet ds; + VkImageView image_view; + struct wlr_vk_descriptor_pool *ds_pool; +}; + +struct wlr_vk_pipeline *setup_get_or_create_pipeline( + struct wlr_vk_render_format_setup *setup, + const struct wlr_vk_pipeline_key *key); +struct wlr_vk_pipeline_layout *get_or_create_pipeline_layout( + struct wlr_vk_renderer *renderer, + const struct wlr_vk_pipeline_layout_key *key); +struct wlr_vk_texture_view *vulkan_texture_get_or_create_view( + struct wlr_vk_texture *texture, + const struct wlr_vk_pipeline_layout *layout); + +// Creates a vulkan renderer for the given device. +struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev); + +// stage utility - for uploading/retrieving data +// Gets an command buffer in recording state which is guaranteed to be +// executed before the next frame. +VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer); + +// Submits the current stage command buffer and waits until it has +// finished execution. +bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer); + +struct wlr_vk_render_pass { + struct wlr_render_pass base; + struct wlr_vk_renderer *renderer; + struct wlr_vk_render_buffer *render_buffer; + struct wlr_vk_command_buffer *command_buffer; + struct rect_union updated_region; + VkPipeline bound_pipeline; + float projection[9]; + bool failed; +}; + +struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *renderer, + struct wlr_vk_render_buffer *buffer); + +// Suballocates a buffer span with the given size that can be mapped +// and used as staging buffer. The allocation is implicitly released when the +// stage cb has finished execution. The start of the span will be a multiple +// of the given alignment. +struct wlr_vk_buffer_span vulkan_get_stage_span( + struct wlr_vk_renderer *renderer, VkDeviceSize size, + VkDeviceSize alignment); + +// Tries to allocate a texture descriptor set. Will additionally +// return the pool it was allocated from when successful (for freeing it later). +struct wlr_vk_descriptor_pool *vulkan_alloc_texture_ds( + struct wlr_vk_renderer *renderer, VkDescriptorSetLayout ds_layout, + VkDescriptorSet *ds); + +// Tries to allocate a descriptor set for the blending image. Will +// additionally return the pool it was allocated from when successful +// (for freeing it later). +struct wlr_vk_descriptor_pool *vulkan_alloc_blend_ds( + struct wlr_vk_renderer *renderer, VkDescriptorSet *ds); + +// Frees the given descriptor set from the pool its pool. +void vulkan_free_ds(struct wlr_vk_renderer *renderer, + struct wlr_vk_descriptor_pool *pool, VkDescriptorSet ds); +struct wlr_vk_format_props *vulkan_format_props_from_drm( + struct wlr_vk_device *dev, uint32_t drm_format); +struct wlr_vk_renderer *vulkan_get_renderer(struct wlr_renderer *r); + +struct wlr_vk_command_buffer *vulkan_acquire_command_buffer( + struct wlr_vk_renderer *renderer); +uint64_t vulkan_end_command_buffer(struct wlr_vk_command_buffer *cb, + struct wlr_vk_renderer *renderer); +void vulkan_reset_command_buffer(struct wlr_vk_command_buffer *cb); +bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb, + struct wlr_vk_renderer *renderer); + +bool vulkan_sync_render_buffer(struct wlr_vk_renderer *renderer, + struct wlr_vk_render_buffer *render_buffer, struct wlr_vk_command_buffer *cb); +bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture); + +bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, + VkFormat src_format, VkImage src_image, + uint32_t drm_format, uint32_t stride, + uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y, + uint32_t dst_x, uint32_t dst_y, void *data); + +// State (e.g. image texture) associated with a surface. +struct wlr_vk_texture { + struct wlr_texture wlr_texture; + struct wlr_vk_renderer *renderer; + uint32_t mem_count; + VkDeviceMemory memories[WLR_DMABUF_MAX_PLANES]; + VkImage image; + const struct wlr_vk_format *format; + enum wlr_vk_texture_transform transform; + struct wlr_vk_command_buffer *last_used_cb; // to track when it can be destroyed + bool dmabuf_imported; + bool owned; // if dmabuf_imported: whether we have ownership of the image + bool transitioned; // if dma_imported: whether we transitioned it away from preinit + bool has_alpha; // whether the image is has alpha channel + bool using_mutable_srgb; // is this accessed through _SRGB format view + struct wl_list foreign_link; // wlr_vk_renderer.foreign_textures + struct wl_list destroy_link; // wlr_vk_command_buffer.destroy_textures + struct wl_list link; // wlr_vk_renderer.textures + + // If imported from a wlr_buffer + struct wlr_buffer *buffer; + struct wlr_addon buffer_addon; + // For DMA-BUF implicit sync interop + VkSemaphore foreign_semaphores[WLR_DMABUF_MAX_PLANES]; + + struct wl_list views; // struct wlr_vk_texture_ds.link +}; + +struct wlr_vk_texture *vulkan_get_texture(struct wlr_texture *wlr_texture); +VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, + const struct wlr_dmabuf_attributes *attribs, + VkDeviceMemory mems[static WLR_DMABUF_MAX_PLANES], uint32_t *n_mems, + bool for_render, bool *using_mutable_srgb); +struct wlr_texture *vulkan_texture_from_buffer( + struct wlr_renderer *wlr_renderer, struct wlr_buffer *buffer); +void vulkan_texture_destroy(struct wlr_vk_texture *texture); + +struct wlr_vk_descriptor_pool { + VkDescriptorPool pool; + uint32_t free; // number of textures that can be allocated + struct wl_list link; // wlr_vk_renderer.descriptor_pools +}; + +struct wlr_vk_allocation { + VkDeviceSize start; + VkDeviceSize size; +}; + +// List of suballocated staging buffers. +// Used to upload to/read from device local images. +struct wlr_vk_shared_buffer { + struct wl_list link; // wlr_vk_renderer.stage.buffers or wlr_vk_command_buffer.stage_buffers + VkBuffer buffer; + VkDeviceMemory memory; + VkDeviceSize buf_size; + struct wl_array allocs; // struct wlr_vk_allocation +}; + +// Suballocated range on a buffer. +struct wlr_vk_buffer_span { + struct wlr_vk_shared_buffer *buffer; + struct wlr_vk_allocation alloc; +}; + +// util +const char *vulkan_strerror(VkResult err); +void vulkan_change_layout(VkCommandBuffer cb, VkImage img, + VkImageLayout ol, VkPipelineStageFlags srcs, VkAccessFlags srca, + VkImageLayout nl, VkPipelineStageFlags dsts, VkAccessFlags dsta); + +#define wlr_vk_error(fmt, res, ...) wlr_log(WLR_ERROR, fmt ": %s (%d)", \ + vulkan_strerror(res), res, ##__VA_ARGS__) + +#endif // RENDER_VULKAN_H diff --git a/include/render/wlr_renderer.h b/include/render/wlr_renderer.h new file mode 100644 index 0000000..a8777bc --- /dev/null +++ b/include/render/wlr_renderer.h @@ -0,0 +1,23 @@ +#ifndef RENDER_WLR_RENDERER_H +#define RENDER_WLR_RENDERER_H + +#include + +/** + * Automatically select and create a renderer suitable for the DRM FD. + */ +struct wlr_renderer *renderer_autocreate_with_drm_fd(int drm_fd); +/** + * Get the supported render formats. Buffers allocated with a format from this + * list may be attached via wlr_renderer_begin_with_buffer. + */ +const struct wlr_drm_format_set *wlr_renderer_get_render_formats( + struct wlr_renderer *renderer); +/** + * Get the supported buffer capabilities. + * + * This functions returns a bitfield of supported wlr_buffer_cap. + */ +uint32_t renderer_get_render_buffer_caps(struct wlr_renderer *renderer); + +#endif diff --git a/include/types/wlr_buffer.h b/include/types/wlr_buffer.h new file mode 100644 index 0000000..947ccde --- /dev/null +++ b/include/types/wlr_buffer.h @@ -0,0 +1,77 @@ +#ifndef TYPES_WLR_BUFFER +#define TYPES_WLR_BUFFER + +#include + +/** + * A read-only buffer that holds a data pointer. + * + * This is suitable for passing raw pixel data to a function that accepts a + * wlr_buffer. + */ +struct wlr_readonly_data_buffer { + struct wlr_buffer base; + + const void *data; + uint32_t format; + size_t stride; + + void *saved_data; +}; + +/** + * Wraps a read-only data pointer into a wlr_buffer. The data pointer may be + * accessed until readonly_data_buffer_drop() is called. + */ +struct wlr_readonly_data_buffer *readonly_data_buffer_create(uint32_t format, + size_t stride, uint32_t width, uint32_t height, const void *data); +/** + * Drops ownership of the buffer (see wlr_buffer_drop() for more details) and + * perform a copy of the data pointer if a consumer still has the buffer locked. + */ +bool readonly_data_buffer_drop(struct wlr_readonly_data_buffer *buffer); + +struct wlr_dmabuf_buffer { + struct wlr_buffer base; + struct wlr_dmabuf_attributes dmabuf; + bool saved; +}; + +/** + * Wraps a DMA-BUF into a wlr_buffer. The DMA-BUF may be accessed until + * dmabuf_buffer_drop() is called. + */ +struct wlr_dmabuf_buffer *dmabuf_buffer_create( + struct wlr_dmabuf_attributes *dmabuf); +/** + * Drops ownership of the buffer (see wlr_buffer_drop() for more details) and + * takes a reference to the DMA-BUF (by dup'ing its file descriptors) if a + * consumer still has the buffer locked. + */ +bool dmabuf_buffer_drop(struct wlr_dmabuf_buffer *buffer); + +/** + * Check whether a buffer is fully opaque. + * + * When true is returned, the buffer is guaranteed to be fully opaque, but the + * reverse is not true: false may be returned in cases where the buffer is fully + * opaque. + */ +bool buffer_is_opaque(struct wlr_buffer *buffer); + +/** + * Creates a struct wlr_client_buffer from a given struct wlr_buffer by creating + * a texture from it, and copying its struct wl_resource. + */ +struct wlr_client_buffer *wlr_client_buffer_create(struct wlr_buffer *buffer, + struct wlr_renderer *renderer); +/** + * Try to update the buffer's content. + * + * Fails if there's more than one reference to the buffer or if the texture + * isn't mutable. + */ +bool wlr_client_buffer_apply_damage(struct wlr_client_buffer *client_buffer, + struct wlr_buffer *next, const pixman_region32_t *damage); + +#endif diff --git a/include/types/wlr_data_device.h b/include/types/wlr_data_device.h new file mode 100644 index 0000000..99aff42 --- /dev/null +++ b/include/types/wlr_data_device.h @@ -0,0 +1,43 @@ +#ifndef TYPES_WLR_DATA_DEVICE_H +#define TYPES_WLR_DATA_DEVICE_H + +#include +#include + +#define DATA_DEVICE_ALL_ACTIONS (WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | \ + WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | \ + WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) + +struct wlr_client_data_source { + struct wlr_data_source source; + struct wlr_data_source_impl impl; + struct wl_resource *resource; + bool finalized; +}; + +extern const struct wlr_surface_role drag_icon_surface_role; + +struct wlr_data_offer *data_offer_create(struct wl_resource *device_resource, + struct wlr_data_source *source, enum wlr_data_offer_type type); +void data_offer_update_action(struct wlr_data_offer *offer); +void data_offer_destroy(struct wlr_data_offer *offer); + +struct wlr_client_data_source *client_data_source_create( + struct wl_client *client, uint32_t version, uint32_t id, + struct wl_list *resource_list); +struct wlr_client_data_source *client_data_source_from_resource( + struct wl_resource *resource); +void data_source_notify_finish(struct wlr_data_source *source); + +struct wlr_seat_client *seat_client_from_data_device_resource( + struct wl_resource *resource); +/** + * Creates a new wl_data_offer if there is a wl_data_source currently set as + * the seat selection and sends it to the seat client, followed by the + * wl_data_device.selection() event. If there is no current selection, the + * wl_data_device.selection() event will carry a NULL wl_data_offer. If the + * client does not have a wl_data_device for the seat nothing will be done. + */ +void seat_client_send_selection(struct wlr_seat_client *seat_client); + +#endif diff --git a/include/types/wlr_keyboard.h b/include/types/wlr_keyboard.h new file mode 100644 index 0000000..7cd26e8 --- /dev/null +++ b/include/types/wlr_keyboard.h @@ -0,0 +1,8 @@ +#include + +void keyboard_key_update(struct wlr_keyboard *keyboard, + struct wlr_keyboard_key_event *event); + +bool keyboard_modifier_update(struct wlr_keyboard *keyboard); + +void keyboard_led_update(struct wlr_keyboard *keyboard); diff --git a/include/types/wlr_matrix.h b/include/types/wlr_matrix.h new file mode 100644 index 0000000..ce599dc --- /dev/null +++ b/include/types/wlr_matrix.h @@ -0,0 +1,15 @@ +#ifndef TYPES_WLR_MATRIX_H +#define TYPES_WLR_MATRIX_H + +#include + +/** + * Writes a 2D orthographic projection matrix to mat of (width, height) with a + * specified wl_output_transform. + * + * Equivalent to glOrtho(0, width, 0, height, 1, -1) with the transform applied. + */ +void matrix_projection(float mat[static 9], int width, int height, + enum wl_output_transform transform); + +#endif diff --git a/include/types/wlr_output.h b/include/types/wlr_output.h new file mode 100644 index 0000000..7bc06f9 --- /dev/null +++ b/include/types/wlr_output.h @@ -0,0 +1,25 @@ +#ifndef TYPES_WLR_OUTPUT_H +#define TYPES_WLR_OUTPUT_H + +#include +#include + +void output_pending_resolution(struct wlr_output *output, + const struct wlr_output_state *state, int *width, int *height); +bool output_pending_enabled(struct wlr_output *output, + const struct wlr_output_state *state); + +bool output_pick_format(struct wlr_output *output, + const struct wlr_drm_format_set *display_formats, + struct wlr_drm_format *format, uint32_t fmt); +bool output_ensure_buffer(struct wlr_output *output, + struct wlr_output_state *state, bool *new_back_buffer); + +bool output_cursor_set_texture(struct wlr_output_cursor *cursor, + struct wlr_texture *texture, bool own_texture, const struct wlr_fbox *src_box, + int dst_width, int dst_height, enum wl_output_transform transform, + int32_t hotspot_x, int32_t hotspot_y); + +void output_defer_present(struct wlr_output *output, struct wlr_output_event_present event); + +#endif diff --git a/include/types/wlr_region.h b/include/types/wlr_region.h new file mode 100644 index 0000000..b00f880 --- /dev/null +++ b/include/types/wlr_region.h @@ -0,0 +1,14 @@ +#ifndef TYPES_WLR_REGION_H +#define TYPES_WLR_REGION_H + +#include + +struct wl_client; + +/* + * Creates a new region resource with the provided new ID. + */ +struct wl_resource *region_create(struct wl_client *client, + uint32_t version, uint32_t id); + +#endif diff --git a/include/types/wlr_scene.h b/include/types/wlr_scene.h new file mode 100644 index 0000000..80dcfd1 --- /dev/null +++ b/include/types/wlr_scene.h @@ -0,0 +1,10 @@ +#ifndef TYPES_WLR_SCENE_H +#define TYPES_WLR_SCENE_H + +#include + +struct wlr_scene *scene_node_get_root(struct wlr_scene_node *node); + +void scene_surface_set_clip(struct wlr_scene_surface *surface, struct wlr_box *clip); + +#endif diff --git a/include/types/wlr_seat.h b/include/types/wlr_seat.h new file mode 100644 index 0000000..844faba --- /dev/null +++ b/include/types/wlr_seat.h @@ -0,0 +1,33 @@ +#ifndef TYPES_WLR_SEAT_H +#define TYPES_WLR_SEAT_H + +#include +#include + +extern const struct wlr_pointer_grab_interface default_pointer_grab_impl; +extern const struct wlr_keyboard_grab_interface default_keyboard_grab_impl; +extern const struct wlr_touch_grab_interface default_touch_grab_impl; + +void seat_client_create_pointer(struct wlr_seat_client *seat_client, + uint32_t version, uint32_t id); +void seat_client_create_inert_pointer(struct wl_client *client, + uint32_t version, uint32_t id); +void seat_client_destroy_pointer(struct wl_resource *resource); +void seat_client_send_pointer_leave_raw(struct wlr_seat_client *seat_client, + struct wlr_surface *surface); + +void seat_client_create_keyboard(struct wlr_seat_client *seat_client, + uint32_t version, uint32_t id); +void seat_client_create_inert_keyboard(struct wl_client *client, + uint32_t version, uint32_t id); +void seat_client_destroy_keyboard(struct wl_resource *resource); +void seat_client_send_keyboard_leave_raw(struct wlr_seat_client *seat_client, + struct wlr_surface *surface); + +void seat_client_create_touch(struct wlr_seat_client *seat_client, + uint32_t version, uint32_t id); +void seat_client_create_inert_touch(struct wl_client *client, + uint32_t version, uint32_t id); +void seat_client_destroy_touch(struct wl_resource *resource); + +#endif diff --git a/include/types/wlr_subcompositor.h b/include/types/wlr_subcompositor.h new file mode 100644 index 0000000..147b283 --- /dev/null +++ b/include/types/wlr_subcompositor.h @@ -0,0 +1,9 @@ +#ifndef TYPES_WLR_SUBCOMPOSITOR_H +#define TYPES_WLR_SUBCOMPOSITOR_H + +#include + +void subsurface_consider_map(struct wlr_subsurface *subsurface); +void subsurface_handle_parent_commit(struct wlr_subsurface *subsurface); + +#endif diff --git a/include/types/wlr_tablet_v2.h b/include/types/wlr_tablet_v2.h new file mode 100644 index 0000000..2e54adc --- /dev/null +++ b/include/types/wlr_tablet_v2.h @@ -0,0 +1,94 @@ +#ifndef TYPES_WLR_TABLET_V2_H +#define TYPES_WLR_TABLET_V2_H + +#include "tablet-unstable-v2-protocol.h" +#include +#include + +struct wlr_tablet_seat_v2 { + struct wl_list link; // wlr_tablet_manager_v2.seats + struct wlr_seat *wlr_seat; + struct wlr_tablet_manager_v2 *manager; + + struct wl_list tablets; // wlr_tablet_v2_tablet.link + struct wl_list tools; + struct wl_list pads; + + struct wl_list clients; // wlr_tablet_seat_v2_client.link + + struct wl_listener seat_destroy; +}; + +struct wlr_tablet_seat_client_v2 { + struct wl_list seat_link; + struct wl_list client_link; + struct wl_client *wl_client; + struct wl_resource *resource; + + struct wlr_tablet_manager_client_v2 *client; + struct wlr_seat_client *seat_client; + + struct wl_listener seat_client_destroy; + + struct wl_list tools; // wlr_tablet_tool_client_v2.link + struct wl_list tablets; // wlr_tablet_client_v2.link + struct wl_list pads; // wlr_tablet_pad_client_v2.link +}; + +struct wlr_tablet_client_v2 { + struct wl_list seat_link; // wlr_tablet_seat_client_v2.tablet + struct wl_list tablet_link; // wlr_tablet_v2_tablet.clients + struct wl_client *client; + struct wl_resource *resource; +}; + +struct wlr_tablet_pad_client_v2 { + struct wl_list seat_link; + struct wl_list pad_link; + struct wl_client *client; + struct wl_resource *resource; + struct wlr_tablet_v2_tablet_pad *pad; + struct wlr_tablet_seat_client_v2 *seat; + + size_t button_count; + + size_t group_count; + struct wl_resource **groups; + + size_t ring_count; + struct wl_resource **rings; + + size_t strip_count; + struct wl_resource **strips; +}; + +struct wlr_tablet_tool_client_v2 { + struct wl_list seat_link; + struct wl_list tool_link; + struct wl_client *client; + struct wl_resource *resource; + struct wlr_tablet_v2_tablet_tool *tool; + struct wlr_tablet_seat_client_v2 *seat; + + struct wl_event_source *frame_source; +}; + +struct wlr_tablet_client_v2 *tablet_client_from_resource(struct wl_resource *resource); +void destroy_tablet_v2(struct wl_resource *resource); +void add_tablet_client(struct wlr_tablet_seat_client_v2 *seat, struct wlr_tablet_v2_tablet *tablet); + +void destroy_tablet_pad_v2(struct wl_resource *resource); +struct wlr_tablet_pad_client_v2 *tablet_pad_client_from_resource(struct wl_resource *resource); +void add_tablet_pad_client(struct wlr_tablet_seat_client_v2 *seat, struct wlr_tablet_v2_tablet_pad *pad); + +void destroy_tablet_tool_v2(struct wl_resource *resource); +struct wlr_tablet_tool_client_v2 *tablet_tool_client_from_resource(struct wl_resource *resource); +void add_tablet_tool_client(struct wlr_tablet_seat_client_v2 *seat, struct wlr_tablet_v2_tablet_tool *tool); + +struct wlr_tablet_seat_client_v2 *tablet_seat_client_from_resource(struct wl_resource *resource); +void tablet_seat_client_v2_destroy(struct wl_resource *resource); +struct wlr_tablet_seat_v2 *get_or_create_tablet_seat( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat); + +#endif /* TYPES_WLR_TABLET_V2_H */ diff --git a/include/types/wlr_xdg_shell.h b/include/types/wlr_xdg_shell.h new file mode 100644 index 0000000..1b4c89c --- /dev/null +++ b/include/types/wlr_xdg_shell.h @@ -0,0 +1,39 @@ +#ifndef TYPES_WLR_XDG_SHELL_H +#define TYPES_WLR_XDG_SHELL_H + +#include +#include +#include "xdg-shell-protocol.h" + +void create_xdg_surface(struct wlr_xdg_client *client, struct wlr_surface *wlr_surface, + uint32_t id); +void destroy_xdg_surface(struct wlr_xdg_surface *surface); + +bool set_xdg_surface_role(struct wlr_xdg_surface *surface, enum wlr_xdg_surface_role role); +void set_xdg_surface_role_object(struct wlr_xdg_surface *surface, + struct wl_resource *role_resource); + +void create_xdg_positioner(struct wlr_xdg_client *client, uint32_t id); + +void create_xdg_popup(struct wlr_xdg_surface *surface, + struct wlr_xdg_surface *parent, + struct wlr_xdg_positioner *positioner, uint32_t id); +void reset_xdg_popup(struct wlr_xdg_popup *popup); +void destroy_xdg_popup(struct wlr_xdg_popup *popup); +void handle_xdg_popup_client_commit(struct wlr_xdg_popup *popup); +struct wlr_xdg_popup_configure *send_xdg_popup_configure( + struct wlr_xdg_popup *popup); +void handle_xdg_popup_ack_configure(struct wlr_xdg_popup *popup, + struct wlr_xdg_popup_configure *configure); + +void create_xdg_toplevel(struct wlr_xdg_surface *surface, + uint32_t id); +void reset_xdg_toplevel(struct wlr_xdg_toplevel *toplevel); +void destroy_xdg_toplevel(struct wlr_xdg_toplevel *toplevel); +void handle_xdg_toplevel_client_commit(struct wlr_xdg_toplevel *toplevel); +struct wlr_xdg_toplevel_configure *send_xdg_toplevel_configure( + struct wlr_xdg_toplevel *toplevel); +void handle_xdg_toplevel_ack_configure(struct wlr_xdg_toplevel *toplevel, + struct wlr_xdg_toplevel_configure *configure); + +#endif diff --git a/include/util/array.h b/include/util/array.h new file mode 100644 index 0000000..a51bdb6 --- /dev/null +++ b/include/util/array.h @@ -0,0 +1,18 @@ +#ifndef UTIL_ARRAY_H +#define UTIL_ARRAY_H + +#include +#include +#include + +/** + * Remove a chunk of memory of the specified size at the specified offset. + */ +void array_remove_at(struct wl_array *arr, size_t offset, size_t size); + +/** + * Grow or shrink the array to fit the specifized size. + */ +bool array_realloc(struct wl_array *arr, size_t size); + +#endif diff --git a/include/util/env.h b/include/util/env.h new file mode 100644 index 0000000..e271f4b --- /dev/null +++ b/include/util/env.h @@ -0,0 +1,23 @@ +#ifndef UTIL_ENV_H +#define UTIL_ENV_H + +#include +#include + +/** + * Parse a bool from an environment variable. + * + * On success, the parsed value is returned. On error, false is returned. + */ +bool env_parse_bool(const char *option); + +/** + * Pick a choice from an environment variable. + * + * On success, the choice index is returned. On error, zero is returned. + * + * switches is a NULL-terminated array. + */ +size_t env_parse_switch(const char *option, const char **switches); + +#endif diff --git a/include/util/global.h b/include/util/global.h new file mode 100644 index 0000000..1c979ab --- /dev/null +++ b/include/util/global.h @@ -0,0 +1,14 @@ +#ifndef UTIL_GLOBAL_H +#define UTIL_GLOBAL_H + +#include + +/** + * Destroy a transient global. + * + * Globals that are created and destroyed on the fly need special handling to + * prevent race conditions with wl_registry. Use this function to destroy them. + */ +void wlr_global_destroy_safe(struct wl_global *global); + +#endif diff --git a/include/util/rect_union.h b/include/util/rect_union.h new file mode 100644 index 0000000..2d74f94 --- /dev/null +++ b/include/util/rect_union.h @@ -0,0 +1,76 @@ +#ifndef UTIL_RECT_UNION_H +#define UTIL_RECT_UNION_H + +#include +#include +#include +#include + +/** + * `struct rect_union` is a data structure to efficiently accumulate a number + * of rectangles and then, when needed, compute a disjoint cover of their union. + * (That is: produce a list of disjoint rectangles which covers every point + * that was contained in one of the added rectangles.) + * + * Add rectangles to the union with `rect_union_add()`; to compute the disjoint + * union, run `rect_union_evaluate()`, which will place the result in `.region`. + * If there were any allocation failures, `.region` will instead contain the + * bounding box for the entire list of rectangles. + * + * Example usage: + * + * struct rect_union r; + * rect_union_init(&r); + * for (j in ...) { + * rect_union_add(&r, box[j]); + * } + * const pixman_region32_t *reg = rect_union_evaluate(&r); + * int nboxes; + * pixman_box32_t *boxes = pixman_region32_rectangles(reg, nboxes); + * for (int i = 0; i < nboxes; i++) { + * do_stuff(boxes[i]); + * } + * rect_union_destroy(&r); + * + */ +struct rect_union { + pixman_box32_t bounding_box; // Always up-to-date bounding box + pixman_region32_t region; // Updated only on _evaluate() + + struct wl_array unsorted; // pixman_box32_t + bool alloc_failure; // If this is true, fall back to computing a bounding box +}; + +/** + * Initialize *r, disregarding any previous contents. + */ +void rect_union_init(struct rect_union *r); + +/** + * Free heap data associated with *r; should only be called after rect_union_init. + * Leaves *r in an invalid state. + */ +void rect_union_finish(struct rect_union *r); + +/** + * Add a rectangle to the union. If `box` is empty or invalid (x2 > x1 || y2 > y1), + * do nothing. + * + * Amortized time: O(1) + */ +void rect_union_add(struct rect_union *r, pixman_box32_t box); + +/** + * Compute an exact cover of the rectangles added so far, and return + * a pointer to a pixman_region32_t giving that cover. The pointer will + * remain valid until the next time *r is modified. If there was an allocation + * failure, this function may return a single-rectangle bounding box instead. + * + * This may be called multiple times and interleaved with rect_union_add(). + * + * Worst case time: O(t^2), where t is the number of rectangles in the list. + * Best case time: O(t), if rectangles are disjoint and have y-x band structure + */ +const pixman_region32_t *rect_union_evaluate(struct rect_union *r); + +#endif diff --git a/include/util/set.h b/include/util/set.h new file mode 100644 index 0000000..d633010 --- /dev/null +++ b/include/util/set.h @@ -0,0 +1,29 @@ +#ifndef UTIL_SET_H +#define UTIL_SET_H + +#include +#include +#include + +/** + * Add target to values. + * + * Target is added to the end of the set. + * + * Returns the index of target, or -1 if the set is full or target already + * exists. + */ +ssize_t set_add(uint32_t values[], size_t *len, size_t cap, uint32_t target); + +/** + * Remove target from values. + * + * When target is removed, the last element of the set is moved to where + * target was. + * + * Returns the previous index of target, or -1 if target wasn't in values. + */ +ssize_t set_remove(uint32_t values[], size_t *len, size_t cap, uint32_t target); + +#endif + diff --git a/include/util/shm.h b/include/util/shm.h new file mode 100644 index 0000000..3beabca --- /dev/null +++ b/include/util/shm.h @@ -0,0 +1,11 @@ +#ifndef UTIL_SHM_H +#define UTIL_SHM_H + +#include +#include + +int create_shm_file(void); +int allocate_shm_file(size_t size); +bool allocate_shm_file_pair(size_t size, int *rw_fd, int *ro_fd); + +#endif diff --git a/include/util/time.h b/include/util/time.h new file mode 100644 index 0000000..3f76aa4 --- /dev/null +++ b/include/util/time.h @@ -0,0 +1,33 @@ +#ifndef UTIL_TIME_H +#define UTIL_TIME_H + +#include +#include + +/** + * Get the current time, in milliseconds. + */ +int64_t get_current_time_msec(void); + +/** + * Convert a timespec to milliseconds. + */ +int64_t timespec_to_msec(const struct timespec *a); + +/** + * Convert a timespec to nanoseconds. + */ +int64_t timespec_to_nsec(const struct timespec *a); + +/** + * Convert nanoseconds to a timespec. + */ +void timespec_from_nsec(struct timespec *r, int64_t nsec); + +/** + * Subtracts timespec `b` from timespec `a`, and stores the difference in `r`. + */ +void timespec_sub(struct timespec *r, const struct timespec *a, + const struct timespec *b); + +#endif diff --git a/include/util/token.h b/include/util/token.h new file mode 100644 index 0000000..3aef69c --- /dev/null +++ b/include/util/token.h @@ -0,0 +1,16 @@ +#ifndef UTIL_TOKEN_H +#define UTIL_TOKEN_H + +#include + +/** + * Number of bytes used by a token, including the terminating zero byte. + */ +#define TOKEN_SIZE 33 + +/** + * Generate a random token string. + */ +bool generate_token(char out[static TOKEN_SIZE]); + +#endif diff --git a/include/util/utf8.h b/include/util/utf8.h new file mode 100644 index 0000000..4d5172c --- /dev/null +++ b/include/util/utf8.h @@ -0,0 +1,11 @@ +#ifndef UTIL_UTF8_H +#define UTIL_UTF8_H + +#include + +/** + * Return true if and only if the string is a valid UTF-8 sequence. + */ +bool is_utf8(const char *string); + +#endif diff --git a/include/wlr/backend.h b/include/wlr/backend.h new file mode 100644 index 0000000..e7d230b --- /dev/null +++ b/include/wlr/backend.h @@ -0,0 +1,65 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_H +#define WLR_BACKEND_H + +#include + +struct wlr_session; +struct wlr_backend_impl; + +/** + * A backend provides a set of input and output devices. + */ +struct wlr_backend { + const struct wlr_backend_impl *impl; + + struct { + /** Raised when destroyed */ + struct wl_signal destroy; + /** Raised when new inputs are added, passed the struct wlr_input_device */ + struct wl_signal new_input; + /** Raised when new outputs are added, passed the struct wlr_output */ + struct wl_signal new_output; + } events; +}; + +/** + * Automatically initializes the most suitable backend given the environment. + * Will always return a multi-backend. The backend is created but not started. + * Returns NULL on failure. + * + * If session_ptr is not NULL, it's populated with the session which has been + * created with the backend, if any. + * + * The multi-backend will be destroyed if one of the primary underlying + * backends is destroyed (e.g. if the primary DRM device is unplugged). + */ +struct wlr_backend *wlr_backend_autocreate(struct wl_event_loop *loop, + struct wlr_session **session_ptr); +/** + * Start the backend. This may signal new_input or new_output immediately, but + * may also wait until the display's event loop begins. Returns false on + * failure. + */ +bool wlr_backend_start(struct wlr_backend *backend); +/** + * Destroy the backend and clean up all of its resources. Normally called + * automatically when the struct wl_display is destroyed. + */ +void wlr_backend_destroy(struct wlr_backend *backend); +/** + * Returns the DRM node file descriptor used by the backend's underlying + * platform. Can be used by consumers for additional rendering operations. + * The consumer must not close the file descriptor since the backend continues + * to have ownership of it. + */ +int wlr_backend_get_drm_fd(struct wlr_backend *backend); + +#endif diff --git a/include/wlr/backend/drm.h b/include/wlr/backend/drm.h new file mode 100644 index 0000000..3ca6390 --- /dev/null +++ b/include/wlr/backend/drm.h @@ -0,0 +1,103 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_DRM_H +#define WLR_BACKEND_DRM_H + +#include +#include +#include +#include + +struct wlr_drm_backend; +typedef struct _drmModeModeInfo drmModeModeInfo; + +struct wlr_drm_lease { + int fd; + uint32_t lessee_id; + struct wlr_drm_backend *backend; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +/** + * Creates a DRM backend using the specified GPU file descriptor (typically from + * a device node in /dev/dri). + * + * To slave this to another DRM backend, pass it as the parent (which _must_ be + * a DRM backend, other kinds of backends raise SIGABRT). + */ +struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session, + struct wlr_device *dev, struct wlr_backend *parent); + +bool wlr_backend_is_drm(struct wlr_backend *backend); +bool wlr_output_is_drm(struct wlr_output *output); + +/** + * Get the parent DRM backend, if any. + */ +struct wlr_backend *wlr_drm_backend_get_parent(struct wlr_backend *backend); + +/** + * Get the KMS connector object ID. + */ +uint32_t wlr_drm_connector_get_id(struct wlr_output *output); + +/** + * Tries to open non-master DRM FD. The compositor must not call drmSetMaster() + * on the returned FD. + * + * Returns a valid opened DRM FD, or -1 on error. + */ +int wlr_drm_backend_get_non_master_fd(struct wlr_backend *backend); + +/** + * Leases the given outputs to the caller. The outputs must be from the + * associated DRM backend. + * + * Returns NULL on error. + */ +struct wlr_drm_lease *wlr_drm_create_lease(struct wlr_output **outputs, + size_t n_outputs, int *lease_fd); + +/** + * Terminates and destroys a given lease. + * + * The outputs will be owned again by the backend. + */ +void wlr_drm_lease_terminate(struct wlr_drm_lease *lease); + +/** + * Add mode to the list of available modes. + */ +struct wlr_output_mode *wlr_drm_connector_add_mode(struct wlr_output *output, + const drmModeModeInfo *mode); + +/** + * Get the raw DRM mode information from a struct wlr_output_mode. + * + * The mode passed in must belong to a DRM output. + */ +const drmModeModeInfo *wlr_drm_mode_get_info(struct wlr_output_mode *mode); + +/** + * Get the connector's panel orientation. + * + * On some devices the panel is mounted in the casing in such a way that the + * top side of the panel does not match with the top side of the device. This + * function returns the output transform which needs to be applied to compensate + * for this. + */ +enum wl_output_transform wlr_drm_connector_get_panel_orientation( + struct wlr_output *output); + +#endif diff --git a/include/wlr/backend/headless.h b/include/wlr/backend/headless.h new file mode 100644 index 0000000..126f1ff --- /dev/null +++ b/include/wlr/backend/headless.h @@ -0,0 +1,31 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_HEADLESS_H +#define WLR_BACKEND_HEADLESS_H + +#include +#include + +/** + * Creates a headless backend. A headless backend has no outputs or inputs by + * default. + */ +struct wlr_backend *wlr_headless_backend_create(struct wl_event_loop *loop); +/** + * Create a new headless output. + * + * The buffers presented on the output won't be displayed to the user. + */ +struct wlr_output *wlr_headless_add_output(struct wlr_backend *backend, + unsigned int width, unsigned int height); + +bool wlr_backend_is_headless(struct wlr_backend *backend); +bool wlr_output_is_headless(struct wlr_output *output); + +#endif diff --git a/include/wlr/backend/interface.h b/include/wlr/backend/interface.h new file mode 100644 index 0000000..da57cae --- /dev/null +++ b/include/wlr/backend/interface.h @@ -0,0 +1,33 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_INTERFACE_H +#define WLR_BACKEND_INTERFACE_H + +#include +#include + +struct wlr_backend_impl { + bool (*start)(struct wlr_backend *backend); + void (*destroy)(struct wlr_backend *backend); + int (*get_drm_fd)(struct wlr_backend *backend); + uint32_t (*get_buffer_caps)(struct wlr_backend *backend); +}; + +/** + * Initializes common state on a struct wlr_backend and sets the implementation + * to the provided struct wlr_backend_impl reference. + */ +void wlr_backend_init(struct wlr_backend *backend, + const struct wlr_backend_impl *impl); +/** + * Emit the destroy event and clean up common backend state. + */ +void wlr_backend_finish(struct wlr_backend *backend); + +#endif diff --git a/include/wlr/backend/libinput.h b/include/wlr/backend/libinput.h new file mode 100644 index 0000000..663f71a --- /dev/null +++ b/include/wlr/backend/libinput.h @@ -0,0 +1,29 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_LIBINPUT_H +#define WLR_BACKEND_LIBINPUT_H + +#include +#include +#include +#include + +struct wlr_input_device; + +struct wlr_backend *wlr_libinput_backend_create(struct wlr_session *session); +/** + * Gets the underlying struct libinput_device handle for the given input device. + */ +struct libinput_device *wlr_libinput_get_device_handle( + struct wlr_input_device *dev); + +bool wlr_backend_is_libinput(struct wlr_backend *backend); +bool wlr_input_device_is_libinput(struct wlr_input_device *device); + +#endif diff --git a/include/wlr/backend/multi.h b/include/wlr/backend/multi.h new file mode 100644 index 0000000..c4322d9 --- /dev/null +++ b/include/wlr/backend/multi.h @@ -0,0 +1,35 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_MULTI_H +#define WLR_BACKEND_MULTI_H + +#include + +/** + * Creates a multi-backend. Multi-backends wrap an arbitrary number of backends + * and aggregate their new_output/new_input signals. + */ +struct wlr_backend *wlr_multi_backend_create(struct wl_event_loop *loop); +/** + * Adds the given backend to the multi backend. This should be done before the + * new backend is started. + */ +bool wlr_multi_backend_add(struct wlr_backend *multi, + struct wlr_backend *backend); + +void wlr_multi_backend_remove(struct wlr_backend *multi, + struct wlr_backend *backend); + +bool wlr_backend_is_multi(struct wlr_backend *backend); +bool wlr_multi_is_empty(struct wlr_backend *backend); + +void wlr_multi_for_each_backend(struct wlr_backend *backend, + void (*callback)(struct wlr_backend *backend, void *data), void *data); + +#endif diff --git a/include/wlr/backend/session.h b/include/wlr/backend/session.h new file mode 100644 index 0000000..3179d1f --- /dev/null +++ b/include/wlr/backend/session.h @@ -0,0 +1,148 @@ +#ifndef WLR_BACKEND_SESSION_H +#define WLR_BACKEND_SESSION_H + +#include +#include +#include + +struct libseat; + +/** + * An opened physical device. + */ +struct wlr_device { + int fd; + int device_id; + dev_t dev; + struct wl_list link; // wlr_session.devices + + struct { + struct wl_signal change; // struct wlr_device_change_event + struct wl_signal remove; + } events; +}; + +/** + * A session manages access to physical devices (such as GPUs and input + * devices). + * + * A session is only required when running on bare metal (e.g. with the KMS or + * libinput backends). + * + * The session listens for device hotplug events, and relays that information + * via the add_drm_card event and the change/remove events on struct wlr_device. + * The session provides functions to gain access to physical device (which is a + * privileged operation), see wlr_session_open_file(). The session also keeps + * track of the virtual terminal state (allowing users to switch between + * compositors or TTYs), see wlr_session_change_vt() and the active event. + */ +struct wlr_session { + /* + * Signal for when the session becomes active/inactive. + * It's called when we swap virtual terminal. + */ + bool active; + + /* + * 0 if virtual terminals are not supported + * i.e. seat != "seat0" + */ + unsigned vtnr; + char seat[256]; + + struct udev *udev; + struct udev_monitor *mon; + struct wl_event_source *udev_event; + + struct libseat *seat_handle; + struct wl_event_source *libseat_event; + + struct wl_list devices; // wlr_device.link + + struct wl_event_loop *event_loop; + struct wl_listener event_loop_destroy; + + struct { + struct wl_signal active; + struct wl_signal add_drm_card; // struct wlr_session_add_event + struct wl_signal destroy; + } events; +}; + +struct wlr_session_add_event { + const char *path; +}; + +enum wlr_device_change_type { + WLR_DEVICE_HOTPLUG = 1, + WLR_DEVICE_LEASE, +}; + +struct wlr_device_hotplug_event { + uint32_t connector_id; + uint32_t prop_id; +}; + +struct wlr_device_change_event { + enum wlr_device_change_type type; + union { + struct wlr_device_hotplug_event hotplug; + }; +}; + +/* + * Opens a session, taking control of the current virtual terminal. + * This should not be called if another program is already in control + * of the terminal (Xorg, another Wayland compositor, etc.). + * + * Returns NULL on error. + */ +struct wlr_session *wlr_session_create(struct wl_event_loop *loop); + +/* + * Closes a previously opened session and restores the virtual terminal. + * You should call wlr_session_close_file() on each files you opened + * with wlr_session_open_file() before you call this. + */ +void wlr_session_destroy(struct wlr_session *session); + +/* + * Opens the file at path. + * + * This can only be used to open DRM or evdev (input) devices. Files opened via + * this function must be closed by calling wlr_session_close_file(). + * + * When the session becomes inactive: + * + * - DRM files lose their DRM master status + * - evdev files become invalid and should be closed + */ +struct wlr_device *wlr_session_open_file(struct wlr_session *session, + const char *path); + +/* + * Closes a file previously opened with wlr_session_open_file(). + */ +void wlr_session_close_file(struct wlr_session *session, + struct wlr_device *device); + +/* + * Changes the virtual terminal. + */ +bool wlr_session_change_vt(struct wlr_session *session, unsigned vt); + +/** + * Enumerate and open KMS devices. + * + * ret is filled with up to ret_len devices. The number of devices ret has been + * filled with is returned on success. If more devices than ret_len are probed, + * the extraneous ones are ignored. If there is no KMS device, the function + * will block until such device is detected up to a timeout. The first device + * returned is the default device (marked as "boot_vga" by the kernel). + * + * On error, or if no device was found, -1 is returned. + */ +ssize_t wlr_session_find_gpus(struct wlr_session *session, + size_t ret_len, struct wlr_device **ret); + +#endif diff --git a/include/wlr/backend/wayland.h b/include/wlr/backend/wayland.h new file mode 100644 index 0000000..f402693 --- /dev/null +++ b/include/wlr/backend/wayland.h @@ -0,0 +1,70 @@ +#ifndef WLR_BACKEND_WAYLAND_H +#define WLR_BACKEND_WAYLAND_H +#include +#include +#include +#include +#include + +struct wlr_input_device; + +/** + * Creates a new Wayland backend. This backend will be created with no outputs; + * you must use wlr_wl_output_create() to add them. + * + * The remote_display argument is an existing libwayland-client struct wl_display + * to use. Leave it NULL to create a new connection to the compositor. + */ +struct wlr_backend *wlr_wl_backend_create(struct wl_event_loop *loop, + struct wl_display *remote_display); + +/** + * Returns the remote struct wl_display used by the Wayland backend. + */ +struct wl_display *wlr_wl_backend_get_remote_display(struct wlr_backend *backend); + +/** + * Adds a new output to this backend. + * + * This creates a new xdg_toplevel in the parent Wayland compositor. + * + * You may remove outputs by destroying them. + * + * Note that if called before initializing the backend, this will return NULL + * and your outputs will be created during initialization (and given to you via + * the new_output signal). + */ +struct wlr_output *wlr_wl_output_create(struct wlr_backend *backend); + +/** + * Create a new output from an existing struct wl_surface. + */ +struct wlr_output *wlr_wl_output_create_from_surface(struct wlr_backend *backend, + struct wl_surface *surface); + +/** + * Check whether the provided backend is a Wayland backend. + */ +bool wlr_backend_is_wl(struct wlr_backend *backend); + +/** + * Check whether the provided input device is a Wayland input device. + */ +bool wlr_input_device_is_wl(struct wlr_input_device *device); + +/** + * Check whether the provided output device is a Wayland output device. + */ +bool wlr_output_is_wl(struct wlr_output *output); + +/** + * Sets the title of a struct wlr_output which is a Wayland toplevel. + */ +void wlr_wl_output_set_title(struct wlr_output *output, const char *title); + +/** + * Returns the remote struct wl_surface used by the Wayland output. + */ +struct wl_surface *wlr_wl_output_get_surface(struct wlr_output *output); + +#endif diff --git a/include/wlr/backend/x11.h b/include/wlr/backend/x11.h new file mode 100644 index 0000000..1791041 --- /dev/null +++ b/include/wlr/backend/x11.h @@ -0,0 +1,51 @@ +#ifndef WLR_BACKEND_X11_H +#define WLR_BACKEND_X11_H + +#include + +#include + +#include +#include + +struct wlr_input_device; + +/** + * Creates a new X11 backend. This backend will be created with no outputs; + * you must use wlr_x11_output_create() to add them. + * + * The `x11_display` argument is the name of the X Display socket. Set + * to NULL for the default behaviour of XOpenDisplay(). + */ +struct wlr_backend *wlr_x11_backend_create(struct wl_event_loop *loop, + const char *x11_display); + +/** + * Adds a new output to this backend. You may remove outputs by destroying them. + * Note that if called before initializing the backend, this will return NULL + * and your outputs will be created during initialization (and given to you via + * the new_output signal). + */ +struct wlr_output *wlr_x11_output_create(struct wlr_backend *backend); + +/** + * Check whether this backend is an X11 backend. + */ +bool wlr_backend_is_x11(struct wlr_backend *backend); + +/** + * Check whether this input device is an X11 input device. + */ +bool wlr_input_device_is_x11(struct wlr_input_device *device); + +/** + * Check whether this output device is an X11 output device. + */ +bool wlr_output_is_x11(struct wlr_output *output); + +/** + * Sets the title of a struct wlr_output which is an X11 window. + */ +void wlr_x11_output_set_title(struct wlr_output *output, const char *title); + +#endif diff --git a/include/wlr/config.h.in b/include/wlr/config.h.in new file mode 100644 index 0000000..6a53d21 --- /dev/null +++ b/include/wlr/config.h.in @@ -0,0 +1,17 @@ +#ifndef WLR_CONFIG_H +#define WLR_CONFIG_H + +#mesondefine WLR_HAS_DRM_BACKEND +#mesondefine WLR_HAS_LIBINPUT_BACKEND +#mesondefine WLR_HAS_X11_BACKEND + +#mesondefine WLR_HAS_GLES2_RENDERER +#mesondefine WLR_HAS_VULKAN_RENDERER + +#mesondefine WLR_HAS_GBM_ALLOCATOR + +#mesondefine WLR_HAS_XWAYLAND + +#mesondefine WLR_HAS_SESSION + +#endif diff --git a/include/wlr/interfaces/wlr_buffer.h b/include/wlr/interfaces/wlr_buffer.h new file mode 100644 index 0000000..820300f --- /dev/null +++ b/include/wlr/interfaces/wlr_buffer.h @@ -0,0 +1,48 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_BUFFER_H +#define WLR_INTERFACES_WLR_BUFFER_H + +#include + +struct wlr_buffer_impl { + void (*destroy)(struct wlr_buffer *buffer); + bool (*get_dmabuf)(struct wlr_buffer *buffer, + struct wlr_dmabuf_attributes *attribs); + bool (*get_shm)(struct wlr_buffer *buffer, + struct wlr_shm_attributes *attribs); + bool (*begin_data_ptr_access)(struct wlr_buffer *buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride); + void (*end_data_ptr_access)(struct wlr_buffer *buffer); +}; + +struct wlr_buffer_resource_interface { + const char *name; + bool (*is_instance)(struct wl_resource *resource); + struct wlr_buffer *(*from_resource)(struct wl_resource *resource); +}; + +/** + * Initialize a buffer. This function should be called by producers. The + * initialized buffer is referenced: once the producer is done with the buffer + * they should call wlr_buffer_drop(). + */ +void wlr_buffer_init(struct wlr_buffer *buffer, + const struct wlr_buffer_impl *impl, int width, int height); + +/** + * Allows the registration of a struct wl_resource implementation. + * + * The matching function will be called for the struct wl_resource when creating + * a struct wlr_buffer from a struct wl_resource. + */ +void wlr_buffer_register_resource_interface( + const struct wlr_buffer_resource_interface *iface); + +#endif diff --git a/include/wlr/interfaces/wlr_keyboard.h b/include/wlr/interfaces/wlr_keyboard.h new file mode 100644 index 0000000..2ec7c8c --- /dev/null +++ b/include/wlr/interfaces/wlr_keyboard.h @@ -0,0 +1,34 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_KEYBOARD_H +#define WLR_INTERFACES_WLR_KEYBOARD_H + +#include +#include + +struct wlr_keyboard_impl { + const char *name; + void (*led_update)(struct wlr_keyboard *keyboard, uint32_t leds); +}; + +void wlr_keyboard_init(struct wlr_keyboard *keyboard, + const struct wlr_keyboard_impl *impl, const char *name); + +/** + * Cleans up all of the resources owned by the struct wlr_keyboard. + */ +void wlr_keyboard_finish(struct wlr_keyboard *keyboard); + +void wlr_keyboard_notify_key(struct wlr_keyboard *keyboard, + struct wlr_keyboard_key_event *event); +void wlr_keyboard_notify_modifiers(struct wlr_keyboard *keyboard, + uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, + uint32_t group); + +#endif diff --git a/include/wlr/interfaces/wlr_output.h b/include/wlr/interfaces/wlr_output.h new file mode 100644 index 0000000..b512347 --- /dev/null +++ b/include/wlr/interfaces/wlr_output.h @@ -0,0 +1,130 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_OUTPUT_H +#define WLR_INTERFACES_WLR_OUTPUT_H + +#include +#include +#include + +/** + * Output state fields that don't require backend support. Backends can ignore + * them without breaking the API contract. + */ +#define WLR_OUTPUT_STATE_BACKEND_OPTIONAL \ + (WLR_OUTPUT_STATE_DAMAGE | \ + WLR_OUTPUT_STATE_SCALE | \ + WLR_OUTPUT_STATE_TRANSFORM | \ + WLR_OUTPUT_STATE_RENDER_FORMAT | \ + WLR_OUTPUT_STATE_SUBPIXEL | \ + WLR_OUTPUT_STATE_LAYERS) + +/** + * A backend implementation of struct wlr_output. + * + * The commit function is mandatory. Other functions are optional. + */ +struct wlr_output_impl { + /** + * Set the output cursor plane image. + * + * If buffer is NULL, the cursor should be hidden. + * + * The hotspot indicates the offset that needs to be applied to the + * top-left corner of the image to match the cursor position. In other + * words, the image should be displayed at (x - hotspot_x, y - hotspot_y). + * The hotspot is given in the buffer's coordinate space. + */ + bool (*set_cursor)(struct wlr_output *output, struct wlr_buffer *buffer, + int hotspot_x, int hotspot_y); + /** + * Set the output cursor plane position. + * + * The position is relative to the cursor hotspot, see set_cursor. + */ + bool (*move_cursor)(struct wlr_output *output, int x, int y); + /** + * Cleanup backend-specific resources tied to the output. + */ + void (*destroy)(struct wlr_output *output); + /** + * Check that the supplied output state is a valid configuration. + * + * If this function returns true, commit can only fail due to a runtime + * error. + */ + bool (*test)(struct wlr_output *output, const struct wlr_output_state *state); + /** + * Commit the supplied output state. + * + * If a buffer has been attached, a frame event is scheduled. + */ + bool (*commit)(struct wlr_output *output, const struct wlr_output_state *state); + /** + * Get the maximum number of gamma LUT elements for each channel. + * + * Zero can be returned if the output doesn't support gamma LUTs. + */ + size_t (*get_gamma_size)(struct wlr_output *output); + /** + * Get the list of formats suitable for the cursor, assuming a buffer with + * the specified capabilities. + * + * If unimplemented, the cursor buffer has no format constraint. If NULL is + * returned, no format is suitable. + */ + const struct wlr_drm_format_set *(*get_cursor_formats)( + struct wlr_output *output, uint32_t buffer_caps); + /** + * Get the size suitable for the cursor buffer. Attempts to use a different + * size for the cursor may fail. + */ + void (*get_cursor_size)(struct wlr_output *output, int *width, int *height); + /** + * Get the list of DRM formats suitable for the primary buffer, + * assuming a buffer with the specified capabilities. + * + * If unimplemented, the primary buffer has no format constraint. If NULL + * is returned, no format is suitable. + */ + const struct wlr_drm_format_set *(*get_primary_formats)( + struct wlr_output *output, uint32_t buffer_caps); +}; + +/** + * Initialize a new output. + */ +void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, + const struct wlr_output_impl *impl, struct wl_event_loop *event_loop, + const struct wlr_output_state *state); +/** + * Notify compositors that they need to submit a new frame in order to apply + * output changes. + */ +void wlr_output_update_needs_frame(struct wlr_output *output); +/** + * Send a frame event. + * + * See wlr_output.events.frame. + */ +void wlr_output_send_frame(struct wlr_output *output); +/** + * Send a present event. + * + * See wlr_output.events.present. + */ +void wlr_output_send_present(struct wlr_output *output, + struct wlr_output_event_present *event); +/** + * Request the compositor to apply new state. + */ +void wlr_output_send_request_state(struct wlr_output *output, + const struct wlr_output_state *state); + +#endif diff --git a/include/wlr/interfaces/wlr_pointer.h b/include/wlr/interfaces/wlr_pointer.h new file mode 100644 index 0000000..74e0fea --- /dev/null +++ b/include/wlr/interfaces/wlr_pointer.h @@ -0,0 +1,22 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_POINTER_H +#define WLR_INTERFACES_WLR_POINTER_H + +#include + +struct wlr_pointer_impl { + const char *name; +}; + +void wlr_pointer_init(struct wlr_pointer *pointer, + const struct wlr_pointer_impl *impl, const char *name); +void wlr_pointer_finish(struct wlr_pointer *pointer); + +#endif diff --git a/include/wlr/interfaces/wlr_switch.h b/include/wlr/interfaces/wlr_switch.h new file mode 100644 index 0000000..793ed25 --- /dev/null +++ b/include/wlr/interfaces/wlr_switch.h @@ -0,0 +1,22 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_SWITCH_H +#define WLR_INTERFACES_WLR_SWITCH_H + +#include + +struct wlr_switch_impl { + const char *name; +}; + +void wlr_switch_init(struct wlr_switch *switch_device, + const struct wlr_switch_impl *impl, const char *name); +void wlr_switch_finish(struct wlr_switch *switch_device); + +#endif diff --git a/include/wlr/interfaces/wlr_tablet_pad.h b/include/wlr/interfaces/wlr_tablet_pad.h new file mode 100644 index 0000000..f4a422f --- /dev/null +++ b/include/wlr/interfaces/wlr_tablet_pad.h @@ -0,0 +1,30 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_TABLET_PAD_H +#define WLR_INTERFACES_WLR_TABLET_PAD_H + +#include + +struct wlr_tablet_pad_impl { + const char *name; +}; + +void wlr_tablet_pad_init(struct wlr_tablet_pad *pad, + const struct wlr_tablet_pad_impl *impl, const char *name); + +/** + * Cleans up the resources owned by a struct wlr_tablet_pad. + * + * This function will not clean the memory allocated by + * struct wlr_tablet_pad_group, it's the responsibility of the caller to clean + * it. + */ +void wlr_tablet_pad_finish(struct wlr_tablet_pad *pad); + +#endif diff --git a/include/wlr/interfaces/wlr_tablet_tool.h b/include/wlr/interfaces/wlr_tablet_tool.h new file mode 100644 index 0000000..68bf8cf --- /dev/null +++ b/include/wlr/interfaces/wlr_tablet_tool.h @@ -0,0 +1,22 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_TABLET_TOOL_H +#define WLR_INTERFACES_WLR_TABLET_TOOL_H + +#include + +struct wlr_tablet_impl { + const char *name; +}; + +void wlr_tablet_init(struct wlr_tablet *tablet, + const struct wlr_tablet_impl *impl, const char *name); +void wlr_tablet_finish(struct wlr_tablet *tablet); + +#endif diff --git a/include/wlr/interfaces/wlr_touch.h b/include/wlr/interfaces/wlr_touch.h new file mode 100644 index 0000000..e48a92f --- /dev/null +++ b/include/wlr/interfaces/wlr_touch.h @@ -0,0 +1,22 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_TOUCH_H +#define WLR_INTERFACES_WLR_TOUCH_H + +#include + +struct wlr_touch_impl { + const char *name; +}; + +void wlr_touch_init(struct wlr_touch *touch, + const struct wlr_touch_impl *impl, const char *name); +void wlr_touch_finish(struct wlr_touch *touch); + +#endif diff --git a/include/wlr/meson.build b/include/wlr/meson.build new file mode 100644 index 0000000..f7ca413 --- /dev/null +++ b/include/wlr/meson.build @@ -0,0 +1,25 @@ +version_base = meson.project_version().split('-')[0] +version_array = version_base.split('.') +version_data = configuration_data() +version_data.set_quoted('WLR_VERSION_STR', meson.project_version()) +version_data.set('WLR_VERSION_MAJOR', version_array[0]) +version_data.set('WLR_VERSION_MINOR', version_array[1]) +version_data.set('WLR_VERSION_MICRO', version_array[2]) + +conf_data = configuration_data() +foreach name, have : features + conf_data.set10('WLR_HAS_' + name.underscorify().to_upper(), have) +endforeach + +conf_h = configure_file( + input: 'config.h.in', + output: 'config.h', + configuration: conf_data, +) +ver_h = configure_file( + input: 'version.h.in', + output: 'version.h', + configuration: version_data, +) + +install_headers(conf_h, ver_h, subdir: 'wlr') diff --git a/include/wlr/render/allocator.h b/include/wlr/render/allocator.h new file mode 100644 index 0000000..f5bb752 --- /dev/null +++ b/include/wlr/render/allocator.h @@ -0,0 +1,70 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_ALLOCATOR_H +#define WLR_ALLOCATOR_H + +#include + +struct wlr_allocator; +struct wlr_backend; +struct wlr_drm_format; +struct wlr_renderer; + +struct wlr_allocator_interface { + struct wlr_buffer *(*create_buffer)(struct wlr_allocator *alloc, + int width, int height, const struct wlr_drm_format *format); + void (*destroy)(struct wlr_allocator *alloc); +}; + +void wlr_allocator_init(struct wlr_allocator *alloc, + const struct wlr_allocator_interface *impl, uint32_t buffer_caps); + +struct wlr_allocator { + const struct wlr_allocator_interface *impl; + + // Capabilities of the buffers created with this allocator + uint32_t buffer_caps; + + struct { + struct wl_signal destroy; + } events; +}; + +/** + * Creates the adequate struct wlr_allocator given a backend and a renderer. + */ +struct wlr_allocator *wlr_allocator_autocreate(struct wlr_backend *backend, + struct wlr_renderer *renderer); +/** + * Destroy the allocator. + */ +void wlr_allocator_destroy(struct wlr_allocator *alloc); + +/** + * Allocate a new buffer. + * + * When the caller is done with it, they must unreference it by calling + * wlr_buffer_drop(). + * + * The `format` passed in indicates the format to use and the list of + * acceptable modifiers. The order in which modifiers are listed is not + * significant. + * + * When running with legacy drivers which don't support explicit modifiers, the + * allocator must recognize two modifiers: INVALID (for implicit tiling and/or + * compression) and LINEAR. + * + * The allocator must return a buffer using one of the modifiers listed. In + * particular, allocators must not return a buffer with an implicit modifier + * unless the user has allowed it by passing INVALID in the modifier list. + */ +struct wlr_buffer *wlr_allocator_create_buffer(struct wlr_allocator *alloc, + int width, int height, const struct wlr_drm_format *format); + +#endif diff --git a/include/wlr/render/dmabuf.h b/include/wlr/render/dmabuf.h new file mode 100644 index 0000000..62f8e02 --- /dev/null +++ b/include/wlr/render/dmabuf.h @@ -0,0 +1,55 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_DMABUF_H +#define WLR_RENDER_DMABUF_H + +#include +#include + +#define WLR_DMABUF_MAX_PLANES 4 + +/** + * A Linux DMA-BUF pixel buffer. + * + * If the buffer was allocated with explicit modifiers enabled, the `modifier` + * field must not be INVALID. + * + * If the buffer was allocated with explicit modifiers disabled (either because + * the driver doesn't support it, or because the user didn't specify a valid + * modifier list), the `modifier` field can have two values: INVALID means that + * an implicit vendor-defined modifier is in use, LINEAR means that the buffer + * is linear. The `modifier` field must not have any other value. + * + * When importing a DMA-BUF, users must not ignore the modifier unless it's + * INVALID or LINEAR. In particular, users must not import a DMA-BUF to a + * legacy API which doesn't support specifying an explicit modifier unless the + * modifier is set to INVALID or LINEAR. + */ +struct wlr_dmabuf_attributes { + int32_t width, height; + uint32_t format; // FourCC code, see DRM_FORMAT_* in + uint64_t modifier; // see DRM_FORMAT_MOD_* in + + int n_planes; + uint32_t offset[WLR_DMABUF_MAX_PLANES]; + uint32_t stride[WLR_DMABUF_MAX_PLANES]; + int fd[WLR_DMABUF_MAX_PLANES]; +}; + +/** + * Closes all file descriptors in the DMA-BUF attributes. + */ +void wlr_dmabuf_attributes_finish(struct wlr_dmabuf_attributes *attribs); +/** + * Clones the DMA-BUF attributes. + */ +bool wlr_dmabuf_attributes_copy(struct wlr_dmabuf_attributes *dst, + const struct wlr_dmabuf_attributes *src); + +#endif diff --git a/include/wlr/render/drm_format_set.h b/include/wlr/render/drm_format_set.h new file mode 100644 index 0000000..cc38bbd --- /dev/null +++ b/include/wlr/render/drm_format_set.h @@ -0,0 +1,97 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_DRM_FORMAT_SET_H +#define WLR_RENDER_DRM_FORMAT_SET_H + +#include +#include +#include + +/** A single DRM format, with a set of modifiers attached. */ +struct wlr_drm_format { + // The actual DRM format, from `drm_fourcc.h` + uint32_t format; + // The number of modifiers + size_t len; + // The capacity of the array; do not use. + size_t capacity; + // The actual modifiers + uint64_t *modifiers; +}; + +/** + * Free all resources allocated to this DRM format. + */ +void wlr_drm_format_finish(struct wlr_drm_format *format); + +/** + * A set of DRM formats and modifiers. + * + * This is used to describe the supported format + modifier combinations. For + * instance, backends will report the set they can display, and renderers will + * report the set they can render to. For a more general overview of formats + * and modifiers, see: + * https://www.kernel.org/doc/html/next/userspace-api/dma-buf-alloc-exchange.html#formats-and-modifiers + * + * For compatibility with legacy drivers which don't support explicit + * modifiers, the special modifier DRM_FORMAT_MOD_INVALID is used to indicate + * that implicit modifiers are supported. Legacy drivers can also support the + * DRM_FORMAT_MOD_LINEAR modifier, which forces the buffer to have a linear + * layout. + * + * Users must not assume that implicit modifiers are supported unless INVALID + * is listed in the modifier list. + */ +struct wlr_drm_format_set { + // The number of formats + size_t len; + // The capacity of the array; private to wlroots + size_t capacity; + // A pointer to an array of `struct wlr_drm_format *` of length `len`. + struct wlr_drm_format *formats; +}; + +/** + * Free all of the DRM formats in the set, making the set empty. + */ +void wlr_drm_format_set_finish(struct wlr_drm_format_set *set); + +/** + * Return a pointer to a member of this struct wlr_drm_format_set of format + * `format`, or NULL if none exists. + */ +const struct wlr_drm_format *wlr_drm_format_set_get( + const struct wlr_drm_format_set *set, uint32_t format); + +bool wlr_drm_format_set_has(const struct wlr_drm_format_set *set, + uint32_t format, uint64_t modifier); + +bool wlr_drm_format_set_add(struct wlr_drm_format_set *set, uint32_t format, + uint64_t modifier); + +/** + * Intersect two DRM format sets `a` and `b`, storing in the destination set + * `dst` the format + modifier pairs which are in both source sets. The `dst` + * must either be zeroed or initialized with other state to be replaced. + * + * Returns false on failure or when the intersection is empty. + */ +bool wlr_drm_format_set_intersect(struct wlr_drm_format_set *dst, + const struct wlr_drm_format_set *a, const struct wlr_drm_format_set *b); + +/** + * Unions DRM format set `a` and `b`, storing in the destination set + * `dst`. The `dst` must either be zeroed or initialized with other state + * to be replaced. + * + * Returns false on failure. + */ +bool wlr_drm_format_set_union(struct wlr_drm_format_set *dst, + const struct wlr_drm_format_set *a, const struct wlr_drm_format_set *b); +#endif diff --git a/include/wlr/render/egl.h b/include/wlr/render/egl.h new file mode 100644 index 0000000..9bf1b80 --- /dev/null +++ b/include/wlr/render/egl.h @@ -0,0 +1,56 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_EGL_H +#define WLR_RENDER_EGL_H + +#ifndef EGL_NO_X11 +#define EGL_NO_X11 +#endif +#ifndef EGL_NO_PLATFORM_SPECIFIC_TYPES +#define EGL_NO_PLATFORM_SPECIFIC_TYPES +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +struct wlr_egl; + +/** + * Create a struct wlr_egl with an existing EGL display and context. + * + * This is typically used by compositors which want to customize EGL + * initialization. + */ +struct wlr_egl *wlr_egl_create_with_context(EGLDisplay display, + EGLContext context); + +/** + * Get the EGL display used by the struct wlr_egl. + * + * This is typically used by compositors which need to perform custom OpenGL + * operations. + */ +EGLDisplay wlr_egl_get_display(struct wlr_egl *egl); + +/** + * Get the EGL context used by the struct wlr_egl. + * + * This is typically used by compositors which need to perform custom OpenGL + * operations. + */ +EGLContext wlr_egl_get_context(struct wlr_egl *egl); + +#endif diff --git a/include/wlr/render/gles2.h b/include/wlr/render/gles2.h new file mode 100644 index 0000000..454e7eb --- /dev/null +++ b/include/wlr/render/gles2.h @@ -0,0 +1,51 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_GLES2_H +#define WLR_RENDER_GLES2_H + +#include + +#include + +struct wlr_egl; + +/** + * OpenGL ES 2 renderer. + * + * Care must be taken to avoid stepping each other's toes with EGL contexts: + * the current EGL is global state. The GLES2 renderer operations will save + * and restore any previous EGL context when called. A render pass is seen as + * a single operation. + * + * The GLES2 renderer doesn't support arbitrarily nested render passes. It + * supports a subset only: after a nested render pass is created, any parent + * render pass can't be used before the nested render pass is submitted. + */ + +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); +GLuint wlr_gles2_renderer_get_buffer_fbo(struct wlr_renderer *renderer, struct wlr_buffer *buffer); + +struct wlr_gles2_texture_attribs { + GLenum target; /* either GL_TEXTURE_2D or GL_TEXTURE_EXTERNAL_OES */ + GLuint tex; + + bool has_alpha; +}; + +bool wlr_renderer_is_gles2(struct wlr_renderer *wlr_renderer); +bool wlr_render_timer_is_gles2(struct wlr_render_timer *timer); +bool wlr_texture_is_gles2(struct wlr_texture *texture); +void wlr_gles2_texture_get_attribs(struct wlr_texture *texture, + struct wlr_gles2_texture_attribs *attribs); + +#endif diff --git a/include/wlr/render/interface.h b/include/wlr/render/interface.h new file mode 100644 index 0000000..9cbf814 --- /dev/null +++ b/include/wlr/render/interface.h @@ -0,0 +1,93 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_INTERFACE_H +#define WLR_RENDER_INTERFACE_H + +#include +#include +#include +#include +#include +#include + +struct wlr_box; +struct wlr_fbox; + +struct wlr_renderer_impl { + const uint32_t *(*get_shm_texture_formats)( + struct wlr_renderer *renderer, size_t *len); + const struct wlr_drm_format_set *(*get_dmabuf_texture_formats)( + struct wlr_renderer *renderer); + const struct wlr_drm_format_set *(*get_render_formats)( + struct wlr_renderer *renderer); + void (*destroy)(struct wlr_renderer *renderer); + int (*get_drm_fd)(struct wlr_renderer *renderer); + uint32_t (*get_render_buffer_caps)(struct wlr_renderer *renderer); + struct wlr_texture *(*texture_from_buffer)(struct wlr_renderer *renderer, + struct wlr_buffer *buffer); + struct wlr_render_pass *(*begin_buffer_pass)(struct wlr_renderer *renderer, + struct wlr_buffer *buffer, const struct wlr_buffer_pass_options *options); + struct wlr_render_timer *(*render_timer_create)(struct wlr_renderer *renderer); +}; + +void wlr_renderer_init(struct wlr_renderer *renderer, + const struct wlr_renderer_impl *impl); + +struct wlr_texture_impl { + bool (*update_from_buffer)(struct wlr_texture *texture, + struct wlr_buffer *buffer, const pixman_region32_t *damage); + bool (*read_pixels)(struct wlr_texture *texture, + const struct wlr_texture_read_pixels_options *options); + uint32_t (*preferred_read_format)(struct wlr_texture *texture); + void (*destroy)(struct wlr_texture *texture); +}; + +void wlr_texture_init(struct wlr_texture *texture, struct wlr_renderer *rendener, + const struct wlr_texture_impl *impl, uint32_t width, uint32_t height); + +struct wlr_render_pass { + const struct wlr_render_pass_impl *impl; +}; + +void wlr_render_pass_init(struct wlr_render_pass *pass, + const struct wlr_render_pass_impl *impl); + +struct wlr_render_pass_impl { + bool (*submit)(struct wlr_render_pass *pass); + void (*add_texture)(struct wlr_render_pass *pass, + const struct wlr_render_texture_options *options); + /* Implementers are also guaranteed that options->box is nonempty */ + void (*add_rect)(struct wlr_render_pass *pass, + const struct wlr_render_rect_options *options); +}; + +struct wlr_render_timer { + const struct wlr_render_timer_impl *impl; +}; + +struct wlr_render_timer_impl { + int (*get_duration_ns)(struct wlr_render_timer *timer); + void (*destroy)(struct wlr_render_timer *timer); +}; + +void wlr_render_texture_options_get_src_box(const struct wlr_render_texture_options *options, + struct wlr_fbox *box); +void wlr_render_texture_options_get_dst_box(const struct wlr_render_texture_options *options, + struct wlr_box *box); +float wlr_render_texture_options_get_alpha(const struct wlr_render_texture_options *options); +void wlr_render_rect_options_get_box(const struct wlr_render_rect_options *options, + const struct wlr_buffer *buffer, struct wlr_box *box); + +void wlr_texture_read_pixels_options_get_src_box( + const struct wlr_texture_read_pixels_options *options, + const struct wlr_texture *texture, struct wlr_box *box); +void *wlr_texture_read_pixel_options_get_data( + const struct wlr_texture_read_pixels_options *options); + +#endif diff --git a/include/wlr/render/pass.h b/include/wlr/render/pass.h new file mode 100644 index 0000000..d1b3eb1 --- /dev/null +++ b/include/wlr/render/pass.h @@ -0,0 +1,123 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_PASS_H +#define WLR_RENDER_PASS_H + +#include +#include +#include +#include + +struct wlr_renderer; +struct wlr_buffer; + +/** + * A render pass accumulates drawing operations until submitted to the GPU. + */ +struct wlr_render_pass; + +/** + * An object that can be queried after a render to get the duration of the render. + */ +struct wlr_render_timer; + +struct wlr_buffer_pass_options { + /* Timer to measure the duration of the render pass */ + struct wlr_render_timer *timer; +}; + +/** + * Begin a new render pass with the supplied destination buffer. + * + * Callers must call wlr_render_pass_submit() once they are done with the + * render pass. + */ +struct wlr_render_pass *wlr_renderer_begin_buffer_pass(struct wlr_renderer *renderer, + struct wlr_buffer *buffer, const struct wlr_buffer_pass_options *options); + +/** + * Submit the render pass. + * + * The render pass cannot be used after this function is called. + */ +bool wlr_render_pass_submit(struct wlr_render_pass *render_pass); + +/** + * Blend modes. + */ +enum wlr_render_blend_mode { + /* Pre-multiplied alpha (default) */ + WLR_RENDER_BLEND_MODE_PREMULTIPLIED, + /* Blending is disabled */ + WLR_RENDER_BLEND_MODE_NONE, +}; + +/** + * Filter modes. + */ +enum wlr_scale_filter_mode { + /* bilinear texture filtering (default) */ + WLR_SCALE_FILTER_BILINEAR, + /* nearest texture filtering */ + WLR_SCALE_FILTER_NEAREST, +}; + +struct wlr_render_texture_options { + /* Source texture */ + struct wlr_texture *texture; + /* Source coordinates, leave empty to render the whole texture */ + struct wlr_fbox src_box; + /* Destination coordinates, width/height default to the texture size */ + struct wlr_box dst_box; + /* Opacity between 0 (transparent) and 1 (opaque), leave NULL for opaque */ + const float *alpha; + /* Clip region, leave NULL to disable clipping */ + const pixman_region32_t *clip; + /* Transform applied to the source texture */ + enum wl_output_transform transform; + /* Filtering */ + enum wlr_scale_filter_mode filter_mode; + /* Blend mode */ + enum wlr_render_blend_mode blend_mode; +}; + +/** + * Render a texture. + */ +void wlr_render_pass_add_texture(struct wlr_render_pass *render_pass, + const struct wlr_render_texture_options *options); + +/** + * A color value. + * + * Each channel has values between 0 and 1 inclusive. The R, G, B + * channels need to be pre-multiplied by A. + */ +struct wlr_render_color { + float r, g, b, a; +}; + +struct wlr_render_rect_options { + /* Rectangle coordinates */ + struct wlr_box box; + /* Source color */ + struct wlr_render_color color; + /* Clip region, leave NULL to disable clipping */ + const pixman_region32_t *clip; + /* Blend mode */ + enum wlr_render_blend_mode blend_mode; +}; + +/** + * Render a rectangle. + */ +void wlr_render_pass_add_rect(struct wlr_render_pass *render_pass, + const struct wlr_render_rect_options *options); + +#endif diff --git a/include/wlr/render/pixman.h b/include/wlr/render/pixman.h new file mode 100644 index 0000000..206dd78 --- /dev/null +++ b/include/wlr/render/pixman.h @@ -0,0 +1,24 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_PIXMAN_H +#define WLR_RENDER_PIXMAN_H + +#include +#include + +struct wlr_renderer *wlr_pixman_renderer_create(void); + +bool wlr_renderer_is_pixman(struct wlr_renderer *wlr_renderer); +bool wlr_texture_is_pixman(struct wlr_texture *texture); + +pixman_image_t *wlr_pixman_renderer_get_buffer_image( + struct wlr_renderer *wlr_renderer, struct wlr_buffer *wlr_buffer); +pixman_image_t *wlr_pixman_texture_get_image(struct wlr_texture *wlr_texture); + +#endif diff --git a/include/wlr/render/swapchain.h b/include/wlr/render/swapchain.h new file mode 100644 index 0000000..cb3aee9 --- /dev/null +++ b/include/wlr/render/swapchain.h @@ -0,0 +1,56 @@ +#ifndef WLR_RENDER_SWAPCHAIN_H +#define WLR_RENDER_SWAPCHAIN_H + +#include +#include +#include + +#define WLR_SWAPCHAIN_CAP 4 + +struct wlr_swapchain_slot { + struct wlr_buffer *buffer; + bool acquired; // waiting for release + int age; + + struct wl_listener release; +}; + +struct wlr_swapchain { + struct wlr_allocator *allocator; // NULL if destroyed + + int width, height; + struct wlr_drm_format format; + + struct wlr_swapchain_slot slots[WLR_SWAPCHAIN_CAP]; + + struct wl_listener allocator_destroy; +}; + +struct wlr_swapchain *wlr_swapchain_create( + struct wlr_allocator *alloc, int width, int height, + const struct wlr_drm_format *format); +void wlr_swapchain_destroy(struct wlr_swapchain *swapchain); +/** + * Acquire a buffer from the swap chain. + * + * The returned buffer is locked. When the caller is done with it, they must + * unlock it by calling wlr_buffer_unlock. + */ +struct wlr_buffer *wlr_swapchain_acquire(struct wlr_swapchain *swapchain, + int *age); +/** + * Returns true if this buffer has been created by this swapchain, and false + * otherwise. + */ +bool wlr_swapchain_has_buffer(struct wlr_swapchain *swapchain, + struct wlr_buffer *buffer); +/** + * Mark the buffer as submitted for presentation. This needs to be called by + * swap chain users on frame boundaries. + * + * If the buffer hasn't been created via the swap chain, the call is ignored. + */ +void wlr_swapchain_set_buffer_submitted(struct wlr_swapchain *swapchain, + struct wlr_buffer *buffer); + +#endif diff --git a/include/wlr/render/vulkan.h b/include/wlr/render/vulkan.h new file mode 100644 index 0000000..50f8c55 --- /dev/null +++ b/include/wlr/render/vulkan.h @@ -0,0 +1,36 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_VULKAN_H +#define WLR_RENDER_VULKAN_H + +#include +#include + +struct wlr_vk_image_attribs { + VkImage image; + VkImageLayout layout; + VkFormat format; +}; + +struct wlr_renderer *wlr_vk_renderer_create_with_drm_fd(int drm_fd); + +VkInstance wlr_vk_renderer_get_instance(struct wlr_renderer *renderer); +VkPhysicalDevice wlr_vk_renderer_get_physical_device(struct wlr_renderer *renderer); +VkDevice wlr_vk_renderer_get_device(struct wlr_renderer *renderer); +uint32_t wlr_vk_renderer_get_queue_family(struct wlr_renderer *renderer); + +bool wlr_renderer_is_vk(struct wlr_renderer *wlr_renderer); +bool wlr_texture_is_vk(struct wlr_texture *texture); + +void wlr_vk_texture_get_image_attribs(struct wlr_texture *texture, + struct wlr_vk_image_attribs *attribs); +bool wlr_vk_texture_has_alpha(struct wlr_texture *texture); + +#endif + diff --git a/include/wlr/render/wlr_renderer.h b/include/wlr/render/wlr_renderer.h new file mode 100644 index 0000000..08333a5 --- /dev/null +++ b/include/wlr/render/wlr_renderer.h @@ -0,0 +1,110 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_WLR_RENDERER_H +#define WLR_RENDER_WLR_RENDERER_H + +#include +#include +#include +#include +#include + +struct wlr_backend; +struct wlr_renderer_impl; +struct wlr_drm_format_set; +struct wlr_buffer; +struct wlr_box; +struct wlr_fbox; + +/** + * A renderer for basic 2D operations. + */ +struct wlr_renderer { + struct { + struct wl_signal destroy; + /** + * Emitted when the GPU is lost, e.g. on GPU reset. + * + * Compositors should destroy the renderer and re-create it. + */ + struct wl_signal lost; + } events; + + // private state + + const struct wlr_renderer_impl *impl; +}; + +/** + * Automatically create a new renderer. + * + * Selects an appropriate renderer type to use depending on the backend, + * platform, environment, etc. + */ +struct wlr_renderer *wlr_renderer_autocreate(struct wlr_backend *backend); + +/** + * Get the shared-memory formats supporting import usage. Buffers allocated + * with a format from this list may be imported via wlr_texture_from_pixels(). + */ +const uint32_t *wlr_renderer_get_shm_texture_formats( + struct wlr_renderer *r, size_t *len); +/** + * Get the DMA-BUF formats supporting sampling usage. Buffers allocated with + * a format from this list may be imported via wlr_texture_from_dmabuf(). + */ +const struct wlr_drm_format_set *wlr_renderer_get_dmabuf_texture_formats( + struct wlr_renderer *renderer); + +/** + * Initializes wl_shm, linux-dmabuf and other buffer factory protocols. + * + * Returns false on failure. + */ +bool wlr_renderer_init_wl_display(struct wlr_renderer *r, + struct wl_display *wl_display); + +/** + * Initializes wl_shm on the provided struct wl_display. + */ +bool wlr_renderer_init_wl_shm(struct wlr_renderer *r, + struct wl_display *wl_display); + +/** + * Obtains the FD of the DRM device used for rendering, or -1 if unavailable. + * + * The caller doesn't have ownership of the FD, it must not close it. + */ +int wlr_renderer_get_drm_fd(struct wlr_renderer *r); + +/** + * Destroys the renderer. + * + * Textures must be destroyed separately. + */ +void wlr_renderer_destroy(struct wlr_renderer *renderer); + +/** + * Allocate and initialise a new render timer. + */ +struct wlr_render_timer *wlr_render_timer_create(struct wlr_renderer *renderer); + +/** + * Get the render duration in nanoseconds from the timer. + * + * Returns -1 if the duration is unavailable. + */ +int wlr_render_timer_get_duration_ns(struct wlr_render_timer *timer); + +/** + * Destroy the render timer. + */ +void wlr_render_timer_destroy(struct wlr_render_timer *timer); + +#endif diff --git a/include/wlr/render/wlr_texture.h b/include/wlr/render/wlr_texture.h new file mode 100644 index 0000000..1e352c6 --- /dev/null +++ b/include/wlr/render/wlr_texture.h @@ -0,0 +1,85 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_WLR_TEXTURE_H +#define WLR_RENDER_WLR_TEXTURE_H + +#include +#include +#include +#include +#include + +struct wlr_buffer; +struct wlr_renderer; +struct wlr_texture_impl; + +struct wlr_texture { + const struct wlr_texture_impl *impl; + uint32_t width, height; + + struct wlr_renderer *renderer; +}; + +struct wlr_texture_read_pixels_options { + /** Memory location to read pixels into */ + void *data; + /** Format used for writing the pixel data */ + uint32_t format; + /** Stride in bytes for the data */ + uint32_t stride; + /** Destination offsets */ + uint32_t dst_x, dst_y; + /** Source box of the texture to read from. If empty, the full texture is assumed. */ + const struct wlr_box src_box; +}; + +bool wlr_texture_read_pixels(struct wlr_texture *texture, + const struct wlr_texture_read_pixels_options *options); + +uint32_t wlr_texture_preferred_read_format(struct wlr_texture *texture); + +/** + * Create a new texture from raw pixel data. `stride` is in bytes. The returned + * texture is mutable. + */ +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); + +/** + * Create a new texture from a DMA-BUF. The returned texture is immutable. + */ +struct wlr_texture *wlr_texture_from_dmabuf(struct wlr_renderer *renderer, + struct wlr_dmabuf_attributes *attribs); + +/** + * Update a texture with a struct wlr_buffer's contents. + * + * The update might be rejected (in case the texture is immutable, the buffer + * has an unsupported type/format, etc), so callers must be prepared to fall + * back to re-creating the texture from scratch via wlr_texture_from_buffer(). + * + * The damage can be used by the renderer as an optimization: only the supplied + * region needs to be updated. + */ +bool wlr_texture_update_from_buffer(struct wlr_texture *texture, + struct wlr_buffer *buffer, const pixman_region32_t *damage); + +/** + * Destroys the texture. + */ +void wlr_texture_destroy(struct wlr_texture *texture); + +/** + * Create a new texture from a buffer. + */ +struct wlr_texture *wlr_texture_from_buffer(struct wlr_renderer *renderer, + struct wlr_buffer *buffer); + +#endif diff --git a/include/wlr/types/wlr_buffer.h b/include/wlr/types/wlr_buffer.h new file mode 100644 index 0000000..de3aeec --- /dev/null +++ b/include/wlr/types/wlr_buffer.h @@ -0,0 +1,164 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_BUFFER_H +#define WLR_TYPES_WLR_BUFFER_H + +#include +#include +#include +#include + +struct wlr_buffer; +struct wlr_renderer; + +struct wlr_shm_attributes { + int fd; + uint32_t format; + int width, height, stride; + off_t offset; +}; + +/** + * Buffer capabilities. + * + * These bits indicate the features supported by a struct wlr_buffer. There is + * one bit per function in struct wlr_buffer_impl. + */ +enum wlr_buffer_cap { + WLR_BUFFER_CAP_DATA_PTR = 1 << 0, + WLR_BUFFER_CAP_DMABUF = 1 << 1, + WLR_BUFFER_CAP_SHM = 1 << 2, +}; + +/** + * A buffer containing pixel data. + * + * A buffer has a single producer (the party who created the buffer) and + * multiple consumers (parties reading the buffer). When all consumers are done + * with the buffer, it gets released and can be re-used by the producer. When + * the producer and all consumers are done with the buffer, it gets destroyed. + */ +struct wlr_buffer { + const struct wlr_buffer_impl *impl; + + int width, height; + + bool dropped; + size_t n_locks; + bool accessing_data_ptr; + + struct { + struct wl_signal destroy; + struct wl_signal release; + } events; + + struct wlr_addon_set addons; +}; + +/** + * Unreference the buffer. This function should be called by producers when + * they are done with the buffer. + */ +void wlr_buffer_drop(struct wlr_buffer *buffer); +/** + * Lock the buffer. This function should be called by consumers to make + * sure the buffer can be safely read from. Once the consumer is done with the + * buffer, they should call wlr_buffer_unlock(). + */ +struct wlr_buffer *wlr_buffer_lock(struct wlr_buffer *buffer); +/** + * Unlock the buffer. This function should be called by consumers once they are + * done with the buffer. + */ +void wlr_buffer_unlock(struct wlr_buffer *buffer); +/** + * Reads the DMA-BUF attributes of the buffer. If this buffer isn't a DMA-BUF, + * returns false. + * + * The returned DMA-BUF attributes are valid for the lifetime of the + * struct wlr_buffer. The caller isn't responsible for cleaning up the DMA-BUF + * attributes. + */ +bool wlr_buffer_get_dmabuf(struct wlr_buffer *buffer, + struct wlr_dmabuf_attributes *attribs); +/** + * Read shared memory attributes of the buffer. If this buffer isn't shared + * memory, returns false. + * + * The returned shared memory attributes are valid for the lifetime of the + * struct wlr_buffer. The caller isn't responsible for cleaning up the shared + * memory attributes. + */ +bool wlr_buffer_get_shm(struct wlr_buffer *buffer, + struct wlr_shm_attributes *attribs); +/** + * Transforms a struct wl_resource into a struct wlr_buffer and locks it. Once + * the caller is done with the buffer, they must call wlr_buffer_unlock(). + * + * The provided struct wl_resource must be a wl_buffer. + */ +struct wlr_buffer *wlr_buffer_try_from_resource(struct wl_resource *resource); + +/** + * Buffer data pointer access flags. + */ +enum wlr_buffer_data_ptr_access_flag { + /** + * The buffer contents can be read back. + */ + WLR_BUFFER_DATA_PTR_ACCESS_READ = 1 << 0, + /** + * The buffer contents can be written to. + */ + WLR_BUFFER_DATA_PTR_ACCESS_WRITE = 1 << 1, +}; + +/** + * Get a pointer to a region of memory referring to the buffer's underlying + * storage. The format and stride can be used to interpret the memory region + * contents. + * + * The returned pointer should be pointing to a valid memory region for the + * operations specified in the flags. The returned pointer is only valid up to + * the next wlr_buffer_end_data_ptr_access() call. + */ +bool wlr_buffer_begin_data_ptr_access(struct wlr_buffer *buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride); +void wlr_buffer_end_data_ptr_access(struct wlr_buffer *buffer); + +/** + * A client buffer. + */ +struct wlr_client_buffer { + struct wlr_buffer base; + + /** + * The buffer's texture, if any. A buffer will not have a texture if the + * client destroys the buffer before it has been released. + */ + struct wlr_texture *texture; + /** + * The buffer this client buffer was created from. NULL if destroyed. + */ + struct wlr_buffer *source; + + // private state + + struct wl_listener source_destroy; + + size_t n_ignore_locks; +}; + +/** + * Get a client buffer from a generic buffer. If the buffer isn't a client + * buffer, returns NULL. + */ +struct wlr_client_buffer *wlr_client_buffer_get(struct wlr_buffer *buffer); + +#endif diff --git a/include/wlr/types/wlr_compositor.h b/include/wlr/types/wlr_compositor.h new file mode 100644 index 0000000..dc95b30 --- /dev/null +++ b/include/wlr/types/wlr_compositor.h @@ -0,0 +1,527 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_COMPOSITOR_H +#define WLR_TYPES_WLR_COMPOSITOR_H + +#include +#include +#include +#include +#include +#include +#include +#include + +enum wlr_surface_state_field { + WLR_SURFACE_STATE_BUFFER = 1 << 0, + WLR_SURFACE_STATE_SURFACE_DAMAGE = 1 << 1, + WLR_SURFACE_STATE_BUFFER_DAMAGE = 1 << 2, + WLR_SURFACE_STATE_OPAQUE_REGION = 1 << 3, + WLR_SURFACE_STATE_INPUT_REGION = 1 << 4, + WLR_SURFACE_STATE_TRANSFORM = 1 << 5, + WLR_SURFACE_STATE_SCALE = 1 << 6, + WLR_SURFACE_STATE_FRAME_CALLBACK_LIST = 1 << 7, + WLR_SURFACE_STATE_VIEWPORT = 1 << 8, + WLR_SURFACE_STATE_OFFSET = 1 << 9, +}; + +struct wlr_surface_state { + uint32_t committed; // enum wlr_surface_state_field + // Sequence number of the surface state. Incremented on each commit, may + // overflow. + uint32_t seq; + + struct wlr_buffer *buffer; + int32_t dx, dy; // relative to previous position + pixman_region32_t surface_damage, buffer_damage; // clipped to bounds + pixman_region32_t opaque, input; + enum wl_output_transform transform; + int32_t scale; + struct wl_list frame_callback_list; // wl_resource + + int width, height; // in surface-local coordinates + int buffer_width, buffer_height; + + struct wl_list subsurfaces_below; + struct wl_list subsurfaces_above; + + /** + * The viewport is applied after the surface transform and scale. + * + * If has_src is true, the surface content is cropped to the provided + * rectangle. If has_dst is true, the surface content is scaled to the + * provided rectangle. + */ + struct { + bool has_src, has_dst; + // In coordinates after scale/transform are applied, but before the + // destination rectangle is applied + struct wlr_fbox src; + int dst_width, dst_height; // in surface-local coordinates + } viewport; + + // Number of locks that prevent this surface state from being committed. + size_t cached_state_locks; + struct wl_list cached_state_link; // wlr_surface.cached + + // Sync'ed object states, one per struct wlr_surface_synced + struct wl_array synced; // void * +}; + +struct wlr_surface_role { + const char *name; + /** + * If true, the role isn't represented by any object. + * For example, this applies to cursor surfaces. + */ + bool no_object; + /** + * Called when the client sends the wl_surface.commit request. May be NULL. + * Typically used to check that the pending state is valid, and send + * protocol errors if not. + * + * If the role is represented by an object, this is only called if + * such object exists. + */ + void (*client_commit)(struct wlr_surface *surface); + /** + * Called when a new surface state is committed. May be NULL. + * + * If the role is represented by an object, this is only called if + * such object exists. + */ + void (*commit)(struct wlr_surface *surface); + /** + * Called when the surface is unmapped. May be NULL. + * + * If the role is represented by an object, this is only called if + * such object exists. + */ + void (*unmap)(struct wlr_surface *surface); + /** + * Called when the object representing the role is destroyed. May be NULL. + */ + void (*destroy)(struct wlr_surface *surface); +}; + +struct wlr_surface_output { + struct wlr_surface *surface; + struct wlr_output *output; + + struct wl_list link; // wlr_surface.current_outputs + struct wl_listener bind; + struct wl_listener destroy; +}; + +struct wlr_surface { + struct wl_resource *resource; + struct wlr_renderer *renderer; // may be NULL + /** + * The surface's buffer, if any. A surface has an attached buffer when it + * commits with a non-null buffer in its pending state. A surface will not + * have a buffer if it has never committed one, has committed a null buffer, + * or something went wrong with uploading the buffer. + */ + struct wlr_client_buffer *buffer; + /** + * The last commit's buffer damage, in buffer-local coordinates. This + * contains both the damage accumulated by the client via + * `wlr_surface_state.surface_damage` and `wlr_surface_state.buffer_damage`. + * If the buffer has been resized, the whole buffer is damaged. + * + * This region needs to be scaled and transformed into output coordinates, + * just like the buffer's texture. In addition, if the buffer has shrunk the + * old size needs to be damaged and if the buffer has moved the old and new + * positions need to be damaged. + */ + pixman_region32_t buffer_damage; + /** + * The current opaque region, in surface-local coordinates. It is clipped to + * the surface bounds. If the surface's buffer is using a fully opaque + * format, this is set to the whole surface. + */ + pixman_region32_t opaque_region; + /** + * The current input region, in surface-local coordinates. It is clipped to + * the surface bounds. + * + * If the protocol states that the input region is ignored, this is empty. + */ + pixman_region32_t input_region; + /** + * `current` contains the current, committed surface state. `pending` + * accumulates state changes from the client between commits and shouldn't + * be accessed by the compositor directly. + */ + struct wlr_surface_state current, pending; + + struct wl_list cached; // wlr_surface_state.cached_link + + /** + * Whether the surface is ready to be displayed. + */ + bool mapped; + + /** + * The lifetime-bound role of the surface. NULL if the role was never set. + */ + const struct wlr_surface_role *role; + + /** + * The role object representing the role. NULL if the role isn't + * represented by any object or the object was destroyed. + */ + struct wl_resource *role_resource; + + struct { + struct wl_signal client_commit; + struct wl_signal commit; + + /** + * The `map` event signals that the surface has a non-null buffer + * committed and is ready to be displayed. + */ + struct wl_signal map; + /** + * The `unmap` event signals that the surface shouldn't be displayed + * anymore. This can happen when a null buffer is committed, + * the associated role object is destroyed, or when the role-specific + * conditions for the surface to be mapped no longer apply. + */ + struct wl_signal unmap; + + /** + * Note: unlike other new_* signals, new_subsurface is emitted when + * the subsurface is added to the parent surface's current state, + * not when the object is created. + */ + struct wl_signal new_subsurface; // struct wlr_subsurface + struct wl_signal destroy; + } events; + + struct wl_list current_outputs; // wlr_surface_output.link + + struct wlr_addon_set addons; + void *data; + + // private state + + struct wl_listener renderer_destroy; + struct wl_listener role_resource_destroy; + + struct { + int32_t scale; + enum wl_output_transform transform; + int width, height; + int buffer_width, buffer_height; + } previous; + + bool unmap_commit; + + bool opaque; + + bool handling_commit; + bool pending_rejected; + + int32_t preferred_buffer_scale; + bool preferred_buffer_transform_sent; + enum wl_output_transform preferred_buffer_transform; + + struct wl_list synced; // wlr_surface_synced.link + size_t synced_len; + + struct wl_resource *pending_buffer_resource; + struct wl_listener pending_buffer_resource_destroy; +}; + +struct wlr_renderer; + +struct wlr_compositor { + struct wl_global *global; + struct wlr_renderer *renderer; // may be NULL + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_surface; + struct wl_signal destroy; + } events; +}; + +typedef void (*wlr_surface_iterator_func_t)(struct wlr_surface *surface, + int sx, int sy, void *data); + +/** + * Set the lifetime role for this surface. + * + * If the surface already has a different role and/or has a role object set, + * the function fails and sends an error to the client. + * + * Returns true on success, false otherwise. + */ +bool wlr_surface_set_role(struct wlr_surface *surface, const struct wlr_surface_role *role, + struct wl_resource *error_resource, uint32_t error_code); + +/** + * Set the role object for this surface. The surface must have a role and + * no already set role object. + * + * When the resource is destroyed, the surface is unmapped, + * wlr_surface_role.destroy is called and the role object is unset. + */ +void wlr_surface_set_role_object(struct wlr_surface *surface, struct wl_resource *role_resource); + +/** + * Map the surface. If the surface is already mapped, this is no-op. + * + * This function must only be used by surface role implementations. + */ +void wlr_surface_map(struct wlr_surface *surface); + +/** + * Unmap the surface. If the surface is already unmapped, this is no-op. + * + * This function must only be used by surface role implementations. + */ +void wlr_surface_unmap(struct wlr_surface *surface); + +/** + * Mark the pending state of a surface as rejected due to a protocol violation, + * preventing it from being cached or committed. + * + * This function must only be used while processing a commit request. + */ +void wlr_surface_reject_pending(struct wlr_surface *surface, struct wl_resource *resource, + uint32_t code, const char *msg, ...); + +/** + * Whether or not this surface currently has an attached buffer. A surface has + * an attached buffer when it commits with a non-null buffer in its pending + * state. A surface will not have a buffer if it has never committed one or has + * committed a null buffer. + */ +bool wlr_surface_has_buffer(struct wlr_surface *surface); + +/** + * Check whether this surface state has an attached buffer. + * + * A surface has an attached buffer when the client commits with a non-null + * buffer. A surface will not have a buffer if the client never committed one, + * or committed a null buffer. + * + * Note that wlr_surface_state.buffer may be NULL even if this function returns + * true: the buffer field is reset after commit, to allow the buffer to be + * released to the client. Additionally, the buffer import or upload may fail. + */ +bool wlr_surface_state_has_buffer(const struct wlr_surface_state *state); + +/** + * Get the texture of the buffer currently attached to this surface. Returns + * NULL if no buffer is currently attached or if something went wrong with + * uploading the buffer. + */ +struct wlr_texture *wlr_surface_get_texture(struct wlr_surface *surface); + +/** + * Get the root of the subsurface tree for this surface. Can return NULL if + * a surface in the tree has been destroyed. + */ +struct wlr_surface *wlr_surface_get_root_surface(struct wlr_surface *surface); + +/** + * Check if the surface accepts input events at the given surface-local + * coordinates. Does not check the surface's subsurfaces. + */ +bool wlr_surface_point_accepts_input(struct wlr_surface *surface, + double sx, double sy); + +/** + * Find a surface in this surface's tree that accepts input events and has all + * parents mapped (except this surface, which can be unmapped) at the given + * surface-local coordinates. Returns the surface and coordinates in the leaf + * surface coordinate system or NULL if no surface is found at that location. + */ +struct wlr_surface *wlr_surface_surface_at(struct wlr_surface *surface, + double sx, double sy, double *sub_x, double *sub_y); + +/** + * Notify the client that the surface has entered an output. + * + * This is a no-op if the surface has already entered the output. + */ +void wlr_surface_send_enter(struct wlr_surface *surface, + struct wlr_output *output); + +/** + * Notify the client that the surface has left an output. + * + * This is a no-op if the surface has already left the output. + */ +void wlr_surface_send_leave(struct wlr_surface *surface, + struct wlr_output *output); + +/** + * Complete the queued frame callbacks for this surface. + * + * This will send an event to the client indicating that now is a good time to + * draw its next frame. + */ +void wlr_surface_send_frame_done(struct wlr_surface *surface, + const struct timespec *when); + +/** + * Get the bounding box that contains the surface and all subsurfaces in + * surface coordinates. + * X and y may be negative, if there are subsurfaces with negative position. + */ +void wlr_surface_get_extends(struct wlr_surface *surface, struct wlr_box *box); + +/** + * Get the struct wlr_surface corresponding to a wl_surface resource. + * + * This asserts that the resource is a valid wl_surface resource created by + * wlroots and will never return NULL. + */ +struct wlr_surface *wlr_surface_from_resource(struct wl_resource *resource); + +/** + * Call `iterator` on each mapped surface in the surface tree (whether or not + * this surface is mapped), with the surface's position relative to the root + * surface. The function is called from root to leaves (in rendering order). + */ +void wlr_surface_for_each_surface(struct wlr_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data); + +/** + * Get the effective surface damage in surface-local coordinate space. + */ +void wlr_surface_get_effective_damage(struct wlr_surface *surface, + pixman_region32_t *damage); + +/** + * Get the source rectangle describing the region of the buffer that needs to + * be sampled to render this surface's current state. The box is in + * buffer-local coordinates. + * + * If the viewport's source rectangle is unset, the position is zero and the + * size is the buffer's. + */ +void wlr_surface_get_buffer_source_box(struct wlr_surface *surface, + struct wlr_fbox *box); + +/** + * Acquire a lock for the pending surface state. + * + * The state won't be committed before the caller releases the lock. Instead, + * the state becomes cached. The caller needs to use wlr_surface_unlock_cached() + * to release the lock. + * + * Returns a surface commit sequence number for the cached state. + */ +uint32_t wlr_surface_lock_pending(struct wlr_surface *surface); + +/** + * Release a lock for a cached state. + * + * Callers should not assume that the cached state will immediately be + * committed. Another caller may still have an active lock. + */ +void wlr_surface_unlock_cached(struct wlr_surface *surface, uint32_t seq); + +/** + * Set the preferred buffer scale for the surface. + * + * This sends an event to the client indicating the preferred scale to use for + * buffers attached to this surface. + */ +void wlr_surface_set_preferred_buffer_scale(struct wlr_surface *surface, + int32_t scale); + +/** + * Set the preferred buffer transform for the surface. + * + * This sends an event to the client indicating the preferred transform to use + * for buffers attached to this surface. + */ +void wlr_surface_set_preferred_buffer_transform(struct wlr_surface *surface, + enum wl_output_transform transform); + +/** + * Implementation for struct wlr_surface_synced. + * + * struct wlr_surface takes care of allocating the sync'ed object state. + * + * The only mandatory field is state_size. + */ +struct wlr_surface_synced_impl { + // Size in bytes of the state struct. + size_t state_size; + // Initialize a state. If NULL, this is a no-op. + void (*init_state)(void *state); + // Finish a state. If NULL, this is a no-op. + void (*finish_state)(void *state); + // Move a state. If NULL, memcpy() is used. + void (*move_state)(void *dst, void *src); +}; + +/** + * An object synchronized with a surface. + * + * This is typically used by surface add-ons which integrate with the surface + * commit mechanism. + * + * A sync'ed object maintains state whose lifecycle is managed by + * struct wlr_surface_synced_impl. Clients make requests to mutate the pending + * state, then clients commit the pending state via wl_surface.commit. The + * pending state may become cached, then becomes current when it's applied. + */ +struct wlr_surface_synced { + struct wlr_surface *surface; + const struct wlr_surface_synced_impl *impl; + struct wl_list link; // wlr_surface.synced + size_t index; +}; + +/** + * Initialize a sync'ed object. + * + * pending and current must be pointers to the sync'ed object's state. This + * function will initialize them. + */ +bool wlr_surface_synced_init(struct wlr_surface_synced *synced, + struct wlr_surface *surface, const struct wlr_surface_synced_impl *impl, + void *pending, void *current); +/** + * Finish a sync'ed object. + * + * This must be called before the struct wlr_surface is destroyed. + */ +void wlr_surface_synced_finish(struct wlr_surface_synced *synced); +/** + * Obtain a sync'ed object state. + */ +void *wlr_surface_synced_get_state(struct wlr_surface_synced *synced, + const struct wlr_surface_state *state); + +/** + * Get a Pixman region from a wl_region resource. + */ +const pixman_region32_t *wlr_region_from_resource(struct wl_resource *resource); + +/** + * Create the wl_compositor global, which can be used by clients to create + * surfaces and regions. + * + * If a renderer is supplied, the compositor will create struct wlr_texture + * objects from client buffers on surface commit. + */ +struct wlr_compositor *wlr_compositor_create(struct wl_display *display, + uint32_t version, struct wlr_renderer *renderer); + +#endif diff --git a/include/wlr/types/wlr_content_type_v1.h b/include/wlr/types/wlr_content_type_v1.h new file mode 100644 index 0000000..6fa13d9 --- /dev/null +++ b/include/wlr/types/wlr_content_type_v1.h @@ -0,0 +1,36 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_CONTENT_TYPE_V1_H +#define WLR_TYPES_WLR_CONTENT_TYPE_V1_H + +#include +#include "content-type-v1-protocol.h" + +struct wlr_surface; + +struct wlr_content_type_manager_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + } events; + + void *data; + + // private state + + struct wl_listener display_destroy; +}; + +struct wlr_content_type_manager_v1 *wlr_content_type_manager_v1_create( + struct wl_display *display, uint32_t version); +enum wp_content_type_v1_type wlr_surface_get_content_type_v1( + struct wlr_content_type_manager_v1 *manager, struct wlr_surface *surface); + +#endif diff --git a/include/wlr/types/wlr_cursor.h b/include/wlr/types/wlr_cursor.h new file mode 100644 index 0000000..57d6e50 --- /dev/null +++ b/include/wlr/types/wlr_cursor.h @@ -0,0 +1,218 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_CURSOR_H +#define WLR_TYPES_WLR_CURSOR_H + +#include +#include +#include + +struct wlr_input_device; +struct wlr_xcursor_manager; + +/** + * wlr_cursor implements the behavior of the "cursor", that is, the image on the + * screen typically moved about with a mouse or so. It provides tracking for + * this in global coordinates, and integrates with struct wlr_output, + * struct wlr_output_layout, and struct wlr_input_device. You can use it to + * abstract multiple input devices over a single cursor, constrain cursor + * movement to the usable area of a struct wlr_output_layout and communicate + * position updates to the hardware cursor, constrain specific input devices to + * specific outputs or regions of the screen, and so on. + */ + +struct wlr_box; +struct wlr_cursor_state; + +struct wlr_cursor { + struct wlr_cursor_state *state; + double x, y; + + /** + * The interpretation of these signals is the responsibility of the + * compositor, but some helpers are provided for your benefit. If you + * receive a relative motion event, for example, you may want to call + * wlr_cursor_move(). If you receive an absolute event, call + * wlr_cursor_warp_absolute(). If you pass an input device into these + * functions, it will apply the region/output constraints associated with + * that device to the resulting cursor motion. If an output layout is + * attached, these functions will constrain the resulting cursor motion to + * within the usable space of the output layout. + * + * Re-broadcasting these signals to, for example, a struct wlr_seat, is also + * your responsibility. + */ + struct { + struct wl_signal motion; + struct wl_signal motion_absolute; + struct wl_signal button; + struct wl_signal axis; + struct wl_signal frame; + struct wl_signal swipe_begin; + struct wl_signal swipe_update; + struct wl_signal swipe_end; + struct wl_signal pinch_begin; + struct wl_signal pinch_update; + struct wl_signal pinch_end; + struct wl_signal hold_begin; + struct wl_signal hold_end; + + struct wl_signal touch_up; + struct wl_signal touch_down; + struct wl_signal touch_motion; + struct wl_signal touch_cancel; + struct wl_signal touch_frame; + + struct wl_signal tablet_tool_axis; + struct wl_signal tablet_tool_proximity; + struct wl_signal tablet_tool_tip; + struct wl_signal tablet_tool_button; + } events; + + void *data; +}; + +struct wlr_cursor *wlr_cursor_create(void); + +void wlr_cursor_destroy(struct wlr_cursor *cur); + +/** + * Warp the cursor to the given x and y in layout coordinates. If x and y are + * out of the layout boundaries or constraints, no warp will happen. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + * + * Returns true when the cursor warp was successful. + */ +bool wlr_cursor_warp(struct wlr_cursor *cur, struct wlr_input_device *dev, + double lx, double ly); + +/** + * Convert absolute 0..1 coordinates to layout coordinates. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + */ +void wlr_cursor_absolute_to_layout_coords(struct wlr_cursor *cur, + struct wlr_input_device *dev, double x, double y, double *lx, double *ly); + + +/** + * Warp the cursor to the given x and y coordinates. If the given point is out + * of the layout boundaries or constraints, the closest point will be used. + * If one coordinate is NAN, it will be ignored. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + */ +void wlr_cursor_warp_closest(struct wlr_cursor *cur, + struct wlr_input_device *dev, double x, double y); + +/** + * Warp the cursor to the given x and y in absolute 0..1 coordinates. If the + * given point is out of the layout boundaries or constraints, the closest point + * will be used. If one coordinate is NAN, it will be ignored. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + */ +void wlr_cursor_warp_absolute(struct wlr_cursor *cur, + struct wlr_input_device *dev, double x, double y); + +/** + * Move the cursor in the direction of the given x and y layout coordinates. If + * one coordinate is NAN, it will be ignored. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + */ +void wlr_cursor_move(struct wlr_cursor *cur, struct wlr_input_device *dev, + double delta_x, double delta_y); + +/** + * Set the cursor buffer. + * + * The buffer is used on all outputs and is scaled accordingly. The hotspot is + * expressed in logical coordinates. A NULL buffer hides the cursor. + */ +void wlr_cursor_set_buffer(struct wlr_cursor *cur, struct wlr_buffer *buffer, + int32_t hotspot_x, int32_t hotspot_y, float scale); + +/** + * Hide the cursor image. + */ +void wlr_cursor_unset_image(struct wlr_cursor *cur); + +/** + * Set the cursor image from an XCursor theme. + * + * The image will be loaded from the struct wlr_xcursor_manager. + */ +void wlr_cursor_set_xcursor(struct wlr_cursor *cur, + struct wlr_xcursor_manager *manager, const char *name); + +/** + * Set the cursor surface. The surface can be committed to update the cursor + * image. The surface position is subtracted from the hotspot. A NULL surface + * commit hides the cursor. + */ +void wlr_cursor_set_surface(struct wlr_cursor *cur, struct wlr_surface *surface, + int32_t hotspot_x, int32_t hotspot_y); + +/** + * Attaches this input device to this cursor. The input device must be one of: + * + * - WLR_INPUT_DEVICE_POINTER + * - WLR_INPUT_DEVICE_TOUCH + * - WLR_INPUT_DEVICE_TABLET + */ +void wlr_cursor_attach_input_device(struct wlr_cursor *cur, + struct wlr_input_device *dev); + +void wlr_cursor_detach_input_device(struct wlr_cursor *cur, + struct wlr_input_device *dev); +/** + * Uses the given layout to establish the boundaries and movement semantics of + * this cursor. Cursors without an output layout allow infinite movement in any + * direction and do not support absolute input events. + */ +void wlr_cursor_attach_output_layout(struct wlr_cursor *cur, + struct wlr_output_layout *l); + +/** + * Attaches this cursor to the given output, which must be among the outputs in + * the current output_layout for this cursor. This call is invalid for a cursor + * without an associated output layout. + */ +void wlr_cursor_map_to_output(struct wlr_cursor *cur, + struct wlr_output *output); + +/** + * Maps all input from a specific input device to a given output. The input + * device must be attached to this cursor and the output must be among the + * outputs in the attached output layout. + */ +void wlr_cursor_map_input_to_output(struct wlr_cursor *cur, + struct wlr_input_device *dev, struct wlr_output *output); + +/** + * Maps this cursor to an arbitrary region on the associated + * struct wlr_output_layout. + */ +void wlr_cursor_map_to_region(struct wlr_cursor *cur, const struct wlr_box *box); + +/** + * Maps inputs from this input device to an arbitrary region on the associated + * struct wlr_output_layout. + */ +void wlr_cursor_map_input_to_region(struct wlr_cursor *cur, + struct wlr_input_device *dev, const struct wlr_box *box); + +#endif diff --git a/include/wlr/types/wlr_cursor_shape_v1.h b/include/wlr/types/wlr_cursor_shape_v1.h new file mode 100644 index 0000000..55818c8 --- /dev/null +++ b/include/wlr/types/wlr_cursor_shape_v1.h @@ -0,0 +1,60 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_CURSOR_SHAPE_V1_H +#define WLR_TYPES_WLR_CURSOR_SHAPE_V1_H + +#include +#include "cursor-shape-v1-protocol.h" + +/** + * Manager for the cursor-shape-v1 protocol. + * + * Compositors should listen to the request_set_shape event and handle it in + * the same way as wlr_seat.events.request_set_cursor. + */ +struct wlr_cursor_shape_manager_v1 { + struct wl_global *global; + + struct { + struct wl_signal request_set_shape; // struct wlr_cursor_shape_manager_v1_request_set_shape_event + struct wl_signal destroy; + } events; + + void *data; + + // private state + + struct wl_listener display_destroy; +}; + +enum wlr_cursor_shape_manager_v1_device_type { + WLR_CURSOR_SHAPE_MANAGER_V1_DEVICE_TYPE_POINTER, + WLR_CURSOR_SHAPE_MANAGER_V1_DEVICE_TYPE_TABLET_TOOL, +}; + +struct wlr_cursor_shape_manager_v1_request_set_shape_event { + struct wlr_seat_client *seat_client; + enum wlr_cursor_shape_manager_v1_device_type device_type; + // NULL if device_type is not TABLET_TOOL + struct wlr_tablet_v2_tablet_tool *tablet_tool; + uint32_t serial; + enum wp_cursor_shape_device_v1_shape shape; +}; + +struct wlr_cursor_shape_manager_v1 *wlr_cursor_shape_manager_v1_create( + struct wl_display *display, uint32_t version); + +/** + * Get the name of a cursor shape. + * + * The name can be used to load a cursor from an XCursor theme. + */ +const char *wlr_cursor_shape_v1_name(enum wp_cursor_shape_device_v1_shape shape); + +#endif diff --git a/include/wlr/types/wlr_damage_ring.h b/include/wlr/types/wlr_damage_ring.h new file mode 100644 index 0000000..203d919 --- /dev/null +++ b/include/wlr/types/wlr_damage_ring.h @@ -0,0 +1,109 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_DAMAGE_RING_H +#define WLR_TYPES_WLR_DAMAGE_RING_H + +#include +#include +#include +#include +#include + +/* For triple buffering, a history of two frames is required. */ +#define WLR_DAMAGE_RING_PREVIOUS_LEN 2 + +struct wlr_box; + +struct wlr_damage_ring_buffer { + struct wlr_buffer *buffer; + struct wl_listener destroy; + pixman_region32_t damage; + + struct wlr_damage_ring *ring; + struct wl_list link; // wlr_damage_ring.buffers +}; + +struct wlr_damage_ring { + int32_t width, height; + + // Difference between the current buffer and the previous one + pixman_region32_t current; + + // private state + + pixman_region32_t previous[WLR_DAMAGE_RING_PREVIOUS_LEN]; + size_t previous_idx; + + struct wl_list buffers; // wlr_damage_ring_buffer.link +}; + +void wlr_damage_ring_init(struct wlr_damage_ring *ring); + +void wlr_damage_ring_finish(struct wlr_damage_ring *ring); + +/** + * Set ring bounds and damage the ring fully. + * + * Next time damage will be added, it will be cropped to the ring bounds. + * If at least one of the dimensions is 0, bounds are removed. + * + * By default, a damage ring doesn't have bounds. + */ +void wlr_damage_ring_set_bounds(struct wlr_damage_ring *ring, + int32_t width, int32_t height); + +/** + * Add a region to the current damage. + * + * Returns true if the region intersects the ring bounds, false otherwise. + */ +bool wlr_damage_ring_add(struct wlr_damage_ring *ring, + const pixman_region32_t *damage); + +/** + * Add a box to the current damage. + * + * Returns true if the box intersects the ring bounds, false otherwise. + */ +bool wlr_damage_ring_add_box(struct wlr_damage_ring *ring, + const struct wlr_box *box); + +/** + * Damage the ring fully. + */ +void wlr_damage_ring_add_whole(struct wlr_damage_ring *ring); + +/** + * Rotate the damage ring. This needs to be called after using the accumulated + * damage, e.g. after rendering to an output's back buffer. + */ +void wlr_damage_ring_rotate(struct wlr_damage_ring *ring); + +/** + * Get accumulated damage, which is the difference between the current buffer + * and the buffer with age of buffer_age; in context of rendering, this is + * the region that needs to be redrawn. + */ +void wlr_damage_ring_get_buffer_damage(struct wlr_damage_ring *ring, + int buffer_age, pixman_region32_t *damage); + +/** + * Get accumulated buffer damage and rotate the damage ring. + * + * The accumulated buffer damage is the difference between the to-be-painted + * buffer and the passed-in buffer. In other words, this is the region that + * needs to be redrawn. + * + * Users should damage the ring if an error occurs while rendering or + * submitting the new buffer to the backend. + */ +void wlr_damage_ring_rotate_buffer(struct wlr_damage_ring *ring, + struct wlr_buffer *buffer, pixman_region32_t *damage); + +#endif diff --git a/include/wlr/types/wlr_data_control_v1.h b/include/wlr/types/wlr_data_control_v1.h new file mode 100644 index 0000000..4435f14 --- /dev/null +++ b/include/wlr/types/wlr_data_control_v1.h @@ -0,0 +1,47 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_DATA_CONTROL_V1_H +#define WLR_TYPES_WLR_DATA_CONTROL_V1_H + +#include +#include + +struct wlr_data_control_manager_v1 { + struct wl_global *global; + struct wl_list devices; // wlr_data_control_device_v1.link + + struct { + struct wl_signal destroy; + struct wl_signal new_device; // wlr_data_control_device_v1 + } events; + + struct wl_listener display_destroy; +}; + +struct wlr_data_control_device_v1 { + struct wl_resource *resource; + struct wlr_data_control_manager_v1 *manager; + struct wl_list link; // wlr_data_control_manager_v1.devices + + struct wlr_seat *seat; + struct wl_resource *selection_offer_resource; // current selection offer + struct wl_resource *primary_selection_offer_resource; // current primary selection offer + + struct wl_listener seat_destroy; + struct wl_listener seat_set_selection; + struct wl_listener seat_set_primary_selection; +}; + +struct wlr_data_control_manager_v1 *wlr_data_control_manager_v1_create( + struct wl_display *display); + +void wlr_data_control_device_v1_destroy( + struct wlr_data_control_device_v1 *device); + +#endif diff --git a/include/wlr/types/wlr_data_device.h b/include/wlr/types/wlr_data_device.h new file mode 100644 index 0000000..85d661a --- /dev/null +++ b/include/wlr/types/wlr_data_device.h @@ -0,0 +1,255 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_DATA_DEVICE_H +#define WLR_TYPES_WLR_DATA_DEVICE_H + +#include +#include + +struct wlr_data_device_manager { + struct wl_global *global; + struct wl_list data_sources; + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +enum wlr_data_offer_type { + WLR_DATA_OFFER_SELECTION, + WLR_DATA_OFFER_DRAG, +}; + +struct wlr_data_offer { + struct wl_resource *resource; + struct wlr_data_source *source; + enum wlr_data_offer_type type; + struct wl_list link; // wlr_seat.{selection_offers,drag_offers} + + uint32_t actions; + enum wl_data_device_manager_dnd_action preferred_action; + bool in_ask; + + struct wl_listener source_destroy; +}; + +/** + * A data source implementation. Only the `send` function is mandatory. Refer to + * the matching `wlr_data_source_*` functions documentation to know what they do. + */ +struct wlr_data_source_impl { + void (*send)(struct wlr_data_source *source, const char *mime_type, + int32_t fd); + void (*accept)(struct wlr_data_source *source, uint32_t serial, + const char *mime_type); + void (*destroy)(struct wlr_data_source *source); + + void (*dnd_drop)(struct wlr_data_source *source); + void (*dnd_finish)(struct wlr_data_source *source); + void (*dnd_action)(struct wlr_data_source *source, + enum wl_data_device_manager_dnd_action action); +}; + +struct wlr_data_source { + const struct wlr_data_source_impl *impl; + + // source metadata + struct wl_array mime_types; + int32_t actions; + + // source status + bool accepted; + + // drag'n'drop status + enum wl_data_device_manager_dnd_action current_dnd_action; + uint32_t compositor_action; + + struct { + struct wl_signal destroy; + } events; +}; + +struct wlr_drag; + +struct wlr_drag_icon { + struct wlr_drag *drag; + struct wlr_surface *surface; + + struct { + struct wl_signal destroy; + } events; + + struct wl_listener surface_destroy; + + void *data; +}; + +enum wlr_drag_grab_type { + WLR_DRAG_GRAB_KEYBOARD, + WLR_DRAG_GRAB_KEYBOARD_POINTER, + WLR_DRAG_GRAB_KEYBOARD_TOUCH, +}; + +struct wlr_drag { + enum wlr_drag_grab_type grab_type; + struct wlr_seat_keyboard_grab keyboard_grab; + struct wlr_seat_pointer_grab pointer_grab; + struct wlr_seat_touch_grab touch_grab; + + struct wlr_seat *seat; + struct wlr_seat_client *seat_client; + struct wlr_seat_client *focus_client; + + struct wlr_drag_icon *icon; // can be NULL + struct wlr_surface *focus; // can be NULL + struct wlr_data_source *source; // can be NULL + + bool started, dropped, cancelling; + int32_t grab_touch_id, touch_id; // if WLR_DRAG_GRAB_TOUCH + + struct { + struct wl_signal focus; + struct wl_signal motion; // struct wlr_drag_motion_event + struct wl_signal drop; // struct wlr_drag_drop_event + struct wl_signal destroy; + } events; + + struct wl_listener source_destroy; + struct wl_listener seat_client_destroy; + struct wl_listener icon_destroy; + + void *data; +}; + +struct wlr_drag_motion_event { + struct wlr_drag *drag; + uint32_t time; + double sx, sy; +}; + +struct wlr_drag_drop_event { + struct wlr_drag *drag; + uint32_t time; +}; + +/** + * Create a wl_data_device_manager global for this display. + */ +struct wlr_data_device_manager *wlr_data_device_manager_create( + struct wl_display *display); + +/** + * Requests a selection to be set for the seat. If the request comes from + * a client, then set `client` to be the matching seat client so that this + * function can verify that the serial provided was once sent to the client + * on this seat. + */ +void wlr_seat_request_set_selection(struct wlr_seat *seat, + struct wlr_seat_client *client, struct wlr_data_source *source, + uint32_t serial); + +/** + * Sets the current selection for the seat. NULL can be provided to clear it. + * This removes the previous one if there was any. In case the selection doesn't + * come from a client, wl_display_next_serial() can be used to generate a + * serial. + */ +void wlr_seat_set_selection(struct wlr_seat *seat, + struct wlr_data_source *source, uint32_t serial); + +/** + * Creates a new drag. To request to start the drag, call + * wlr_seat_request_start_drag(). + */ +struct wlr_drag *wlr_drag_create(struct wlr_seat_client *seat_client, + struct wlr_data_source *source, struct wlr_surface *icon_surface); + +/** + * Requests a drag to be started on the seat. + */ +void wlr_seat_request_start_drag(struct wlr_seat *seat, struct wlr_drag *drag, + struct wlr_surface *origin, uint32_t serial); + +/** + * Starts a drag on the seat. This starts an implicit keyboard grab, but doesn't + * start a pointer or a touch grab. + */ +void wlr_seat_start_drag(struct wlr_seat *seat, struct wlr_drag *drag, + uint32_t serial); + +/** + * Starts a pointer drag on the seat. This starts implicit keyboard and pointer + * grabs. + */ +void wlr_seat_start_pointer_drag(struct wlr_seat *seat, struct wlr_drag *drag, + uint32_t serial); + +/** + * Starts a touch drag on the seat. This starts implicit keyboard and touch + * grabs. + */ +void wlr_seat_start_touch_drag(struct wlr_seat *seat, struct wlr_drag *drag, + uint32_t serial, struct wlr_touch_point *point); + +/** + * Initializes the data source with the provided implementation. + */ +void wlr_data_source_init(struct wlr_data_source *source, + const struct wlr_data_source_impl *impl); + +/** + * Sends the data as the specified MIME type over the passed file descriptor, + * then close it. + */ +void wlr_data_source_send(struct wlr_data_source *source, const char *mime_type, + int32_t fd); + +/** + * Notifies the data source that a target accepts one of the offered MIME types. + * If a target doesn't accept any of the offered types, `mime_type` is NULL. + */ +void wlr_data_source_accept(struct wlr_data_source *source, uint32_t serial, + const char *mime_type); + +/** + * Notifies the data source it is no longer valid and should be destroyed. That + * destroys immediately the data source. + */ +void wlr_data_source_destroy(struct wlr_data_source *source); + +/** + * Notifies the data source that the drop operation was performed. This does not + * indicate acceptance. + * + * The data source may still be used in the future and should not be destroyed + * here. + */ +void wlr_data_source_dnd_drop(struct wlr_data_source *source); + +/** + * Notifies the data source that the drag-and-drop operation concluded. That + * potentially destroys immediately the data source. + */ +void wlr_data_source_dnd_finish(struct wlr_data_source *source); + +/** + * Notifies the data source that a target accepts the drag with the specified + * action. + * + * This shouldn't be called after wlr_data_source_dnd_drop() unless the + * drag-and-drop operation ended in an "ask" action. + */ +void wlr_data_source_dnd_action(struct wlr_data_source *source, + enum wl_data_device_manager_dnd_action action); + +#endif diff --git a/include/wlr/types/wlr_drm.h b/include/wlr/types/wlr_drm.h new file mode 100644 index 0000000..b2a5ce5 --- /dev/null +++ b/include/wlr/types/wlr_drm.h @@ -0,0 +1,57 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_DRM_H +#define WLR_TYPES_WLR_DRM_H + +#include +#include +#include + +struct wlr_renderer; + +struct wlr_drm_buffer { + struct wlr_buffer base; + + struct wl_resource *resource; // can be NULL if the client destroyed it + struct wlr_dmabuf_attributes dmabuf; + + struct wl_listener release; +}; + +/** + * A stub implementation of Mesa's wl_drm protocol. + * + * It only implements the minimum necessary for modern clients to behave + * properly. In particular, flink handles are left unimplemented. + * + * Deprecated: this protocol is legacy and superseded by linux-dmabuf. The + * implementation will be dropped in a future wlroots version. + */ +struct wlr_drm { + struct wl_global *global; + + struct { + struct wl_signal destroy; + } events; + + // private state + + char *node_name; + struct wlr_drm_format_set formats; + + struct wl_listener display_destroy; +}; + +struct wlr_drm_buffer *wlr_drm_buffer_try_from_resource( + struct wl_resource *resource); + +struct wlr_drm *wlr_drm_create(struct wl_display *display, + struct wlr_renderer *renderer); + +#endif diff --git a/include/wlr/types/wlr_drm_lease_v1.h b/include/wlr/types/wlr_drm_lease_v1.h new file mode 100644 index 0000000..cda02a1 --- /dev/null +++ b/include/wlr/types/wlr_drm_lease_v1.h @@ -0,0 +1,146 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_DRM_LEASE_V1_H +#define WLR_TYPES_WLR_DRM_LEASE_V1_H + +#include + +struct wlr_backend; +struct wlr_output; + +struct wlr_drm_lease_v1_manager { + struct wl_list devices; // wlr_drm_lease_device_v1.link + + struct wl_display *display; + struct wl_listener display_destroy; + + struct { + /** + * Upon receiving this signal, call + * wlr_drm_lease_device_v1_grant_lease_request() to grant a lease of the + * requested DRM resources, or + * wlr_drm_lease_device_v1_reject_lease_request() to reject the request. + */ + struct wl_signal request; + } events; +}; + +struct wlr_drm_lease_device_v1 { + struct wl_list resources; + struct wl_global *global; + + struct wlr_drm_lease_v1_manager *manager; + struct wlr_backend *backend; + + struct wl_list connectors; // wlr_drm_lease_connector_v1.link + struct wl_list leases; // wlr_drm_lease_v1.link + struct wl_list requests; // wlr_drm_lease_request_v1.link + struct wl_list link; // wlr_drm_lease_v1_manager.devices + + struct wl_listener backend_destroy; + + void *data; +}; + +struct wlr_drm_lease_v1; + +struct wlr_drm_lease_connector_v1 { + struct wl_list resources; // wl_resource_get_link() + + struct wlr_output *output; + struct wlr_drm_lease_device_v1 *device; + /** NULL if no client is currently leasing this connector */ + struct wlr_drm_lease_v1 *active_lease; + + struct wl_listener destroy; + + struct wl_list link; // wlr_drm_lease_device_v1.connectors +}; + +struct wlr_drm_lease_request_v1 { + struct wl_resource *resource; + + struct wlr_drm_lease_device_v1 *device; + + struct wlr_drm_lease_connector_v1 **connectors; + size_t n_connectors; + + struct wl_resource *lease_resource; + + bool invalid; + + struct wl_list link; // wlr_drm_lease_device_v1.requests +}; + +struct wlr_drm_lease_v1 { + struct wl_resource *resource; + struct wlr_drm_lease *drm_lease; + + struct wlr_drm_lease_device_v1 *device; + + struct wlr_drm_lease_connector_v1 **connectors; + size_t n_connectors; + + struct wl_list link; // wlr_drm_lease_device_v1.leases + + struct wl_listener destroy; + + void *data; +}; + +/** + * Creates a DRM lease manager. A DRM lease device will be created for each + * DRM backend supplied in case of a struct wlr_multi_backend. + * + * Returns NULL if no DRM backend is supplied. + */ +struct wlr_drm_lease_v1_manager *wlr_drm_lease_v1_manager_create( + struct wl_display *display, struct wlr_backend *backend); + +/** + * Offers a wlr_output for lease. + * + * Returns false if the output can't be offered to lease. + */ +bool wlr_drm_lease_v1_manager_offer_output( + struct wlr_drm_lease_v1_manager *manager, struct wlr_output *output); + +/** + * Withdraws a previously offered output for lease. If the output is leased to + * a client, a finished event will be send and the lease will be terminated. + */ +void wlr_drm_lease_v1_manager_withdraw_output( + struct wlr_drm_lease_v1_manager *manager, struct wlr_output *output); + +/** + * Grants a client's lease request. The lease device will then provision the + * DRM lease and transfer the file descriptor to the client. After calling this, + * each struct wlr_output leased is destroyed, and will be re-issued through + * wlr_backend.events.new_outputs when the lease is revoked. + * + * This will return NULL without leasing any resources if the lease is invalid; + * this can happen for example if two clients request the same resources and an + * attempt to grant both leases is made. + */ +struct wlr_drm_lease_v1 *wlr_drm_lease_request_v1_grant( + struct wlr_drm_lease_request_v1 *request); + +/** + * Rejects a client's lease request. The output will still be available to + * lease until withdrawn by the compositor. + */ +void wlr_drm_lease_request_v1_reject(struct wlr_drm_lease_request_v1 *request); + +/** + * Revokes a client's lease request. The output will still be available to + * lease until withdrawn by the compositor. + */ +void wlr_drm_lease_v1_revoke(struct wlr_drm_lease_v1 *lease); + +#endif diff --git a/include/wlr/types/wlr_export_dmabuf_v1.h b/include/wlr/types/wlr_export_dmabuf_v1.h new file mode 100644 index 0000000..ba2d8bd --- /dev/null +++ b/include/wlr/types/wlr_export_dmabuf_v1.h @@ -0,0 +1,43 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_EXPORT_DMABUF_V1_H +#define WLR_TYPES_WLR_EXPORT_DMABUF_V1_H + +#include +#include +#include + +struct wlr_export_dmabuf_manager_v1 { + struct wl_global *global; + struct wl_list frames; // wlr_export_dmabuf_frame_v1.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; +}; + +struct wlr_export_dmabuf_frame_v1 { + struct wl_resource *resource; + struct wlr_export_dmabuf_manager_v1 *manager; + struct wl_list link; // wlr_export_dmabuf_manager_v1.frames + + struct wlr_output *output; + + bool cursor_locked; + + struct wl_listener output_commit; + struct wl_listener output_destroy; +}; + +struct wlr_export_dmabuf_manager_v1 *wlr_export_dmabuf_manager_v1_create( + struct wl_display *display); + +#endif diff --git a/include/wlr/types/wlr_ext_foreign_toplevel_list_v1.h b/include/wlr/types/wlr_ext_foreign_toplevel_list_v1.h new file mode 100644 index 0000000..a5ba9d3 --- /dev/null +++ b/include/wlr/types/wlr_ext_foreign_toplevel_list_v1.h @@ -0,0 +1,67 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_FOREIGN_TOPLEVEL_LIST_V1_H +#define WLR_TYPES_WLR_FOREIGN_TOPLEVEL_LIST_V1_H + +#include + +struct wlr_ext_foreign_toplevel_list_v1 { + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link() + struct wl_list toplevels; // ext_foreign_toplevel_handle_v1.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_ext_foreign_toplevel_handle_v1 { + struct wlr_ext_foreign_toplevel_list_v1 *list; + struct wl_list resources; // wl_resource_get_link() + struct wl_list link; // wlr_ext_foreign_toplevel_list_v1.toplevels + + char *title; + char *app_id; + char *identifier; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_ext_foreign_toplevel_handle_v1_state { + const char *title; + const char *app_id; +}; + +struct wlr_ext_foreign_toplevel_list_v1 *wlr_ext_foreign_toplevel_list_v1_create( + struct wl_display *display, uint32_t version); + +struct wlr_ext_foreign_toplevel_handle_v1 *wlr_ext_foreign_toplevel_handle_v1_create( + struct wlr_ext_foreign_toplevel_list_v1 *list, + const struct wlr_ext_foreign_toplevel_handle_v1_state *state); + +/** + * Destroy the given toplevel handle, sending the closed event to any + * client. + */ +void wlr_ext_foreign_toplevel_handle_v1_destroy( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel); + +void wlr_ext_foreign_toplevel_handle_v1_update_state( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + const struct wlr_ext_foreign_toplevel_handle_v1_state *state); + +#endif diff --git a/include/wlr/types/wlr_foreign_toplevel_management_v1.h b/include/wlr/types/wlr_foreign_toplevel_management_v1.h new file mode 100644 index 0000000..c4abcb3 --- /dev/null +++ b/include/wlr/types/wlr_foreign_toplevel_management_v1.h @@ -0,0 +1,153 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_FOREIGN_TOPLEVEL_MANAGEMENT_V1_H +#define WLR_TYPES_WLR_FOREIGN_TOPLEVEL_MANAGEMENT_V1_H + +#include +#include + +struct wlr_foreign_toplevel_manager_v1 { + struct wl_event_loop *event_loop; + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link() + struct wl_list toplevels; // wlr_foreign_toplevel_handle_v1.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +enum wlr_foreign_toplevel_handle_v1_state { + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED = (1 << 0), + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED = (1 << 1), + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED = (1 << 2), + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN = (1 << 3), +}; + +struct wlr_foreign_toplevel_handle_v1_output { + struct wl_list link; // wlr_foreign_toplevel_handle_v1.outputs + struct wlr_output *output; + struct wlr_foreign_toplevel_handle_v1 *toplevel; + + // private state + + struct wl_listener output_bind; + struct wl_listener output_destroy; +}; + +struct wlr_foreign_toplevel_handle_v1 { + struct wlr_foreign_toplevel_manager_v1 *manager; + struct wl_list resources; + struct wl_list link; + struct wl_event_source *idle_source; + + char *title; + char *app_id; + struct wlr_foreign_toplevel_handle_v1 *parent; + struct wl_list outputs; // wlr_foreign_toplevel_v1_output.link + uint32_t state; // enum wlr_foreign_toplevel_v1_state + + struct { + // struct wlr_foreign_toplevel_handle_v1_maximized_event + struct wl_signal request_maximize; + // struct wlr_foreign_toplevel_handle_v1_minimized_event + struct wl_signal request_minimize; + // struct wlr_foreign_toplevel_handle_v1_activated_event + struct wl_signal request_activate; + // struct wlr_foreign_toplevel_handle_v1_fullscreen_event + struct wl_signal request_fullscreen; + struct wl_signal request_close; + + // struct wlr_foreign_toplevel_handle_v1_set_rectangle_event + struct wl_signal set_rectangle; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_foreign_toplevel_handle_v1_maximized_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + bool maximized; +}; + +struct wlr_foreign_toplevel_handle_v1_minimized_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + bool minimized; +}; + +struct wlr_foreign_toplevel_handle_v1_activated_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + struct wlr_seat *seat; +}; + +struct wlr_foreign_toplevel_handle_v1_fullscreen_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + bool fullscreen; + struct wlr_output *output; +}; + +struct wlr_foreign_toplevel_handle_v1_set_rectangle_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + struct wlr_surface *surface; + int32_t x, y, width, height; +}; + +struct wlr_foreign_toplevel_manager_v1 *wlr_foreign_toplevel_manager_v1_create( + struct wl_display *display); + +struct wlr_foreign_toplevel_handle_v1 *wlr_foreign_toplevel_handle_v1_create( + struct wlr_foreign_toplevel_manager_v1 *manager); +/** + * Destroy the given toplevel handle, sending the closed event to any + * client. Also, if the destroyed toplevel is set as a parent of any + * other valid toplevel, clients still holding a handle to both are + * sent a parent signal with NULL parent. If this is not desired, the + * caller should ensure that any child toplevels are destroyed before + * the parent. + */ +void wlr_foreign_toplevel_handle_v1_destroy( + struct wlr_foreign_toplevel_handle_v1 *toplevel); + +void wlr_foreign_toplevel_handle_v1_set_title( + struct wlr_foreign_toplevel_handle_v1 *toplevel, const char *title); +void wlr_foreign_toplevel_handle_v1_set_app_id( + struct wlr_foreign_toplevel_handle_v1 *toplevel, const char *app_id); + +void wlr_foreign_toplevel_handle_v1_output_enter( + struct wlr_foreign_toplevel_handle_v1 *toplevel, struct wlr_output *output); +void wlr_foreign_toplevel_handle_v1_output_leave( + struct wlr_foreign_toplevel_handle_v1 *toplevel, struct wlr_output *output); + +void wlr_foreign_toplevel_handle_v1_set_maximized( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool maximized); +void wlr_foreign_toplevel_handle_v1_set_minimized( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool minimized); +void wlr_foreign_toplevel_handle_v1_set_activated( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool activated); +void wlr_foreign_toplevel_handle_v1_set_fullscreen( + struct wlr_foreign_toplevel_handle_v1* toplevel, bool fullscreen); + +/** + * Set the parent of a toplevel. If the parent changed from its previous + * value, also sends a parent event to all clients that hold handles to + * both toplevel and parent (no message is sent to clients that have + * previously destroyed their parent handle). NULL is allowed as the + * parent, meaning no parent exists. + */ +void wlr_foreign_toplevel_handle_v1_set_parent( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wlr_foreign_toplevel_handle_v1 *parent); + + +#endif diff --git a/include/wlr/types/wlr_fractional_scale_v1.h b/include/wlr/types/wlr_fractional_scale_v1.h new file mode 100644 index 0000000..9126360 --- /dev/null +++ b/include/wlr/types/wlr_fractional_scale_v1.h @@ -0,0 +1,34 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_FRACTIONAL_SCALE_V1_H +#define WLR_TYPES_WLR_FRACTIONAL_SCALE_V1_H + +#include + +struct wlr_surface; + +struct wlr_fractional_scale_manager_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + } events; + + // private state + + struct wl_listener display_destroy; +}; + +void wlr_fractional_scale_v1_notify_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); + +#endif diff --git a/include/wlr/types/wlr_fullscreen_shell_v1.h b/include/wlr/types/wlr_fullscreen_shell_v1.h new file mode 100644 index 0000000..4e5b2cf --- /dev/null +++ b/include/wlr/types/wlr_fullscreen_shell_v1.h @@ -0,0 +1,39 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_FULLSCREEN_SHELL_V1_H +#define WLR_TYPES_WLR_FULLSCREEN_SHELL_V1_H + +#include +#include "fullscreen-shell-unstable-v1-protocol.h" + +struct wlr_fullscreen_shell_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + // struct wlr_fullscreen_shell_v1_present_surface_event + struct wl_signal present_surface; + } events; + + struct wl_listener display_destroy; + + void *data; +}; + +struct wlr_fullscreen_shell_v1_present_surface_event { + struct wl_client *client; + struct wlr_surface *surface; // can be NULL + enum zwp_fullscreen_shell_v1_present_method method; + struct wlr_output *output; // can be NULL +}; + +struct wlr_fullscreen_shell_v1 *wlr_fullscreen_shell_v1_create( + struct wl_display *display); + +#endif diff --git a/include/wlr/types/wlr_gamma_control_v1.h b/include/wlr/types/wlr_gamma_control_v1.h new file mode 100644 index 0000000..48fce09 --- /dev/null +++ b/include/wlr/types/wlr_gamma_control_v1.h @@ -0,0 +1,50 @@ +#ifndef WLR_TYPES_WLR_GAMMA_CONTROL_V1_H +#define WLR_TYPES_WLR_GAMMA_CONTROL_V1_H + +#include + +struct wlr_output; +struct wlr_output_state; + +struct wlr_gamma_control_manager_v1 { + struct wl_global *global; + struct wl_list controls; // wlr_gamma_control_v1.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + struct wl_signal set_gamma; // struct wlr_gamma_control_manager_v1_set_gamma_event + } events; + + void *data; +}; + +struct wlr_gamma_control_manager_v1_set_gamma_event { + struct wlr_output *output; + struct wlr_gamma_control_v1 *control; // may be NULL +}; + +struct wlr_gamma_control_v1 { + struct wl_resource *resource; + struct wlr_output *output; + struct wlr_gamma_control_manager_v1 *manager; + struct wl_list link; + + uint16_t *table; + size_t ramp_size; + + struct wl_listener output_destroy_listener; + + void *data; +}; + +struct wlr_gamma_control_manager_v1 *wlr_gamma_control_manager_v1_create( + struct wl_display *display); +struct wlr_gamma_control_v1 *wlr_gamma_control_manager_v1_get_control( + struct wlr_gamma_control_manager_v1 *manager, struct wlr_output *output); +bool wlr_gamma_control_v1_apply(struct wlr_gamma_control_v1 *gamma_control, + struct wlr_output_state *output_state); +void wlr_gamma_control_v1_send_failed_and_destroy(struct wlr_gamma_control_v1 *gamma_control); + +#endif diff --git a/include/wlr/types/wlr_idle_inhibit_v1.h b/include/wlr/types/wlr_idle_inhibit_v1.h new file mode 100644 index 0000000..596fbe8 --- /dev/null +++ b/include/wlr/types/wlr_idle_inhibit_v1.h @@ -0,0 +1,56 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_IDLE_INHIBIT_V1_H +#define WLR_TYPES_WLR_IDLE_INHIBIT_V1_H + +#include + +/* This interface permits clients to inhibit the idle behavior such as + * screenblanking, locking, and screensaving. + * + * This allows clients to ensure they stay visible instead of being hidden by + * power-saving. + * + * Inhibitors are created for surfaces. They should only be in effect, while + * this surface is visible. + * The effect could also be limited to outputs it is displayed on (e.g. + * dimm/dpms off outputs, except the one a video is displayed on). + */ + +struct wlr_idle_inhibit_manager_v1 { + struct wl_list inhibitors; // wlr_idle_inhibit_inhibitor_v1.link + struct wl_global *global; + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_inhibitor; // struct wlr_idle_inhibitor_v1 + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_idle_inhibitor_v1 { + struct wlr_surface *surface; + struct wl_resource *resource; + struct wl_listener surface_destroy; + + struct wl_list link; // wlr_idle_inhibit_manager_v1.inhibitors + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_idle_inhibit_manager_v1 *wlr_idle_inhibit_v1_create(struct wl_display *display); + +#endif diff --git a/include/wlr/types/wlr_idle_notify_v1.h b/include/wlr/types/wlr_idle_notify_v1.h new file mode 100644 index 0000000..7bc11cb --- /dev/null +++ b/include/wlr/types/wlr_idle_notify_v1.h @@ -0,0 +1,44 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_IDLE_NOTIFY_H +#define WLR_TYPES_WLR_IDLE_NOTIFY_H + +#include + +struct wlr_seat; + +/** + * An idle notifier, implementing the ext-idle-notify-v1 protocol. + */ +struct wlr_idle_notifier_v1; + +/** + * Create the ext_idle_notifier_v1 global. + */ +struct wlr_idle_notifier_v1 *wlr_idle_notifier_v1_create(struct wl_display *display); + +/** + * Inhibit idle. + * + * Compositors should call this function when the idle state is disabled, e.g. + * because a visible client is using the idle-inhibit protocol. + */ +void wlr_idle_notifier_v1_set_inhibited(struct wlr_idle_notifier_v1 *notifier, + bool inhibited); + +/** + * Notify for user activity on a seat. + * + * Compositors should call this function whenever an input event is triggered + * on a seat. + */ +void wlr_idle_notifier_v1_notify_activity(struct wlr_idle_notifier_v1 *notifier, + struct wlr_seat *seat); + +#endif diff --git a/include/wlr/types/wlr_input_device.h b/include/wlr/types/wlr_input_device.h new file mode 100644 index 0000000..7f78cf2 --- /dev/null +++ b/include/wlr/types/wlr_input_device.h @@ -0,0 +1,51 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_INPUT_DEVICE_H +#define WLR_TYPES_WLR_INPUT_DEVICE_H + +#include + +enum wlr_button_state { + WLR_BUTTON_RELEASED, + WLR_BUTTON_PRESSED, +}; + +/** + * Type of an input device. + */ +enum wlr_input_device_type { + WLR_INPUT_DEVICE_KEYBOARD, // struct wlr_keyboard + WLR_INPUT_DEVICE_POINTER, // struct wlr_pointer + WLR_INPUT_DEVICE_TOUCH, // struct wlr_touch + WLR_INPUT_DEVICE_TABLET, // struct wlr_tablet + WLR_INPUT_DEVICE_TABLET_PAD, // struct wlr_tablet_pad + WLR_INPUT_DEVICE_SWITCH, // struct wlr_switch +}; + +/** + * An input device. + * + * Depending on its type, the input device can be converted to a more specific + * type. See the various wlr_*_from_input_device() functions. + * + * Input devices are typically advertised by the new_input event in + * struct wlr_backend. + */ +struct wlr_input_device { + enum wlr_input_device_type type; + char *name; // may be NULL + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +#endif diff --git a/include/wlr/types/wlr_input_method_v2.h b/include/wlr/types/wlr_input_method_v2.h new file mode 100644 index 0000000..fd299cc --- /dev/null +++ b/include/wlr/types/wlr_input_method_v2.h @@ -0,0 +1,144 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_INPUT_METHOD_V2_H +#define WLR_TYPES_WLR_INPUT_METHOD_V2_H +#include +#include +#include +#include +#include + +struct wlr_input_method_v2_preedit_string { + char *text; + int32_t cursor_begin; + int32_t cursor_end; +}; + +struct wlr_input_method_v2_delete_surrounding_text { + uint32_t before_length; + uint32_t after_length; +}; + +struct wlr_input_method_v2_state { + struct wlr_input_method_v2_preedit_string preedit; + char *commit_text; + struct wlr_input_method_v2_delete_surrounding_text delete; +}; + +struct wlr_input_method_v2 { + struct wl_resource *resource; + + struct wlr_seat *seat; + struct wlr_seat_client *seat_client; + + struct wlr_input_method_v2_state pending; + struct wlr_input_method_v2_state current; + bool active; // pending compositor-side state + bool client_active; // state known to the client + uint32_t current_serial; // received in last commit call + + struct wl_list popup_surfaces; + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab; + + struct wl_list link; + + struct wl_listener seat_client_destroy; + + struct { + struct wl_signal commit; // struct wlr_input_method_v2 + struct wl_signal new_popup_surface; // struct wlr_input_popup_surface_v2 + struct wl_signal grab_keyboard; // struct wlr_input_method_keyboard_grab_v2 + struct wl_signal destroy; // struct wlr_input_method_v2 + } events; +}; + +struct wlr_input_popup_surface_v2 { + struct wl_resource *resource; + struct wlr_input_method_v2 *input_method; + struct wl_list link; + + struct wlr_surface *surface; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_input_method_keyboard_grab_v2 { + struct wl_resource *resource; + struct wlr_input_method_v2 *input_method; + struct wlr_keyboard *keyboard; + + struct wl_listener keyboard_keymap; + struct wl_listener keyboard_repeat_info; + struct wl_listener keyboard_destroy; + + struct { + struct wl_signal destroy; // struct wlr_input_method_keyboard_grab_v2 + } events; +}; + +struct wlr_input_method_manager_v2 { + struct wl_global *global; + struct wl_list input_methods; // struct wlr_input_method_v2.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal input_method; // struct wlr_input_method_v2 + struct wl_signal destroy; // struct wlr_input_method_manager_v2 + } events; +}; + +struct wlr_input_method_manager_v2 *wlr_input_method_manager_v2_create( + struct wl_display *display); + +void wlr_input_method_v2_send_activate( + struct wlr_input_method_v2 *input_method); +void wlr_input_method_v2_send_deactivate( + struct wlr_input_method_v2 *input_method); +void wlr_input_method_v2_send_surrounding_text( + struct wlr_input_method_v2 *input_method, const char *text, + uint32_t cursor, uint32_t anchor); +void wlr_input_method_v2_send_content_type( + struct wlr_input_method_v2 *input_method, uint32_t hint, + uint32_t purpose); +void wlr_input_method_v2_send_text_change_cause( + struct wlr_input_method_v2 *input_method, uint32_t cause); +void wlr_input_method_v2_send_done(struct wlr_input_method_v2 *input_method); +void wlr_input_method_v2_send_unavailable( + struct wlr_input_method_v2 *input_method); + +/** + * Get a struct wlr_input_popup_surface_v2 from a struct wlr_surface. + * + * Returns NULL if the surface has a different role or if the input popup + * surface has been destroyed. + */ +struct wlr_input_popup_surface_v2 *wlr_input_popup_surface_v2_try_from_wlr_surface( + struct wlr_surface *surface); + +void wlr_input_popup_surface_v2_send_text_input_rectangle( + struct wlr_input_popup_surface_v2 *popup_surface, struct wlr_box *sbox); + +void wlr_input_method_keyboard_grab_v2_send_key( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + uint32_t time, uint32_t key, uint32_t state); +void wlr_input_method_keyboard_grab_v2_send_modifiers( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + struct wlr_keyboard_modifiers *modifiers); +void wlr_input_method_keyboard_grab_v2_set_keyboard( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + struct wlr_keyboard *keyboard); +void wlr_input_method_keyboard_grab_v2_destroy( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab); + +#endif diff --git a/include/wlr/types/wlr_keyboard.h b/include/wlr/types/wlr_keyboard.h new file mode 100644 index 0000000..9046493 --- /dev/null +++ b/include/wlr/types/wlr_keyboard.h @@ -0,0 +1,142 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_KEYBOARD_H +#define WLR_TYPES_WLR_KEYBOARD_H + +#include +#include +#include +#include +#include +#include + +#define WLR_LED_COUNT 3 + +enum wlr_keyboard_led { + WLR_LED_NUM_LOCK = 1 << 0, + WLR_LED_CAPS_LOCK = 1 << 1, + WLR_LED_SCROLL_LOCK = 1 << 2, +}; + +#define WLR_MODIFIER_COUNT 8 + +enum wlr_keyboard_modifier { + WLR_MODIFIER_SHIFT = 1 << 0, + WLR_MODIFIER_CAPS = 1 << 1, + WLR_MODIFIER_CTRL = 1 << 2, + WLR_MODIFIER_ALT = 1 << 3, + WLR_MODIFIER_MOD2 = 1 << 4, + WLR_MODIFIER_MOD3 = 1 << 5, + WLR_MODIFIER_LOGO = 1 << 6, + WLR_MODIFIER_MOD5 = 1 << 7, +}; + +#define WLR_KEYBOARD_KEYS_CAP 32 + +struct wlr_keyboard_impl; + +struct wlr_keyboard_modifiers { + xkb_mod_mask_t depressed; + xkb_mod_mask_t latched; + xkb_mod_mask_t locked; + xkb_layout_index_t group; +}; + +struct wlr_keyboard { + struct wlr_input_device base; + + const struct wlr_keyboard_impl *impl; + struct wlr_keyboard_group *group; + + char *keymap_string; + size_t keymap_size; + int keymap_fd; + struct xkb_keymap *keymap; + struct xkb_state *xkb_state; + xkb_led_index_t led_indexes[WLR_LED_COUNT]; + xkb_mod_index_t mod_indexes[WLR_MODIFIER_COUNT]; + + uint32_t leds; + uint32_t keycodes[WLR_KEYBOARD_KEYS_CAP]; + size_t num_keycodes; + struct wlr_keyboard_modifiers modifiers; + + struct { + int32_t rate; + int32_t delay; + } repeat_info; + + struct { + /** + * The `key` event signals with a struct wlr_keyboard_key_event that a + * key has been pressed or released on the keyboard. This event is + * emitted before the xkb state of the keyboard has been updated + * (including modifiers). + */ + struct wl_signal key; + + /** + * The `modifiers` event signals that the modifier state of the + * struct wlr_keyboard has been updated. At this time, you can read the + * modifier state of the struct wlr_keyboard and handle the updated + * state by sending it to clients. + */ + struct wl_signal modifiers; + struct wl_signal keymap; + struct wl_signal repeat_info; + } events; + + void *data; +}; + +struct wlr_keyboard_key_event { + uint32_t time_msec; + uint32_t keycode; + bool update_state; // if backend doesn't update modifiers on its own + enum wl_keyboard_key_state state; +}; + +/** + * Get a struct wlr_keyboard from a struct wlr_input_device. + * + * Asserts that the input device is a keyboard. + */ +struct wlr_keyboard *wlr_keyboard_from_input_device( + struct wlr_input_device *input_device); + +bool wlr_keyboard_set_keymap(struct wlr_keyboard *kb, + struct xkb_keymap *keymap); + +bool wlr_keyboard_keymaps_match(struct xkb_keymap *km1, struct xkb_keymap *km2); + +/** + * Set the keyboard repeat info. + * + * rate is in key repeats/second and delay is in milliseconds. + */ +void wlr_keyboard_set_repeat_info(struct wlr_keyboard *kb, int32_t rate_hz, + int32_t delay_ms); + +/** + * Update the LEDs on the device, if any. + * + * leds is a bitmask of enum wlr_keyboard_led. + * + * If the device doesn't have the provided LEDs, this function is a no-op. + */ +void wlr_keyboard_led_update(struct wlr_keyboard *keyboard, uint32_t leds); + +/** + * Get the set of currently depressed or latched modifiers. + * + * A bitmask of enum wlr_keyboard_modifier is returned. + */ +uint32_t wlr_keyboard_get_modifiers(struct wlr_keyboard *keyboard); + +#endif diff --git a/include/wlr/types/wlr_keyboard_group.h b/include/wlr/types/wlr_keyboard_group.h new file mode 100644 index 0000000..0d5c6a2 --- /dev/null +++ b/include/wlr/types/wlr_keyboard_group.h @@ -0,0 +1,59 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_KEYBOARD_GROUP_H +#define WLR_TYPES_WLR_KEYBOARD_GROUP_H + +#include +#include + +struct wlr_keyboard_group { + struct wlr_keyboard keyboard; + struct wl_list devices; // keyboard_group_device.link + struct wl_list keys; // keyboard_group_key.link + + struct { + /** + * Sent when a keyboard has entered the group with keys currently + * pressed that are not pressed by any other keyboard in the group. The + * data for this signal will be a struct wl_array containing the key + * codes. This should be used to update the compositor's internal state. + * Bindings should not be triggered based off of these key codes and + * they should also not notify any surfaces of the key press. + */ + struct wl_signal enter; + + /** + * Sent when a keyboard has left the group with keys currently pressed + * that are not pressed by any other keyboard in the group. The data for + * this signal will be a struct wl_array containing the key codes. This + * should be used to update the compositor's internal state. Bindings + * should not be triggered based off of these key codes. Additionally, + * surfaces should only be notified if they received a corresponding key + * press for the key code. + */ + struct wl_signal leave; + } events; + + void *data; +}; + +struct wlr_keyboard_group *wlr_keyboard_group_create(void); + +struct wlr_keyboard_group *wlr_keyboard_group_from_wlr_keyboard( + struct wlr_keyboard *keyboard); + +bool wlr_keyboard_group_add_keyboard(struct wlr_keyboard_group *group, + struct wlr_keyboard *keyboard); + +void wlr_keyboard_group_remove_keyboard(struct wlr_keyboard_group *group, + struct wlr_keyboard *keyboard); + +void wlr_keyboard_group_destroy(struct wlr_keyboard_group *group); + +#endif diff --git a/include/wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h b/include/wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h new file mode 100644 index 0000000..946e75f --- /dev/null +++ b/include/wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h @@ -0,0 +1,85 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_KEYBOARD_SHORTCUTS_INHIBIT_V1_H +#define WLR_TYPES_WLR_KEYBOARD_SHORTCUTS_INHIBIT_V1_H + +#include +#include + +/* This interface permits clients to inhibit keyboard shortcut processing by + * the compositor. + * + * This allows clients to pass them on to e.g. remote desktops or virtual + * machine guests. + * + * Inhibitors are created for surfaces and seats. They should only be in effect + * while this surface has focus. + */ + +struct wlr_keyboard_shortcuts_inhibit_manager_v1 { + // wlr_keyboard_shortcuts_inhibitor_v1.link + struct wl_list inhibitors; + struct wl_global *global; + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_inhibitor; // struct wlr_keyboard_shortcuts_inhibitor_v1 + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_keyboard_shortcuts_inhibitor_v1 { + struct wlr_surface *surface; + struct wlr_seat *seat; + bool active; + struct wl_resource *resource; + + struct wl_listener surface_destroy; + struct wl_listener seat_destroy; + + // wlr_keyboard_shortcuts_inhibit_manager_v1.inhibitors + struct wl_list link; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +/* + * A compositor creating a manager will handle the new_inhibitor event and call + * wlr_keyboard_shortcuts_inhibitor_v1_activate() if it decides to honour the + * inhibitor. This will send the active event to the client, confirming + * activation of the inhibitor. From then on the compositor should respect the + * inhibitor until it calls wlr_keyboard_shortcuts_inhibitor_v1_deactivate() to + * suspend the inhibitor with an inactive event to the client or receives the + * destroy signal from wlroots, telling it that the inhibitor has been + * destroyed. + * + * Not sending the active event to the client is the only way under the + * protocol to let the client know that the compositor will not be honouring an + * inhibitor. It's the client's job to somehow deal with not receiving the + * event, i.e. not assume that shortcuts are inhibited and maybe destroy the + * pending and request a new inhibitor after a timeout. + */ + +struct wlr_keyboard_shortcuts_inhibit_manager_v1 * +wlr_keyboard_shortcuts_inhibit_v1_create(struct wl_display *display); + +void wlr_keyboard_shortcuts_inhibitor_v1_activate( + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor); + +void wlr_keyboard_shortcuts_inhibitor_v1_deactivate( + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor); + +#endif diff --git a/include/wlr/types/wlr_layer_shell_v1.h b/include/wlr/types/wlr_layer_shell_v1.h new file mode 100644 index 0000000..eaf6af4 --- /dev/null +++ b/include/wlr/types/wlr_layer_shell_v1.h @@ -0,0 +1,188 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_LAYER_SHELL_V1_H +#define WLR_TYPES_WLR_LAYER_SHELL_V1_H + +#include +#include +#include +#include +#include "wlr-layer-shell-unstable-v1-protocol.h" + +/** + * wlr_layer_shell_v1 allows clients to arrange themselves in "layers" on the + * desktop in accordance with the wlr-layer-shell protocol. When a client is + * added, the new_surface signal will be raised and passed a reference to our + * struct wlr_layer_surface_v1. At this time, the client will have configured the + * surface as it desires, including information like desired anchors and + * margins. The compositor should use this information to decide how to arrange + * the layer on-screen, then determine the dimensions of the layer and call + * wlr_layer_surface_v1_configure(). The client will then attach a buffer and + * commit the surface, at which point the wlr_layer_surface_v1 map signal is + * raised and the compositor should begin rendering the surface. + */ +struct wlr_layer_shell_v1 { + struct wl_global *global; + + struct wl_listener display_destroy; + + struct { + // Note: the output may be NULL. In this case, it is your + // responsibility to assign an output before returning. + struct wl_signal new_surface; // struct wlr_layer_surface_v1 + struct wl_signal destroy; + } events; + + void *data; +}; + +enum wlr_layer_surface_v1_state_field { + WLR_LAYER_SURFACE_V1_STATE_DESIRED_SIZE = 1 << 0, + WLR_LAYER_SURFACE_V1_STATE_ANCHOR = 1 << 1, + WLR_LAYER_SURFACE_V1_STATE_EXCLUSIVE_ZONE = 1 << 2, + WLR_LAYER_SURFACE_V1_STATE_MARGIN = 1 << 3, + WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY = 1 << 4, + WLR_LAYER_SURFACE_V1_STATE_LAYER = 1 << 5, +}; + +struct wlr_layer_surface_v1_state { + uint32_t committed; // enum wlr_layer_surface_v1_state_field + + uint32_t anchor; + int32_t exclusive_zone; + struct { + int32_t top, right, bottom, left; + } margin; + enum zwlr_layer_surface_v1_keyboard_interactivity keyboard_interactive; + uint32_t desired_width, desired_height; + enum zwlr_layer_shell_v1_layer layer; + + uint32_t configure_serial; + uint32_t actual_width, actual_height; +}; + +struct wlr_layer_surface_v1_configure { + struct wl_list link; // wlr_layer_surface_v1.configure_list + uint32_t serial; + + uint32_t width, height; +}; + +struct wlr_layer_surface_v1 { + struct wlr_surface *surface; + struct wlr_output *output; + struct wl_resource *resource; + struct wlr_layer_shell_v1 *shell; + struct wl_list popups; // wlr_xdg_popup.link + + char *namespace; + + bool configured; + struct wl_list configure_list; + + struct wlr_layer_surface_v1_state current, pending; + + // Whether the surface is ready to receive configure events + bool initialized; + // Whether the latest commit is an initial commit + bool initial_commit; + + struct { + /** + * The destroy signal indicates that the struct wlr_layer_surface is + * about to be freed. It is guaranteed that the unmap signal is raised + * before the destroy signal if the layer surface is destroyed while + * mapped. + */ + struct wl_signal destroy; + /** + * The new_popup signal is raised when a new popup is created. The data + * parameter passed to the listener is a pointer to the new + * struct wlr_xdg_popup. + */ + struct wl_signal new_popup; + } events; + + void *data; + + // private state + + struct wlr_surface_synced synced; +}; + +struct wlr_layer_shell_v1 *wlr_layer_shell_v1_create(struct wl_display *display, + uint32_t version); + +/** + * Notifies the layer surface to configure itself with this width/height. The + * layer_surface will signal its map event when the surface is ready to assume + * this size. Returns the associated configure serial. + */ +uint32_t wlr_layer_surface_v1_configure(struct wlr_layer_surface_v1 *surface, + uint32_t width, uint32_t height); + +/** + * Notify the client that the surface has been closed and destroy the + * struct wlr_layer_surface_v1, rendering the resource inert. + */ +void wlr_layer_surface_v1_destroy(struct wlr_layer_surface_v1 *surface); + +/** + * Get a struct wlr_layer_surface from a struct wlr_surface. + * + * Returns NULL if the surface doesn't have the layer surface role or if + * the layer surface has been destroyed. + */ +struct wlr_layer_surface_v1 *wlr_layer_surface_v1_try_from_wlr_surface( + struct wlr_surface *surface); + +/** + * Calls the iterator function for each mapped sub-surface and popup of this + * surface (whether or not this surface is mapped). + */ +void wlr_layer_surface_v1_for_each_surface(struct wlr_layer_surface_v1 *surface, + wlr_surface_iterator_func_t iterator, void *user_data); + +/** + * Call `iterator` on each popup's surface and popup's subsurface in the + * layer surface's tree, with the surfaces's position relative to the root + * layer surface. The function is called from root to leaves (in rendering + * order). + */ +void wlr_layer_surface_v1_for_each_popup_surface( + struct wlr_layer_surface_v1 *surface, + wlr_surface_iterator_func_t iterator, void *user_data); + +/** + * Find a surface within this layer-surface tree at the given surface-local + * coordinates. Returns the surface and coordinates in the leaf surface + * coordinate system or NULL if no surface is found at that location. + */ +struct wlr_surface *wlr_layer_surface_v1_surface_at( + struct wlr_layer_surface_v1 *surface, double sx, double sy, + double *sub_x, double *sub_y); + +/** + * Find a surface within this layer-surface's popup tree at the given + * surface-local coordinates. Returns the surface and coordinates in the leaf + * surface coordinate system or NULL if no surface is found at that location. + */ +struct wlr_surface *wlr_layer_surface_v1_popup_surface_at( + struct wlr_layer_surface_v1 *surface, double sx, double sy, + double *sub_x, double *sub_y); + +/** + * Get the corresponding struct wlr_layer_surface_v1 from a resource. + * + * Aborts if the resource doesn't have the correct type. + */ +struct wlr_layer_surface_v1 *wlr_layer_surface_v1_from_resource( + struct wl_resource *resource); + +#endif diff --git a/include/wlr/types/wlr_linux_dmabuf_v1.h b/include/wlr/types/wlr_linux_dmabuf_v1.h new file mode 100644 index 0000000..cf967f9 --- /dev/null +++ b/include/wlr/types/wlr_linux_dmabuf_v1.h @@ -0,0 +1,137 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_LINUX_DMABUF_V1_H +#define WLR_TYPES_WLR_LINUX_DMABUF_V1_H + +#include +#include +#include +#include +#include +#include + +struct wlr_surface; + +struct wlr_dmabuf_v1_buffer { + struct wlr_buffer base; + + struct wl_resource *resource; // can be NULL if the client destroyed it + struct wlr_dmabuf_attributes attributes; + + // private state + + struct wl_listener release; +}; + +/** + * Returns the struct wlr_dmabuf_buffer if the given resource was created + * via the linux-dmabuf buffer protocol or NULL otherwise. + */ +struct wlr_dmabuf_v1_buffer *wlr_dmabuf_v1_buffer_try_from_buffer_resource( + struct wl_resource *buffer_resource); + +struct wlr_linux_dmabuf_feedback_v1 { + dev_t main_device; + struct wl_array tranches; // struct wlr_linux_dmabuf_feedback_v1_tranche +}; + +struct wlr_linux_dmabuf_feedback_v1_tranche { + dev_t target_device; + uint32_t flags; // bitfield of enum zwp_linux_dmabuf_feedback_v1_tranche_flags + struct wlr_drm_format_set formats; +}; + +/* the protocol interface */ +struct wlr_linux_dmabuf_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + } events; + + // private state + + struct wlr_linux_dmabuf_feedback_v1_compiled *default_feedback; + struct wlr_drm_format_set default_formats; // for legacy clients + struct wl_list surfaces; // wlr_linux_dmabuf_v1_surface.link + + int main_device_fd; // to sanity check FDs sent by clients, -1 if unavailable + + struct wl_listener display_destroy; + + bool (*check_dmabuf_callback)(struct wlr_dmabuf_attributes *attribs, void *data); + void *check_dmabuf_callback_data; +}; + +/** + * Create the linux-dmabuf-v1 global. + * + * Compositors using struct wlr_renderer should use + * wlr_linux_dmabuf_v1_create_with_renderer() instead. + */ +struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_create(struct wl_display *display, + uint32_t version, const struct wlr_linux_dmabuf_feedback_v1 *default_feedback); + +/** + * Create the linux-dmabuf-v1 global. + * + * The default DMA-BUF feedback is initialized from the struct wlr_renderer. + */ +struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_create_with_renderer(struct wl_display *display, + uint32_t version, struct wlr_renderer *renderer); + +/** + * Set the dmabuf import check callback + * + * This can be used by consumers who want to implement specific dmabuf checks. Useful for + * users such as gamescope who do not use the rendering logic of wlroots. + */ +void wlr_linux_dmabuf_v1_set_check_dmabuf_callback(struct wlr_linux_dmabuf_v1 *linux_dmabuf, + bool (*callback)(struct wlr_dmabuf_attributes *attribs, void *data), void *data); + +/** + * Set a surface's DMA-BUF feedback. + * + * Passing a NULL feedback resets it to the default feedback. + */ +bool wlr_linux_dmabuf_v1_set_surface_feedback( + struct wlr_linux_dmabuf_v1 *linux_dmabuf, struct wlr_surface *surface, + const struct wlr_linux_dmabuf_feedback_v1 *feedback); + +/** + * Append a tranche at the end of the DMA-BUF feedback list. + * + * Tranches must be added with decreasing priority. + */ +struct wlr_linux_dmabuf_feedback_v1_tranche *wlr_linux_dmabuf_feedback_add_tranche( + struct wlr_linux_dmabuf_feedback_v1 *feedback); + +/** + * Release resources allocated by a DMA-BUF feedback object. + */ +void wlr_linux_dmabuf_feedback_v1_finish(struct wlr_linux_dmabuf_feedback_v1 *feedback); + +struct wlr_linux_dmabuf_feedback_v1_init_options { + // Main renderer used by the compositor + struct wlr_renderer *main_renderer; + // Output on which direct scan-out is possible on the primary plane, or NULL + struct wlr_output *scanout_primary_output; + // Output layer feedback event, or NULL + const struct wlr_output_layer_feedback_event *output_layer_feedback_event; +}; + +/** + * Initialize a DMA-BUF feedback object with the provided options. + * + * The caller is responsible for calling wlr_linux_dmabuf_feedback_v1_finish() after use. + */ +bool wlr_linux_dmabuf_feedback_v1_init_with_options(struct wlr_linux_dmabuf_feedback_v1 *feedback, + const struct wlr_linux_dmabuf_feedback_v1_init_options *options); + +#endif diff --git a/include/wlr/types/wlr_matrix.h b/include/wlr/types/wlr_matrix.h new file mode 100644 index 0000000..674cb53 --- /dev/null +++ b/include/wlr/types/wlr_matrix.h @@ -0,0 +1,44 @@ +/* + * This is a deprecated interface of wlroots. It will be removed in a future + * version. + */ + +#ifndef WLR_TYPES_WLR_MATRIX_H +#define WLR_TYPES_WLR_MATRIX_H + +#include + +struct wlr_box; + +/** Writes the identity matrix into mat */ +void wlr_matrix_identity(float mat[static 9]); + +/** mat ← a × b */ +void wlr_matrix_multiply(float mat[static 9], const float a[static 9], + const float b[static 9]); + +void wlr_matrix_transpose(float mat[static 9], const float a[static 9]); + +/** Writes a 2D translation matrix to mat of magnitude (x, y) */ +void wlr_matrix_translate(float mat[static 9], float x, float y); + +/** Writes a 2D scale matrix to mat of magnitude (x, y) */ +void wlr_matrix_scale(float mat[static 9], float x, float y); + +/** Writes a 2D rotation matrix to mat at an angle of rad radians */ +void wlr_matrix_rotate(float mat[static 9], float rad); + +/** Writes a transformation matrix which applies the specified + * wl_output_transform to mat */ +void wlr_matrix_transform(float mat[static 9], + enum wl_output_transform transform); + +/** Shortcut for the various matrix operations involved in projecting the + * specified wlr_box onto a given orthographic projection with a given + * rotation. The result is written to mat, which can be applied to each + * coordinate of the box to get a new coordinate from [-1,1]. */ +void wlr_matrix_project_box(float mat[static 9], const struct wlr_box *box, + enum wl_output_transform transform, float rotation, + const float projection[static 9]); + +#endif diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h new file mode 100644 index 0000000..865bc5e --- /dev/null +++ b/include/wlr/types/wlr_output.h @@ -0,0 +1,583 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_OUTPUT_H +#define WLR_TYPES_WLR_OUTPUT_H + +#include +#include +#include +#include +#include +#include +#include +#include + +enum wlr_output_mode_aspect_ratio { + WLR_OUTPUT_MODE_ASPECT_RATIO_NONE, + WLR_OUTPUT_MODE_ASPECT_RATIO_4_3, + WLR_OUTPUT_MODE_ASPECT_RATIO_16_9, + WLR_OUTPUT_MODE_ASPECT_RATIO_64_27, + WLR_OUTPUT_MODE_ASPECT_RATIO_256_135, +}; + +struct wlr_output_mode { + int32_t width, height; + int32_t refresh; // mHz + bool preferred; + enum wlr_output_mode_aspect_ratio picture_aspect_ratio; + struct wl_list link; +}; + +struct wlr_output_cursor { + struct wlr_output *output; + double x, y; + bool enabled; + bool visible; + uint32_t width, height; + struct wlr_fbox src_box; + enum wl_output_transform transform; + int32_t hotspot_x, hotspot_y; + struct wlr_texture *texture; + bool own_texture; + struct wl_list link; +}; + +enum wlr_output_adaptive_sync_status { + WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED, + WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED, +}; + +enum wlr_output_state_field { + WLR_OUTPUT_STATE_BUFFER = 1 << 0, + WLR_OUTPUT_STATE_DAMAGE = 1 << 1, + WLR_OUTPUT_STATE_MODE = 1 << 2, + WLR_OUTPUT_STATE_ENABLED = 1 << 3, + WLR_OUTPUT_STATE_SCALE = 1 << 4, + WLR_OUTPUT_STATE_TRANSFORM = 1 << 5, + WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED = 1 << 6, + WLR_OUTPUT_STATE_GAMMA_LUT = 1 << 7, + WLR_OUTPUT_STATE_RENDER_FORMAT = 1 << 8, + WLR_OUTPUT_STATE_SUBPIXEL = 1 << 9, + WLR_OUTPUT_STATE_LAYERS = 1 << 10, +}; + +enum wlr_output_state_mode_type { + WLR_OUTPUT_STATE_MODE_FIXED, + WLR_OUTPUT_STATE_MODE_CUSTOM, +}; + +/** + * Holds the double-buffered output state. + */ +struct wlr_output_state { + uint32_t committed; // enum wlr_output_state_field + // Set to true to allow output reconfiguration to occur which may result + // in temporary output disruptions and content misrepresentations. + bool allow_reconfiguration; + pixman_region32_t damage; // output-buffer-local coordinates + bool enabled; + float scale; + enum wl_output_transform transform; + bool adaptive_sync_enabled; + uint32_t render_format; + enum wl_output_subpixel subpixel; + + struct wlr_buffer *buffer; + /* Request a tearing page-flip. When enabled, this may cause the output to + * display a part of the previous buffer and a part of the current buffer at + * the same time. The backend may reject the commit if a tearing page-flip + * cannot be performed, in which case the caller should fall back to a + * regular page-flip at the next wlr_output.frame event. */ + bool tearing_page_flip; + + enum wlr_output_state_mode_type mode_type; + struct wlr_output_mode *mode; + struct { + int32_t width, height; + int32_t refresh; // mHz, may be zero + } custom_mode; + + uint16_t *gamma_lut; + size_t gamma_lut_size; + + struct wlr_output_layer_state *layers; + size_t layers_len; +}; + +struct wlr_output_impl; +struct wlr_render_pass; + +/** + * A compositor output region. This typically corresponds to a monitor that + * displays part of the compositor space. + * + * The `frame` event will be emitted when it is a good time for the compositor + * to submit a new frame. + * + * To render a new frame, compositors should call wlr_output_begin_render_pass(), + * perform rendering on that render pass and finally call wlr_output_commit(). + */ +struct wlr_output { + const struct wlr_output_impl *impl; + struct wlr_backend *backend; + struct wl_event_loop *event_loop; + + struct wl_global *global; + struct wl_list resources; + + char *name; + char *description; // may be NULL + char *make, *model, *serial; // may be NULL + int32_t phys_width, phys_height; // mm + + // Note: some backends may have zero modes + struct wl_list modes; // wlr_output_mode.link + struct wlr_output_mode *current_mode; + int32_t width, height; + int32_t refresh; // mHz, may be zero + + bool enabled; + float scale; + enum wl_output_subpixel subpixel; + enum wl_output_transform transform; + enum wlr_output_adaptive_sync_status adaptive_sync_status; + uint32_t render_format; + + bool needs_frame; + // damage for cursors and fullscreen surface, in output-local coordinates + bool frame_pending; + + // true for example with VR headsets + bool non_desktop; + + // Commit sequence number. Incremented on each commit, may overflow. + uint32_t commit_seq; + + struct { + // Request to render a frame + struct wl_signal frame; + // Emitted when software cursors or backend-specific logic damage the + // output + struct wl_signal damage; // struct wlr_output_event_damage + // Emitted when a new frame needs to be committed (because of + // backend-specific logic) + struct wl_signal needs_frame; + // Emitted right before commit + struct wl_signal precommit; // struct wlr_output_event_precommit + // Emitted right after commit + struct wl_signal commit; // struct wlr_output_event_commit + // Emitted right after a commit has been presented to the user for + // enabled outputs + struct wl_signal present; // struct wlr_output_event_present + // Emitted after a client bound the wl_output global + struct wl_signal bind; // struct wlr_output_event_bind + struct wl_signal description; + struct wl_signal request_state; // struct wlr_output_event_request_state + struct wl_signal destroy; + } events; + + struct wl_event_source *idle_frame; + struct wl_event_source *idle_done; + + int attach_render_locks; // number of locks forcing rendering + + struct wl_list cursors; // wlr_output_cursor.link + struct wlr_output_cursor *hardware_cursor; + struct wlr_swapchain *cursor_swapchain; + struct wlr_buffer *cursor_front_buffer; + int software_cursor_locks; // number of locks forcing software cursors + + struct wl_list layers; // wlr_output_layer.link + + struct wlr_allocator *allocator; + struct wlr_renderer *renderer; + struct wlr_swapchain *swapchain; + + struct wl_listener display_destroy; + + struct wlr_addon_set addons; + + void *data; +}; + +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 { + // The presentation was synchronized to the "vertical retrace" by the + // display hardware such that tearing does not happen. + WLR_OUTPUT_PRESENT_VSYNC = 0x1, + // The display hardware provided measurements that the hardware driver + // converted into a presentation timestamp. + WLR_OUTPUT_PRESENT_HW_CLOCK = 0x2, + // The display hardware signalled that it started using the new image + // content. + WLR_OUTPUT_PRESENT_HW_COMPLETION = 0x4, + // The presentation of this update was done zero-copy. + WLR_OUTPUT_PRESENT_ZERO_COPY = 0x8, +}; + +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; +}; + +struct wlr_surface; + +void wlr_output_create_global(struct wlr_output *output, struct wl_display *display); +void wlr_output_destroy_global(struct wlr_output *output); +/** + * Initialize the output's rendering subsystem with the provided allocator and + * renderer. After initialization, this function may invoked again to reinitialize + * the allocator and renderer to different values. + * + * Call this function prior to any call to wlr_output_begin_render_pass(), + * wlr_output_commit() or wlr_output_cursor_create(). + * + * The buffer capabilities of the provided must match the capabilities of the + * output's backend. Returns false otherwise. + */ +bool wlr_output_init_render(struct wlr_output *output, + struct wlr_allocator *allocator, struct wlr_renderer *renderer); +/** + * Returns the preferred mode for this output. If the output doesn't support + * modes, returns NULL. + */ +struct wlr_output_mode *wlr_output_preferred_mode(struct wlr_output *output); +/** + * Set the output name. + * + * Output names are subject to the following rules: + * + * - Each output name must be unique. + * - The name cannot change after the output has been advertised to clients. + * + * For more details, see the protocol documentation for wl_output.name. + */ +void wlr_output_set_name(struct wlr_output *output, const char *name); +void wlr_output_set_description(struct wlr_output *output, const char *desc); +/** + * Schedule a done event. + * + * This is intended to be used by wl_output add-on interfaces. + */ +void wlr_output_schedule_done(struct wlr_output *output); +void wlr_output_destroy(struct wlr_output *output); +/** + * Computes the transformed output resolution. + */ +void wlr_output_transformed_resolution(struct wlr_output *output, + int *width, int *height); +/** + * Computes the transformed and scaled output resolution. + */ +void wlr_output_effective_resolution(struct wlr_output *output, + int *width, int *height); +/** + * Test whether this output state would be accepted by the backend. If this + * function returns true, wlr_output_commit_state() will only fail due to a + * runtime error. This function does not change the current state of the + * output. + */ +bool wlr_output_test_state(struct wlr_output *output, + const struct wlr_output_state *state); +/** + * Attempts to apply the state to this output. This function may fail for any + * reason and return false. If failed, none of the state would have been applied, + * this function is atomic. If the commit succeeded, true is returned. + * + * Note: wlr_output_state_finish() would typically be called after the state + * has been committed. + */ +bool wlr_output_commit_state(struct wlr_output *output, + const struct wlr_output_state *state); +/** + * Manually schedules a `frame` event. If a `frame` event is already pending, + * it is a no-op. + */ +void wlr_output_schedule_frame(struct wlr_output *output); +/** + * Returns the maximum length of each gamma ramp, or 0 if unsupported. + */ +size_t wlr_output_get_gamma_size(struct wlr_output *output); +/** + * Returns the wlr_output matching the provided wl_output resource. If the + * resource isn't a wl_output, it aborts. If the resource is inert (because the + * wlr_output has been destroyed), NULL is returned. + */ +struct wlr_output *wlr_output_from_resource(struct wl_resource *resource); +/** + * Locks the output to only use rendering instead of direct scan-out. This is + * useful if direct scan-out needs to be temporarily disabled (e.g. during + * screen capture). There must be as many unlocks as there have been locks to + * restore the original state. There should never be an unlock before a lock. + */ +void wlr_output_lock_attach_render(struct wlr_output *output, bool lock); +/** + * Locks the output to only use software cursors instead of hardware cursors. + * This is useful if hardware cursors need to be temporarily disabled (e.g. + * during screen capture). There must be as many unlocks as there have been + * locks to restore the original state. There should never be an unlock before + * a lock. + */ +void wlr_output_lock_software_cursors(struct wlr_output *output, bool lock); +/** + * Renders software cursors. This is a utility function that can be called when + * compositors render. + */ +void wlr_output_render_software_cursors(struct wlr_output *output, + const pixman_region32_t *damage); +/** + * Render software cursors. + * + * This is a utility function that can be called when compositors render. + */ +void wlr_output_add_software_cursors_to_render_pass(struct wlr_output *output, + struct wlr_render_pass *render_pass, const pixman_region32_t *damage); +/** + * Get the set of DRM formats suitable for the primary buffer, assuming a + * buffer with the specified capabilities. + * + * NULL is returned if the backend doesn't have any format constraint, ie. all + * formats are supported. An empty set is returned if the backend doesn't + * support any format. + */ +const struct wlr_drm_format_set *wlr_output_get_primary_formats( + struct wlr_output *output, uint32_t buffer_caps); +/** + * Check whether direct scan-out is allowed on the output. + * + * Direct scan-out is typically disallowed when there are software cursors or + * during screen capture. + */ +bool wlr_output_is_direct_scanout_allowed(struct wlr_output *output); + + +struct wlr_output_cursor *wlr_output_cursor_create(struct wlr_output *output); +bool wlr_output_cursor_set_buffer(struct wlr_output_cursor *cursor, + struct wlr_buffer *buffer, int32_t hotspot_x, int32_t hotspot_y); +bool wlr_output_cursor_move(struct wlr_output_cursor *cursor, + double x, double y); +void wlr_output_cursor_destroy(struct wlr_output_cursor *cursor); + +/** + * Initialize an output state. + */ +void wlr_output_state_init(struct wlr_output_state *state); +/** + * Releases all resources associated with an output state. + */ +void wlr_output_state_finish(struct wlr_output_state *state); +/** + * Enables or disables an output. A disabled output is turned off and doesn't + * emit `frame` events. + * + * This state will be applied once wlr_output_commit_state() is called. + */ +void wlr_output_state_set_enabled(struct wlr_output_state *state, + bool enabled); +/** + * Sets the output mode of an output. An output mode will specify the resolution + * and refresh rate, among other things. + * + * This state will be applied once wlr_output_commit_state() is called. + */ +void wlr_output_state_set_mode(struct wlr_output_state *state, + struct wlr_output_mode *mode); +/** + * Sets a custom output mode for an output. See wlr_output_state_set_mode() + * for details. + * + * When the output advertises fixed modes, custom modes are not guaranteed to + * work correctly, they may result in visual artifacts. If a suitable fixed mode + * is available, compositors should prefer it and use wlr_output_state_set_mode() + * instead of custom modes. + * + * Setting `refresh` to zero lets the backend pick a preferred value. The + * output needs to be enabled. + * + * This state will be applied once wlr_output_commit_state() is called. + */ +void wlr_output_state_set_custom_mode(struct wlr_output_state *state, + int32_t width, int32_t height, int32_t refresh); +/** + * Sets the scale of an output. The scale is used to increase the size of UI + * elements to aid users who use high DPI outputs. + * + * This state will be applied once wlr_output_commit_state() is called. + */ +void wlr_output_state_set_scale(struct wlr_output_state *state, float scale); +/** + * Sets the transform of an output. The transform is used to rotate or flip + * the contents of the screen. + * + * This state will be applied once wlr_output_commit_state() is called. + */ +void wlr_output_state_set_transform(struct wlr_output_state *state, + enum wl_output_transform transform); +/** + * Enables or disable adaptive sync for an output (ie. variable refresh rate). + * Compositors can inspect `wlr_output.adaptive_sync_status` to query the + * effective status. Backends that don't support adaptive sync will reject the + * output commit. + * + * This state will be applied once wlr_output_commit_state() is called. + */ +void wlr_output_state_set_adaptive_sync_enabled(struct wlr_output_state *state, + bool enabled); +/** + * Sets the render format for an output. + * + * The default value is DRM_FORMAT_XRGB8888. + * + * While high bit depth render formats are necessary for a monitor to receive + * useful high bit data, they do not guarantee it; a DRM_FORMAT_XBGR2101010 + * buffer will only lead to sending 10-bpc image data to the monitor if + * hardware and software permit this. + * + * This only affects the format of the output buffer used when rendering using + * the output's own swapchain as with wlr_output_begin_render_pass(). It has no + * impact on the cursor buffer format, or on the formats supported for direct + * scan-out (see also wlr_output_state_set_buffer()). + * + * This state will be applied once wlr_output_commit_state() is called. + */ +void wlr_output_state_set_render_format(struct wlr_output_state *state, + uint32_t format); +/** + * Sets the subpixel layout hint for an output. Note that this is only a hint + * and may be ignored. The default value depends on the backend. + * + * This state will be applied once wlr_output_commit_state() is called. + */ +void wlr_output_state_set_subpixel(struct wlr_output_state *state, + enum wl_output_subpixel subpixel); +/** + * 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 wlr_output_commit_state() is called. + */ +void wlr_output_state_set_buffer(struct wlr_output_state *state, + struct wlr_buffer *buffer); +/** + * Sets the gamma table for an output. `r`, `g` and `b` are gamma ramps for + * red, green and blue. `size` is the length of the ramps and must not exceed + * the value returned by wlr_output_get_gamma_size(). + * + * Providing zero-sized ramps resets the gamma table. + * + * This state will be applied once wlr_output_commit_state() is called. + */ +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); +/** + * 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 wlr_output_state_set_buffer(). + * This state will be applied once wlr_output_commit_state() is called. + */ +void wlr_output_state_set_damage(struct wlr_output_state *state, + const pixman_region32_t *damage); +/** + * Set the output layers for an output. Output layers are used to try and take + * advantage of backend features that may reduce the amount of things that + * need to be composited. + * + * The array must be kept valid by the caller until wlr_output_state_finish() + * and all copies of the state have been finished as well. + * This state will be applied once wlr_output_commit_state() is called. + */ +void wlr_output_state_set_layers(struct wlr_output_state *state, + struct wlr_output_layer_state *layers, size_t layers_len); + +/** + * Copies the output state from src to dst. It is safe to then + * wlr_output_state_finish() src and have dst still be valid. + * + * Note: The lifetime of the output layers inside the state are not managed. It + * is the responsibility of the constructor of the output layers to make sure + * they remain valid for the output state and all copies made. + */ +bool wlr_output_state_copy(struct wlr_output_state *dst, + const struct wlr_output_state *src); + + +/** + * Re-configure the swapchain as required for the output's primary buffer. + * + * If a NULL swapchain is passed in, a new swapchain is allocated. If the + * swapchain is already suitable for the output's primary buffer, this function + * is a no-op. + * + * The state describes the output changes the swapchain's buffers will be + * committed with. A NULL state indicates no change. + */ +bool wlr_output_configure_primary_swapchain(struct wlr_output *output, + const struct wlr_output_state *state, struct wlr_swapchain **swapchain); +/** + * Begin a render pass on this output. + * + * Compositors can call this function to begin rendering. After the render pass + * has been submitted, they should call wlr_output_commit_state() to submit the + * new frame. + * + * On error, NULL is returned. Creating a render pass on a disabled output is + * an error. + * + * The state describes the output changes the rendered frame will be + * committed with. A NULL state indicates no change. + * + * If non-NULL, buffer_age is set to the drawing buffer age in number of + * frames or -1 if unknown. This is useful for damage tracking. + */ +struct wlr_render_pass *wlr_output_begin_render_pass(struct wlr_output *output, + struct wlr_output_state *state, int *buffer_age, + struct wlr_buffer_pass_options *render_options); + +#endif diff --git a/include/wlr/types/wlr_output_layer.h b/include/wlr/types/wlr_output_layer.h new file mode 100644 index 0000000..af843d0 --- /dev/null +++ b/include/wlr/types/wlr_output_layer.h @@ -0,0 +1,103 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_OUTPUT_LAYER_H +#define WLR_TYPES_WLR_OUTPUT_LAYER_H + +#include +#include +#include +#include + +/** + * An output layer. + * + * Output layers are displayed between the output primary buffer (see + * wlr_output_attach_buffer()) and the cursor buffer. They can offload some + * rendering work to the backend. + * + * To configure output layers, callers should call wlr_output_layer_create() to + * create layers, attach struct wlr_output_layer_state onto + * struct wlr_output_state via wlr_output_set_layers() to describe their new + * state, and commit the output via wlr_output_commit(). + * + * Backends may have arbitrary limitations when it comes to displaying output + * layers. Backends indicate whether or not a layer can be displayed via + * wlr_output_layer_state.accepted after wlr_output_test() or + * wlr_output_commit() is called. Compositors using the output layers API + * directly are expected to setup layers, call wlr_output_test(), paint the + * layers that the backend rejected with the renderer, then call + * wlr_output_commit(). + * + * Callers are responsible for disabling output layers when they need the full + * output contents to be composited onto a single buffer, e.g. during screen + * capture. + * + * Callers must always include the state for all layers on output test/commit. + */ +struct wlr_output_layer { + struct wl_list link; // wlr_output.layers + struct wlr_addon_set addons; + + struct { + struct wl_signal feedback; // struct wlr_output_layer_feedback_event + } events; + + void *data; + + // private state + + struct wlr_fbox src_box; + struct wlr_box dst_box; +}; + +/** + * State for an output layer. + */ +struct wlr_output_layer_state { + struct wlr_output_layer *layer; + + // Buffer to display, or NULL to disable the layer + struct wlr_buffer *buffer; + // Source box, leave empty to use the whole buffer + struct wlr_fbox src_box; + // Destination box in output-buffer-local coordinates + struct wlr_box dst_box; + // Damaged region since last commit in buffer-local coordinates. Leave NULL + // to damage the whole buffer. + const pixman_region32_t *damage; + + // Populated by the backend after wlr_output_test() and wlr_output_commit(), + // indicates whether the backend has acknowledged and will take care of + // displaying the layer + bool accepted; +}; + +/** + * Feedback for an output layer. + * + * After an output commit, if the backend is not able to display a layer, it + * can send feedback events. These events can be used to re-allocate the + * layer's buffers so that they have a higher chance to get displayed. + */ +struct wlr_output_layer_feedback_event { + dev_t target_device; + const struct wlr_drm_format_set *formats; +}; + +/** + * Create a new output layer. + */ +struct wlr_output_layer *wlr_output_layer_create(struct wlr_output *output); + +/** + * Destroy an output layer. + */ +void wlr_output_layer_destroy(struct wlr_output_layer *layer); + +#endif diff --git a/include/wlr/types/wlr_output_layout.h b/include/wlr/types/wlr_output_layout.h new file mode 100644 index 0000000..40c7fb9 --- /dev/null +++ b/include/wlr/types/wlr_output_layout.h @@ -0,0 +1,166 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_OUTPUT_LAYOUT_H +#define WLR_TYPES_WLR_OUTPUT_LAYOUT_H + +#include +#include +#include +#include + +struct wlr_box; + +/** + * Helper to arrange outputs in a 2D coordinate space. The output effective + * resolution is used, see wlr_output_effective_resolution(). + * + * Outputs added to the output layout are automatically exposed to clients (see + * wlr_output_create_global()). They are no longer exposed when removed from the + * layout. + */ +struct wlr_output_layout { + struct wl_list outputs; + struct wl_display *display; + + struct { + struct wl_signal add; // struct wlr_output_layout_output + struct wl_signal change; + struct wl_signal destroy; + } events; + + void *data; + + // private state + + struct wl_listener display_destroy; +}; + +struct wlr_output_layout_output { + struct wlr_output_layout *layout; + + struct wlr_output *output; + + int x, y; + struct wl_list link; + + bool auto_configured; + + struct { + struct wl_signal destroy; + } events; + + // private state + + struct wlr_addon addon; + + struct wl_listener commit; +}; + +struct wlr_output_layout *wlr_output_layout_create(struct wl_display *display); + +void wlr_output_layout_destroy(struct wlr_output_layout *layout); + +/** + * Get the output layout for the specified output. Returns NULL if no output + * matches. + */ +struct wlr_output_layout_output *wlr_output_layout_get( + struct wlr_output_layout *layout, struct wlr_output *reference); + +/** + * Get the output at the specified layout coordinates. Returns NULL if no + * output matches the coordinates. + */ +struct wlr_output *wlr_output_layout_output_at( + struct wlr_output_layout *layout, double lx, double ly); + +/** + * Add the output to the layout at the specified coordinates. If the output is + * already a part of the output layout, it will become manually configured and + * will be moved to the specified coordinates. + * + * Returns true on success, false on a memory allocation error. + */ +struct wlr_output_layout_output *wlr_output_layout_add(struct wlr_output_layout *layout, + struct wlr_output *output, int lx, int ly); + +/** + * Add the output to the layout as automatically configured. This will place + * the output in a sensible location in the layout. The coordinates of + * the output in the layout will be adjusted dynamically when the layout + * changes. If the output is already a part of the layout, it will become + * automatically configured. + * + * Returns true on success, false on a memory allocation error. + */ +struct wlr_output_layout_output *wlr_output_layout_add_auto(struct wlr_output_layout *layout, + struct wlr_output *output); + +/** + * Remove the output from the layout. If the output is already not a part of + * the layout, this function is a no-op. + */ +void wlr_output_layout_remove(struct wlr_output_layout *layout, + struct wlr_output *output); + +/** + * Given x and y in layout coordinates, adjusts them to local output + * coordinates relative to the given reference output. + */ +void wlr_output_layout_output_coords(struct wlr_output_layout *layout, + struct wlr_output *reference, double *lx, double *ly); + +bool wlr_output_layout_contains_point(struct wlr_output_layout *layout, + struct wlr_output *reference, int lx, int ly); + +bool wlr_output_layout_intersects(struct wlr_output_layout *layout, + struct wlr_output *reference, const struct wlr_box *target_lbox); + +/** + * Get the closest point on this layout from the given point from the reference + * output. If reference is NULL, gets the closest point from the entire layout. + * If the layout is empty, the result is the given point itself. + */ +void wlr_output_layout_closest_point(struct wlr_output_layout *layout, + struct wlr_output *reference, double lx, double ly, + double *dest_lx, double *dest_ly); + +/** + * Get the box of the layout for the given reference output in layout + * coordinates. If `reference` is NULL, the box will be for the extents of the + * entire layout. If the output isn't in the layout, the box will be empty. + */ +void wlr_output_layout_get_box(struct wlr_output_layout *layout, + struct wlr_output *reference, struct wlr_box *dest_box); + +/** + * Get the output closest to the center of the layout extents. + */ +struct wlr_output *wlr_output_layout_get_center_output( + struct wlr_output_layout *layout); + +enum wlr_direction { + WLR_DIRECTION_UP = 1 << 0, + WLR_DIRECTION_DOWN = 1 << 1, + WLR_DIRECTION_LEFT = 1 << 2, + WLR_DIRECTION_RIGHT = 1 << 3, +}; + +/** + * Get the closest adjacent output to the reference output from the reference + * point in the given direction. + */ +struct wlr_output *wlr_output_layout_adjacent_output( + struct wlr_output_layout *layout, enum wlr_direction direction, + struct wlr_output *reference, double ref_lx, double ref_ly); +struct wlr_output *wlr_output_layout_farthest_output( + struct wlr_output_layout *layout, enum wlr_direction direction, + struct wlr_output *reference, double ref_lx, double ref_ly); + +#endif diff --git a/include/wlr/types/wlr_output_management_v1.h b/include/wlr/types/wlr_output_management_v1.h new file mode 100644 index 0000000..f1cd5ec --- /dev/null +++ b/include/wlr/types/wlr_output_management_v1.h @@ -0,0 +1,157 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_OUTPUT_MANAGEMENT_V1_H +#define WLR_TYPES_WLR_OUTPUT_MANAGEMENT_V1_H + +#include +#include +#include + +struct wlr_output_manager_v1 { + struct wl_display *display; + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link() + + struct wl_list heads; // wlr_output_head_v1.link + uint32_t serial; + bool current_configuration_dirty; + + struct { + /** + * The `apply` and `test` events are emitted when a client requests a + * configuration to be applied or tested. The compositor should send + * feedback with `wlr_output_configuration_v1_send_succeeded` xor + * `wlr_output_configuration_v1_send_failed`. + * + * The compositor gains ownership over the configuration (passed as the + * event data). That is, the compositor is responsible for destroying + * the configuration. + */ + struct wl_signal apply; // struct wlr_output_configuration_v1 + struct wl_signal test; // struct wlr_output_configuration_v1 + + struct wl_signal destroy; + } events; + + struct wl_listener display_destroy; + + void *data; +}; + +struct wlr_output_head_v1_state { + struct wlr_output *output; + + bool enabled; + struct wlr_output_mode *mode; + struct { + int32_t width, height; + int32_t refresh; + } custom_mode; + int32_t x, y; + enum wl_output_transform transform; + float scale; + bool adaptive_sync_enabled; +}; + +struct wlr_output_head_v1 { + struct wlr_output_head_v1_state state; + struct wlr_output_manager_v1 *manager; + struct wl_list link; // wlr_output_manager_v1.heads + + struct wl_list resources; // wl_resource_get_link() + struct wl_list mode_resources; // wl_resource_get_link() + + struct wl_listener output_destroy; +}; + +struct wlr_output_configuration_v1 { + struct wl_list heads; // wlr_output_configuration_head_v1.link + + // client state + struct wlr_output_manager_v1 *manager; + uint32_t serial; + bool finalized; // client has requested to apply the config + bool finished; // feedback has been sent by the compositor + struct wl_resource *resource; // can be NULL if destroyed early +}; + +struct wlr_output_configuration_head_v1 { + struct wlr_output_head_v1_state state; + struct wlr_output_configuration_v1 *config; + struct wl_list link; // wlr_output_configuration_v1.heads + + // client state + struct wl_resource *resource; // can be NULL if finalized or disabled + + struct wl_listener output_destroy; +}; + +/** + * Create a new output manager. The compositor is responsible for calling + * wlr_output_manager_v1_set_configuration() whenever the current output + * configuration changes. + */ +struct wlr_output_manager_v1 *wlr_output_manager_v1_create( + struct wl_display *display); +/** + * Updates the output manager's current configuration. This will broadcast any + * changes to all clients. + * + * This function takes ownership over `config`. That is, the compositor must not + * access the configuration anymore. + */ +void wlr_output_manager_v1_set_configuration( + struct wlr_output_manager_v1 *manager, + struct wlr_output_configuration_v1 *config); + +/** + * Create a new, empty output configuration. Compositors should add current head + * status with wlr_output_configuration_head_v1_create(). They can then call + * wlr_output_manager_v1_set_configuration(). + */ +struct wlr_output_configuration_v1 *wlr_output_configuration_v1_create(void); +void wlr_output_configuration_v1_destroy( + struct wlr_output_configuration_v1 *config); +/** + * If the configuration comes from a client request, this sends positive + * feedback to the client (configuration has been applied). + */ +void wlr_output_configuration_v1_send_succeeded( + struct wlr_output_configuration_v1 *config); +/** + * If the configuration comes from a client request, this sends negative + * feedback to the client (configuration has not been applied). + */ +void wlr_output_configuration_v1_send_failed( + struct wlr_output_configuration_v1 *config); + +/** + * Create a new configuration head for the given output. This adds the head to + * the provided output configuration. + * + * The configuration head will be pre-filled with data from `output`. The + * compositor should adjust this data according to its current internal state. + */ +struct wlr_output_configuration_head_v1 * + wlr_output_configuration_head_v1_create( + struct wlr_output_configuration_v1 *config, struct wlr_output *output); + +/** + * Apply the head state on the supplied struct wlr_output_state. + * + * Compositors can then pass the resulting struct wlr_output_state to + * wlr_output_commit_state() or wlr_output_test_state(). + * + * The position needs to be applied manually by the caller. + */ +void wlr_output_head_v1_state_apply( + const struct wlr_output_head_v1_state *head_state, + struct wlr_output_state *output_state); + +#endif diff --git a/include/wlr/types/wlr_output_power_management_v1.h b/include/wlr/types/wlr_output_power_management_v1.h new file mode 100644 index 0000000..688e37a --- /dev/null +++ b/include/wlr/types/wlr_output_power_management_v1.h @@ -0,0 +1,41 @@ +#ifndef WLR_TYPES_WLR_OUTPUT_POWER_MANAGEMENT_V1_H +#define WLR_TYPES_WLR_OUTPUT_POWER_MANAGEMENT_V1_H + +#include +#include "wlr-output-power-management-unstable-v1-protocol.h" + +struct wlr_output_power_manager_v1 { + struct wl_global *global; + struct wl_list output_powers; // wlr_output_power_v1.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal set_mode; // struct wlr_output_power_v1_set_mode_event + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_output_power_v1 { + struct wl_resource *resource; + struct wlr_output *output; + struct wlr_output_power_manager_v1 *manager; + struct wl_list link; // wlr_output_power_manager_v1.output_powers + + struct wl_listener output_destroy_listener; + struct wl_listener output_commit_listener; + + void *data; +}; + +struct wlr_output_power_v1_set_mode_event { + struct wlr_output *output; + enum zwlr_output_power_v1_mode mode; +}; + +struct wlr_output_power_manager_v1 *wlr_output_power_manager_v1_create( + struct wl_display *display); + +#endif diff --git a/include/wlr/types/wlr_pointer.h b/include/wlr/types/wlr_pointer.h new file mode 100644 index 0000000..2fa8513 --- /dev/null +++ b/include/wlr/types/wlr_pointer.h @@ -0,0 +1,147 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_POINTER_H +#define WLR_TYPES_WLR_POINTER_H + +#include +#include +#include +#include + +struct wlr_pointer_impl; + +struct wlr_pointer { + struct wlr_input_device base; + + const struct wlr_pointer_impl *impl; + + char *output_name; + + struct { + struct wl_signal motion; // struct wlr_pointer_motion_event + struct wl_signal motion_absolute; // struct wlr_pointer_motion_absolute_event + struct wl_signal button; // struct wlr_pointer_button_event + struct wl_signal axis; // struct wlr_pointer_axis_event + struct wl_signal frame; + + struct wl_signal swipe_begin; // struct wlr_pointer_swipe_begin_event + struct wl_signal swipe_update; // struct wlr_pointer_swipe_update_event + struct wl_signal swipe_end; // struct wlr_pointer_swipe_end_event + + struct wl_signal pinch_begin; // struct wlr_pointer_pinch_begin_event + struct wl_signal pinch_update; // struct wlr_pointer_pinch_update_event + struct wl_signal pinch_end; // struct wlr_pointer_pinch_end_event + + struct wl_signal hold_begin; // struct wlr_pointer_hold_begin_event + struct wl_signal hold_end; // struct wlr_pointer_hold_end_event + } events; + + void *data; +}; + +struct wlr_pointer_motion_event { + struct wlr_pointer *pointer; + uint32_t time_msec; + double delta_x, delta_y; + double unaccel_dx, unaccel_dy; +}; + +struct wlr_pointer_motion_absolute_event { + struct wlr_pointer *pointer; + uint32_t time_msec; + // From 0..1 + double x, y; +}; + +struct wlr_pointer_button_event { + struct wlr_pointer *pointer; + uint32_t time_msec; + uint32_t button; + enum wl_pointer_button_state state; +}; + +#define WLR_POINTER_AXIS_DISCRETE_STEP 120 + +struct wlr_pointer_axis_event { + struct wlr_pointer *pointer; + uint32_t time_msec; + enum wl_pointer_axis_source source; + enum wl_pointer_axis orientation; + enum wl_pointer_axis_relative_direction relative_direction; + double delta; + int32_t delta_discrete; +}; + +struct wlr_pointer_swipe_begin_event { + struct wlr_pointer *pointer; + uint32_t time_msec; + uint32_t fingers; +}; + +struct wlr_pointer_swipe_update_event { + struct wlr_pointer *pointer; + uint32_t time_msec; + uint32_t fingers; + // Relative coordinates of the logical center of the gesture + // compared to the previous event. + double dx, dy; +}; + +struct wlr_pointer_swipe_end_event { + struct wlr_pointer *pointer; + uint32_t time_msec; + bool cancelled; +}; + +struct wlr_pointer_pinch_begin_event { + struct wlr_pointer *pointer; + uint32_t time_msec; + uint32_t fingers; +}; + +struct wlr_pointer_pinch_update_event { + struct wlr_pointer *pointer; + uint32_t time_msec; + uint32_t fingers; + // Relative coordinates of the logical center of the gesture + // compared to the previous event. + double dx, dy; + // Absolute scale compared to the begin event + double scale; + // Relative angle in degrees clockwise compared to the previous event. + double rotation; +}; + +struct wlr_pointer_pinch_end_event { + struct wlr_pointer *pointer; + uint32_t time_msec; + bool cancelled; +}; + +struct wlr_pointer_hold_begin_event { + struct wlr_pointer *pointer; + uint32_t time_msec; + uint32_t fingers; +}; + +struct wlr_pointer_hold_end_event { + struct wlr_pointer *pointer; + uint32_t time_msec; + bool cancelled; +}; + +/** + * Get a struct wlr_pointer from a struct wlr_input_device. + * + * Asserts that the input device is a pointer. + */ +struct wlr_pointer *wlr_pointer_from_input_device( + struct wlr_input_device *input_device); + +#endif diff --git a/include/wlr/types/wlr_pointer_constraints_v1.h b/include/wlr/types/wlr_pointer_constraints_v1.h new file mode 100644 index 0000000..2b4722f --- /dev/null +++ b/include/wlr/types/wlr_pointer_constraints_v1.h @@ -0,0 +1,110 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_POINTER_CONSTRAINTS_V1_H +#define WLR_TYPES_WLR_POINTER_CONSTRAINTS_V1_H + +#include +#include +#include +#include +#include +#include "pointer-constraints-unstable-v1-protocol.h" + +struct wlr_seat; + +enum wlr_pointer_constraint_v1_type { + WLR_POINTER_CONSTRAINT_V1_LOCKED, + WLR_POINTER_CONSTRAINT_V1_CONFINED, +}; + +enum wlr_pointer_constraint_v1_state_field { + WLR_POINTER_CONSTRAINT_V1_STATE_REGION = 1 << 0, + WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT = 1 << 1, +}; + +struct wlr_pointer_constraint_v1_state { + uint32_t committed; // enum wlr_pointer_constraint_v1_state_field + pixman_region32_t region; + + // only valid for locked_pointer + struct { + bool enabled; + double x, y; + } cursor_hint; +}; + +struct wlr_pointer_constraint_v1 { + struct wlr_pointer_constraints_v1 *pointer_constraints; + + struct wl_resource *resource; + struct wlr_surface *surface; + struct wlr_seat *seat; + enum zwp_pointer_constraints_v1_lifetime lifetime; + enum wlr_pointer_constraint_v1_type type; + pixman_region32_t region; + + struct wlr_pointer_constraint_v1_state current, pending; + + struct wl_list link; // wlr_pointer_constraints_v1.constraints + + struct { + /** + * Called when a pointer constraint's region is updated, + * post-surface-commit. + */ + struct wl_signal set_region; + struct wl_signal destroy; + } events; + + void *data; + + // private state + + struct wl_listener surface_commit; + struct wl_listener surface_destroy; + struct wl_listener seat_destroy; + + struct wlr_surface_synced synced; +}; + +struct wlr_pointer_constraints_v1 { + struct wl_global *global; + struct wl_list constraints; // wlr_pointer_constraint_v1.link + + struct { + /** + * Called when a new pointer constraint is created. + * + * The data pointer is a struct wlr_pointer_constraint_v1. + */ + struct wl_signal new_constraint; + } events; + + struct wl_listener display_destroy; + + void *data; +}; + +struct wlr_pointer_constraints_v1 *wlr_pointer_constraints_v1_create( + struct wl_display *display); + +struct wlr_pointer_constraint_v1 * + wlr_pointer_constraints_v1_constraint_for_surface( + struct wlr_pointer_constraints_v1 *pointer_constraints, + struct wlr_surface *surface, struct wlr_seat *seat); + +void wlr_pointer_constraint_v1_send_activated( + struct wlr_pointer_constraint_v1 *constraint); +/** + * Deactivate the constraint. May destroy the constraint. + */ +void wlr_pointer_constraint_v1_send_deactivated( + struct wlr_pointer_constraint_v1 *constraint); + +#endif diff --git a/include/wlr/types/wlr_pointer_gestures_v1.h b/include/wlr/types/wlr_pointer_gestures_v1.h new file mode 100644 index 0000000..5510ce2 --- /dev/null +++ b/include/wlr/types/wlr_pointer_gestures_v1.h @@ -0,0 +1,82 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_POINTER_GESTURES_V1_H +#define WLR_TYPES_WLR_POINTER_GESTURES_V1_H + +#include +#include + +struct wlr_surface; + +struct wlr_pointer_gestures_v1 { + struct wl_global *global; + struct wl_list swipes; // wl_resource_get_link() + struct wl_list pinches; // wl_resource_get_link() + struct wl_list holds; // wl_resource_get_link() + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_pointer_gestures_v1 *wlr_pointer_gestures_v1_create( + struct wl_display *display); + +void wlr_pointer_gestures_v1_send_swipe_begin( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + uint32_t fingers); +void wlr_pointer_gestures_v1_send_swipe_update( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + double dx, + double dy); +void wlr_pointer_gestures_v1_send_swipe_end( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + bool cancelled); + +void wlr_pointer_gestures_v1_send_pinch_begin( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + uint32_t fingers); +void wlr_pointer_gestures_v1_send_pinch_update( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + double dx, + double dy, + double scale, + double rotation); +void wlr_pointer_gestures_v1_send_pinch_end( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + bool cancelled); + +void wlr_pointer_gestures_v1_send_hold_begin( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + uint32_t fingers); +void wlr_pointer_gestures_v1_send_hold_end( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + bool cancelled); + +#endif diff --git a/include/wlr/types/wlr_presentation_time.h b/include/wlr/types/wlr_presentation_time.h new file mode 100644 index 0000000..b772295 --- /dev/null +++ b/include/wlr/types/wlr_presentation_time.h @@ -0,0 +1,106 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_PRESENTATION_TIME_H +#define WLR_TYPES_WLR_PRESENTATION_TIME_H + +#include +#include +#include +#include + +struct wlr_surface; + +struct wlr_output; +struct wlr_output_event_present; + +struct wlr_presentation { + struct wl_global *global; + + struct { + struct wl_signal destroy; + } events; + + struct wl_listener display_destroy; +}; + +struct wlr_presentation_feedback { + struct wl_list resources; // wl_resource_get_link() + + // Only when the wlr_presentation_surface_textured_on_output() or + // wlr_presentation_surface_scanned_out_on_output() helper has been called. + struct wlr_output *output; + bool output_committed; + uint32_t output_commit_seq; + bool zero_copy; + + struct wl_listener output_commit; + struct wl_listener output_present; + struct wl_listener output_destroy; +}; + +struct wlr_presentation_event { + struct wlr_output *output; + uint64_t tv_sec; + uint32_t tv_nsec; + uint32_t refresh; + uint64_t seq; + uint32_t flags; // enum wp_presentation_feedback_kind +}; + +struct wlr_backend; + +struct wlr_presentation *wlr_presentation_create(struct wl_display *display, + struct wlr_backend *backend); +/** + * Mark the current surface's buffer as sampled. + * + * The compositor must call this function when it uses the surface's current + * contents (e.g. when rendering the surface's current texture, when + * referencing its current buffer, or when directly scanning out its current + * buffer). A wlr_presentation_feedback is returned. The compositor should call + * wlr_presentation_feedback_send_presented() if this content has been displayed, + * then wlr_presentation_feedback_destroy(). + * + * NULL is returned if the client hasn't requested presentation feedback for + * this surface. + */ +struct wlr_presentation_feedback *wlr_presentation_surface_sampled( + struct wlr_surface *surface); +void wlr_presentation_feedback_send_presented( + struct wlr_presentation_feedback *feedback, + const struct wlr_presentation_event *event); +void wlr_presentation_feedback_destroy( + struct wlr_presentation_feedback *feedback); + +/** + * Fill a wlr_presentation_event from a struct wlr_output_event_present. + */ +void wlr_presentation_event_from_output(struct wlr_presentation_event *event, + const struct wlr_output_event_present *output_event); + +/** + * Mark the current surface's buffer as textured on the given output. + * + * Instead of calling wlr_presentation_surface_sampled() and managing the + * struct wlr_presentation_feedback itself, the compositor can call this function + * before a wlr_output_commit() call to indicate that the surface's current + * contents have been copied to a buffer which will be displayed on the output. + */ +void wlr_presentation_surface_textured_on_output(struct wlr_surface *surface, + struct wlr_output *output); +/** + * Mark the current surface's buffer as scanned out on the given output. + * + * Same as wlr_presentation_surface_textured_on_output(), but indicates direct + * scan-out. + */ +void wlr_presentation_surface_scanned_out_on_output(struct wlr_surface *surface, + struct wlr_output *output); + +#endif diff --git a/include/wlr/types/wlr_primary_selection.h b/include/wlr/types/wlr_primary_selection.h new file mode 100644 index 0000000..af3d484 --- /dev/null +++ b/include/wlr/types/wlr_primary_selection.h @@ -0,0 +1,68 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_PRIMARY_SELECTION_H +#define WLR_TYPES_WLR_PRIMARY_SELECTION_H + +#include +#include + +struct wlr_primary_selection_source; + +/** + * A data source implementation. Only the `send` function is mandatory. + */ +struct wlr_primary_selection_source_impl { + void (*send)(struct wlr_primary_selection_source *source, + const char *mime_type, int fd); + void (*destroy)(struct wlr_primary_selection_source *source); +}; + +/** + * A source is the sending side of a selection. + */ +struct wlr_primary_selection_source { + const struct wlr_primary_selection_source_impl *impl; + + // source metadata + struct wl_array mime_types; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +void wlr_primary_selection_source_init( + struct wlr_primary_selection_source *source, + const struct wlr_primary_selection_source_impl *impl); +void wlr_primary_selection_source_destroy( + struct wlr_primary_selection_source *source); +void wlr_primary_selection_source_send( + struct wlr_primary_selection_source *source, const char *mime_type, + int fd); + +/** + * Request setting the primary selection. If `client` is not null, then the + * serial will be checked against the set of serials sent to the client on that + * seat. + */ +void wlr_seat_request_set_primary_selection(struct wlr_seat *seat, + struct wlr_seat_client *client, + struct wlr_primary_selection_source *source, uint32_t serial); +/** + * Sets the current primary selection for the seat. NULL can be provided to + * clear it. This removes the previous one if there was any. In case the + * selection doesn't come from a client, wl_display_next_serial() can be used to + * generate a serial. + */ +void wlr_seat_set_primary_selection(struct wlr_seat *seat, + struct wlr_primary_selection_source *source, uint32_t serial); + +#endif diff --git a/include/wlr/types/wlr_primary_selection_v1.h b/include/wlr/types/wlr_primary_selection_v1.h new file mode 100644 index 0000000..78b542a --- /dev/null +++ b/include/wlr/types/wlr_primary_selection_v1.h @@ -0,0 +1,49 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_PRIMARY_SELECTION_V1_H +#define WLR_TYPES_WLR_PRIMARY_SELECTION_V1_H + +#include +#include + +struct wlr_primary_selection_v1_device_manager { + struct wl_global *global; + struct wl_list devices; // wlr_primary_selection_v1_device.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +/** + * A device is a per-seat object used to set and get the current selection. + */ +struct wlr_primary_selection_v1_device { + struct wlr_primary_selection_v1_device_manager *manager; + struct wlr_seat *seat; + struct wl_list link; // wlr_primary_selection_v1_device_manager.devices + struct wl_list resources; // wl_resource_get_link() + + struct wl_list offers; // wl_resource_get_link() + + struct wl_listener seat_destroy; + struct wl_listener seat_focus_change; + struct wl_listener seat_set_primary_selection; + + void *data; +}; + +struct wlr_primary_selection_v1_device_manager * + wlr_primary_selection_v1_device_manager_create(struct wl_display *display); + +#endif diff --git a/include/wlr/types/wlr_region.h b/include/wlr/types/wlr_region.h new file mode 100644 index 0000000..483ac96 --- /dev/null +++ b/include/wlr/types/wlr_region.h @@ -0,0 +1,20 @@ +/* + * This is a deprecated interface of wlroots. It will be removed in a future + * version. wlr/types/wlr_compositor.h should be used instead. + */ + +#ifndef WLR_TYPES_WLR_REGION_H +#define WLR_TYPES_WLR_REGION_H + +#include + +struct wl_resource; + +/** + * Obtain a Pixman region from a wl_region resource. + * + * To allow clients to create wl_region objects, call wlr_compositor_create(). + */ +const pixman_region32_t *wlr_region_from_resource(struct wl_resource *resource); + +#endif diff --git a/include/wlr/types/wlr_relative_pointer_v1.h b/include/wlr/types/wlr_relative_pointer_v1.h new file mode 100644 index 0000000..fcd44d5 --- /dev/null +++ b/include/wlr/types/wlr_relative_pointer_v1.h @@ -0,0 +1,78 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_RELATIVE_POINTER_V1_H +#define WLR_TYPES_WLR_RELATIVE_POINTER_V1_H + +#include + +/** + * This protocol specifies a set of interfaces used for making clients able to + * receive relative pointer events not obstructed by barriers (such as the + * monitor edge or pointer constraints). + */ + +/** + * A global interface used for getting the relative pointer object for a given + * pointer. + */ +struct wlr_relative_pointer_manager_v1 { + struct wl_global *global; + struct wl_list relative_pointers; // wlr_relative_pointer_v1.link + + struct { + struct wl_signal destroy; + struct wl_signal new_relative_pointer; // struct wlr_relative_pointer_v1 + } events; + + struct wl_listener display_destroy_listener; + + void *data; +}; + +/** + * A wp_relative_pointer object is an extension to the wl_pointer interface + * used for emitting relative pointer events. It shares the same focus as + * wl_pointer objects of the same seat and will only emit events when it has + * focus. + */ +struct wlr_relative_pointer_v1 { + struct wl_resource *resource; + struct wl_resource *pointer_resource; + struct wlr_seat *seat; + struct wl_list link; // wlr_relative_pointer_manager_v1.relative_pointers + + struct { + struct wl_signal destroy; + } events; + + struct wl_listener seat_destroy; + struct wl_listener pointer_destroy; + + void *data; +}; + +struct wlr_relative_pointer_manager_v1 *wlr_relative_pointer_manager_v1_create( + struct wl_display *display); + +/** + * Send a relative motion event to the seat. Time is given in microseconds + * (unlike wl_pointer which uses milliseconds). + */ +void wlr_relative_pointer_manager_v1_send_relative_motion( + struct wlr_relative_pointer_manager_v1 *manager, struct wlr_seat *seat, + uint64_t time_usec, double dx, double dy, + double dx_unaccel, double dy_unaccel); + +/** + * Get a relative pointer from its resource. Returns NULL if inert. + */ +struct wlr_relative_pointer_v1 *wlr_relative_pointer_v1_from_resource( + struct wl_resource *resource); + +#endif diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h new file mode 100644 index 0000000..253de16 --- /dev/null +++ b/include/wlr/types/wlr_scene.h @@ -0,0 +1,594 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SCENE_H +#define WLR_TYPES_WLR_SCENE_H + +/** + * The scene-graph API provides a declarative way to display surfaces. The + * compositor creates a scene, adds surfaces, then renders the scene on + * outputs. + * + * The scene-graph API only supports basic 2D composition operations (like the + * KMS API or the Wayland protocol does). For anything more complicated, + * compositors need to implement custom rendering logic. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct wlr_output; +struct wlr_output_layout; +struct wlr_output_layout_output; +struct wlr_xdg_surface; +struct wlr_layer_surface_v1; +struct wlr_drag_icon; +struct wlr_surface; + +struct wlr_scene_node; +struct wlr_scene_buffer; +struct wlr_scene_output_layout; + +struct wlr_presentation; +struct wlr_linux_dmabuf_v1; +struct wlr_output_state; + +typedef bool (*wlr_scene_buffer_point_accepts_input_func_t)( + struct wlr_scene_buffer *buffer, double *sx, double *sy); + +typedef void (*wlr_scene_buffer_iterator_func_t)( + struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data); + +enum wlr_scene_node_type { + WLR_SCENE_NODE_TREE, + WLR_SCENE_NODE_RECT, + WLR_SCENE_NODE_BUFFER, +}; + +/** A node is an object in the scene. */ +struct wlr_scene_node { + enum wlr_scene_node_type type; + struct wlr_scene_tree *parent; + + struct wl_list link; // wlr_scene_tree.children + + bool enabled; + int x, y; // relative to parent + + struct { + struct wl_signal destroy; + } events; + + void *data; + + struct wlr_addon_set addons; + + // private state + + pixman_region32_t visible; +}; + +enum wlr_scene_debug_damage_option { + WLR_SCENE_DEBUG_DAMAGE_NONE, + WLR_SCENE_DEBUG_DAMAGE_RERENDER, + WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT +}; + +/** A sub-tree in the scene-graph. */ +struct wlr_scene_tree { + struct wlr_scene_node node; + + struct wl_list children; // wlr_scene_node.link +}; + +/** The root scene-graph node. */ +struct wlr_scene { + struct wlr_scene_tree tree; + + struct wl_list outputs; // wlr_scene_output.link + + // May be NULL + struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1; + + // private state + + struct wl_listener linux_dmabuf_v1_destroy; + + enum wlr_scene_debug_damage_option debug_damage_option; + bool direct_scanout; + bool calculate_visibility; +}; + +/** A scene-graph node displaying a single surface. */ +struct wlr_scene_surface { + struct wlr_scene_buffer *buffer; + struct wlr_surface *surface; + + // private state + + struct wlr_box clip; + + struct wlr_addon addon; + + struct wl_listener outputs_update; + struct wl_listener output_enter; + struct wl_listener output_leave; + struct wl_listener output_sample; + struct wl_listener frame_done; + struct wl_listener surface_destroy; + struct wl_listener surface_commit; +}; + +/** A scene-graph node displaying a solid-colored rectangle */ +struct wlr_scene_rect { + struct wlr_scene_node node; + int width, height; + float color[4]; +}; + +struct wlr_scene_outputs_update_event { + struct wlr_scene_output **active; + size_t size; +}; + +struct wlr_scene_output_sample_event { + struct wlr_scene_output *output; + bool direct_scanout; +}; + +/** A scene-graph node displaying a buffer */ +struct wlr_scene_buffer { + struct wlr_scene_node node; + + // May be NULL + struct wlr_buffer *buffer; + + struct { + struct wl_signal outputs_update; // struct wlr_scene_outputs_update_event + struct wl_signal output_enter; // struct wlr_scene_output + struct wl_signal output_leave; // struct wlr_scene_output + struct wl_signal output_sample; // struct wlr_scene_output_sample_event + struct wl_signal frame_done; // struct timespec + } events; + + // May be NULL + wlr_scene_buffer_point_accepts_input_func_t point_accepts_input; + + /** + * The output that the largest area of this buffer is displayed on. + * This may be NULL if the buffer is not currently displayed on any + * outputs. This is the output that should be used for frame callbacks, + * presentation feedback, etc. + */ + struct wlr_scene_output *primary_output; + + float opacity; + enum wlr_scale_filter_mode filter_mode; + struct wlr_fbox src_box; + int dst_width, dst_height; + enum wl_output_transform transform; + pixman_region32_t opaque_region; + + // private state + + uint64_t active_outputs; + struct wlr_texture *texture; + struct wlr_linux_dmabuf_feedback_v1_init_options prev_feedback_options; + + bool own_buffer; + int buffer_width, buffer_height; + bool buffer_is_opaque; + + struct wl_listener buffer_release; +}; + +/** A viewport for an output in the scene-graph */ +struct wlr_scene_output { + struct wlr_output *output; + struct wl_list link; // wlr_scene.outputs + struct wlr_scene *scene; + struct wlr_addon addon; + + struct wlr_damage_ring damage_ring; + + int x, y; + + struct { + struct wl_signal destroy; + } events; + + // private state + + pixman_region32_t pending_commit_damage; + + uint8_t index; + bool prev_scanout; + + struct wl_listener output_commit; + struct wl_listener output_damage; + struct wl_listener output_needs_frame; + + struct wl_list damage_highlight_regions; + + struct wl_array render_list; +}; + +struct wlr_scene_timer { + int64_t pre_render_duration; + struct wlr_render_timer *render_timer; +}; + +/** A layer shell scene helper */ +struct wlr_scene_layer_surface_v1 { + struct wlr_scene_tree *tree; + struct wlr_layer_surface_v1 *layer_surface; + + // private state + + struct wl_listener tree_destroy; + struct wl_listener layer_surface_destroy; + struct wl_listener layer_surface_map; + struct wl_listener layer_surface_unmap; +}; + +/** + * Immediately destroy the scene-graph node. + */ +void wlr_scene_node_destroy(struct wlr_scene_node *node); +/** + * Enable or disable this node. If a node is disabled, all of its children are + * implicitly disabled as well. + */ +void wlr_scene_node_set_enabled(struct wlr_scene_node *node, bool enabled); +/** + * Set the position of the node relative to its parent. + */ +void wlr_scene_node_set_position(struct wlr_scene_node *node, int x, int y); +/** + * Move the node right above the specified sibling. + * Asserts that node and sibling are distinct and share the same parent. + */ +void wlr_scene_node_place_above(struct wlr_scene_node *node, + struct wlr_scene_node *sibling); +/** + * Move the node right below the specified sibling. + * Asserts that node and sibling are distinct and share the same parent. + */ +void wlr_scene_node_place_below(struct wlr_scene_node *node, + struct wlr_scene_node *sibling); +/** + * Move the node above all of its sibling nodes. + */ +void wlr_scene_node_raise_to_top(struct wlr_scene_node *node); +/** + * Move the node below all of its sibling nodes. + */ +void wlr_scene_node_lower_to_bottom(struct wlr_scene_node *node); +/** + * Move the node to another location in the tree. + */ +void wlr_scene_node_reparent(struct wlr_scene_node *node, + struct wlr_scene_tree *new_parent); +/** + * Get the node's layout-local coordinates. + * + * True is returned if the node and all of its ancestors are enabled. + */ +bool wlr_scene_node_coords(struct wlr_scene_node *node, int *lx, int *ly); +/** + * Call `iterator` on each buffer in the scene-graph, with the buffer's + * position in layout coordinates. The function is called from root to leaves + * (in rendering order). + */ +void wlr_scene_node_for_each_buffer(struct wlr_scene_node *node, + wlr_scene_buffer_iterator_func_t iterator, void *user_data); +/** + * Find the topmost node in this scene-graph that contains the point at the + * given layout-local coordinates. (For surface nodes, this means accepting + * input events at that point.) Returns the node and coordinates relative to the + * returned node, or NULL if no node is found at that location. + */ +struct wlr_scene_node *wlr_scene_node_at(struct wlr_scene_node *node, + double lx, double ly, double *nx, double *ny); + +/** + * Create a new scene-graph. + */ +struct wlr_scene *wlr_scene_create(void); + +/** + * Handles linux_dmabuf_v1 feedback for all surfaces in the scene. + * + * Asserts that a struct wlr_linux_dmabuf_v1 hasn't already been set for the scene. + */ +void wlr_scene_set_linux_dmabuf_v1(struct wlr_scene *scene, + struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1); + + +/** + * Add a node displaying nothing but its children. + */ +struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent); + +/** + * Add a node displaying a single surface to the scene-graph. + * + * The child sub-surfaces are ignored. + * + * wlr_surface_send_enter() and wlr_surface_send_leave() will be called + * automatically based on the position of the surface and outputs in + * the scene. + */ +struct wlr_scene_surface *wlr_scene_surface_create(struct wlr_scene_tree *parent, + struct wlr_surface *surface); + +/** + * If this node represents a wlr_scene_buffer, that buffer will be returned. It + * is not legal to feed a node that does not represent a wlr_scene_buffer. + */ +struct wlr_scene_buffer *wlr_scene_buffer_from_node(struct wlr_scene_node *node); + +/** + * If this node represents a wlr_scene_tree, that tree will be returned. It + * is not legal to feed a node that does not represent a wlr_scene_tree. + */ +struct wlr_scene_tree *wlr_scene_tree_from_node(struct wlr_scene_node *node); + +/** + * If this node represents a wlr_scene_rect, that rect will be returned. It + * is not legal to feed a node that does not represent a wlr_scene_rect. + */ +struct wlr_scene_rect *wlr_scene_rect_from_node(struct wlr_scene_node *node); + +/** + * If this buffer is backed by a surface, then the struct wlr_scene_surface is + * returned. If not, NULL will be returned. + */ +struct wlr_scene_surface *wlr_scene_surface_try_from_buffer( + struct wlr_scene_buffer *scene_buffer); + +/** + * Add a node displaying a solid-colored rectangle to the scene-graph. + */ +struct wlr_scene_rect *wlr_scene_rect_create(struct wlr_scene_tree *parent, + int width, int height, const float color[static 4]); + +/** + * Change the width and height of an existing rectangle node. + */ +void wlr_scene_rect_set_size(struct wlr_scene_rect *rect, int width, int height); + +/** + * Change the color of an existing rectangle node. + */ +void wlr_scene_rect_set_color(struct wlr_scene_rect *rect, const float color[static 4]); + +/** + * Add a node displaying a buffer to the scene-graph. + * + * If the buffer is NULL, this node will not be displayed. + */ +struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, + struct wlr_buffer *buffer); + +/** + * Sets the buffer's backing buffer. + * + * If the buffer is NULL, the buffer node will not be displayed. + */ +void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, + struct wlr_buffer *buffer); + +/** + * Sets the buffer's backing buffer with a custom damage region. + * + * The damage region is in buffer-local coordinates. If the region is NULL, + * the whole buffer node will be damaged. + */ +void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer, + struct wlr_buffer *buffer, const pixman_region32_t *region); + +/** + * Sets the buffer's opaque region. This is an optimization hint used to + * determine if buffers which reside under this one need to be rendered or not. + */ +void wlr_scene_buffer_set_opaque_region(struct wlr_scene_buffer *scene_buffer, + const pixman_region32_t *region); + +/** + * Set the source rectangle describing the region of the buffer which will be + * sampled to render this node. This allows cropping the buffer. + * + * If NULL, the whole buffer is sampled. By default, the source box is NULL. + */ +void wlr_scene_buffer_set_source_box(struct wlr_scene_buffer *scene_buffer, + const struct wlr_fbox *box); + +/** + * Set the destination size describing the region of the scene-graph the buffer + * will be painted onto. This allows scaling the buffer. + * + * If zero, the destination size will be the buffer size. By default, the + * destination size is zero. + */ +void wlr_scene_buffer_set_dest_size(struct wlr_scene_buffer *scene_buffer, + int width, int height); + +/** + * Set a transform which will be applied to the buffer. + */ +void wlr_scene_buffer_set_transform(struct wlr_scene_buffer *scene_buffer, + enum wl_output_transform transform); + +/** +* Sets the opacity of this buffer +*/ +void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer, + float opacity); + +/** +* Sets the filter mode to use when scaling the buffer +*/ +void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer, + enum wlr_scale_filter_mode filter_mode); + +/** + * Calls the buffer's frame_done signal. + */ +void wlr_scene_buffer_send_frame_done(struct wlr_scene_buffer *scene_buffer, + struct timespec *now); + +/** + * Add a viewport for the specified output to the scene-graph. + * + * An output can only be added once to the scene-graph. + */ +struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene, + struct wlr_output *output); +/** + * Destroy a scene-graph output. + */ +void wlr_scene_output_destroy(struct wlr_scene_output *scene_output); +/** + * Set the output's position in the scene-graph. + */ +void wlr_scene_output_set_position(struct wlr_scene_output *scene_output, + int lx, int ly); + +struct wlr_scene_output_state_options { + struct wlr_scene_timer *timer; +}; + +/** + * Render and commit an output. + */ +bool wlr_scene_output_commit(struct wlr_scene_output *scene_output, + const struct wlr_scene_output_state_options *options); + +/** + * Render and populate given output state. + */ +bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, + struct wlr_output_state *state, const struct wlr_scene_output_state_options *options); + +/** + * Retrieve the duration in nanoseconds between the last wlr_scene_output_commit() call and the end + * of its operations, including those on the GPU that may have finished after the call returned. + * + * Returns -1 if the duration is unavailable. + */ +int64_t wlr_scene_timer_get_duration_ns(struct wlr_scene_timer *timer); +void wlr_scene_timer_finish(struct wlr_scene_timer *timer); + +/** + * Call wlr_surface_send_frame_done() on all surfaces in the scene rendered by + * wlr_scene_output_commit() for which wlr_scene_surface.primary_output + * matches the given scene_output. + */ +void wlr_scene_output_send_frame_done(struct wlr_scene_output *scene_output, + struct timespec *now); +/** + * Call `iterator` on each buffer in the scene-graph visible on the output, + * with the buffer's position in layout coordinates. The function is called + * from root to leaves (in rendering order). + */ +void wlr_scene_output_for_each_buffer(struct wlr_scene_output *scene_output, + wlr_scene_buffer_iterator_func_t iterator, void *user_data); +/** + * Get a scene-graph output from a struct wlr_output. + * + * If the output hasn't been added to the scene-graph, returns NULL. + */ +struct wlr_scene_output *wlr_scene_get_scene_output(struct wlr_scene *scene, + struct wlr_output *output); + +/** + * Attach an output layout to a scene. + * + * The resulting scene output layout allows to synchronize the positions of scene + * outputs with the positions of corresponding layout outputs. + * + * It is automatically destroyed when the scene or the output layout is destroyed. + */ +struct wlr_scene_output_layout *wlr_scene_attach_output_layout(struct wlr_scene *scene, + struct wlr_output_layout *output_layout); + +/** + * Add an output to the scene output layout. + * + * When the layout output is repositioned, the scene output will be repositioned + * accordingly. + */ +void wlr_scene_output_layout_add_output(struct wlr_scene_output_layout *sol, + struct wlr_output_layout_output *lo, struct wlr_scene_output *so); + +/** + * Add a node displaying a surface and all of its sub-surfaces to the + * scene-graph. + */ +struct wlr_scene_tree *wlr_scene_subsurface_tree_create( + struct wlr_scene_tree *parent, struct wlr_surface *surface); + +/** + * Sets a cropping region for any subsurface trees that are children of this + * scene node. The clip coordinate space will be that of the root surface of + * the subsurface tree. + * + * A NULL or empty clip will disable clipping + */ +void wlr_scene_subsurface_tree_set_clip(struct wlr_scene_node *node, + struct wlr_box *clip); + +/** + * Add a node displaying an xdg_surface and all of its sub-surfaces to the + * scene-graph. + * + * The origin of the returned scene-graph node will match the top-left corner + * of the xdg_surface window geometry. + */ +struct wlr_scene_tree *wlr_scene_xdg_surface_create( + struct wlr_scene_tree *parent, struct wlr_xdg_surface *xdg_surface); + +/** + * Add a node displaying a layer_surface_v1 and all of its sub-surfaces to the + * scene-graph. + * + * The origin of the returned scene-graph node will match the top-left corner + * of the layer surface. + */ +struct wlr_scene_layer_surface_v1 *wlr_scene_layer_surface_v1_create( + struct wlr_scene_tree *parent, struct wlr_layer_surface_v1 *layer_surface); + +/** + * Configure a layer_surface_v1, position its scene node in accordance to its + * current state, and update the remaining usable area. + * + * full_area represents the entire area that may be used by the layer surface + * if its exclusive_zone is -1, and is usually the output dimensions. + * usable_area represents what remains of full_area that can be used if + * exclusive_zone is >= 0. usable_area is updated if the surface has a positive + * exclusive_zone, so that it can be used for the next layer surface. + */ +void wlr_scene_layer_surface_v1_configure( + struct wlr_scene_layer_surface_v1 *scene_layer_surface, + const struct wlr_box *full_area, struct wlr_box *usable_area); + +/** + * Add a node displaying a drag icon and all its sub-surfaces to the + * scene-graph. + */ +struct wlr_scene_tree *wlr_scene_drag_icon_create( + struct wlr_scene_tree *parent, struct wlr_drag_icon *drag_icon); + +#endif diff --git a/include/wlr/types/wlr_screencopy_v1.h b/include/wlr/types/wlr_screencopy_v1.h new file mode 100644 index 0000000..c1e990f --- /dev/null +++ b/include/wlr/types/wlr_screencopy_v1.h @@ -0,0 +1,63 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SCREENCOPY_V1_H +#define WLR_TYPES_WLR_SCREENCOPY_V1_H + +#include +#include +#include +#include + +struct wlr_screencopy_manager_v1 { + struct wl_global *global; + struct wl_list frames; // wlr_screencopy_frame_v1.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_screencopy_v1_client { + int ref; + struct wlr_screencopy_manager_v1 *manager; + struct wl_list damages; +}; + +struct wlr_screencopy_frame_v1 { + struct wl_resource *resource; + struct wlr_screencopy_v1_client *client; + struct wl_list link; // wlr_screencopy_manager_v1.frames + + uint32_t shm_format, dmabuf_format; // DRM format codes + struct wlr_box box; + int shm_stride; + + bool overlay_cursor, cursor_locked; + + bool with_damage; + + enum wlr_buffer_cap buffer_cap; + struct wlr_buffer *buffer; + + struct wlr_output *output; + struct wl_listener output_commit; + struct wl_listener output_destroy; + struct wl_listener output_enable; + + void *data; +}; + +struct wlr_screencopy_manager_v1 *wlr_screencopy_manager_v1_create( + struct wl_display *display); + +#endif diff --git a/include/wlr/types/wlr_seat.h b/include/wlr/types/wlr_seat.h new file mode 100644 index 0000000..0318b0f --- /dev/null +++ b/include/wlr/types/wlr_seat.h @@ -0,0 +1,752 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SEAT_H +#define WLR_TYPES_WLR_SEAT_H + +#include +#include +#include +#include +#include + +struct wlr_surface; + +#define WLR_SERIAL_RINGSET_SIZE 128 + +struct wlr_serial_range { + uint32_t min_incl; + uint32_t max_incl; +}; +struct wlr_serial_ringset { + struct wlr_serial_range data[WLR_SERIAL_RINGSET_SIZE]; + int end; + int count; +}; + +/** + * Contains state for a single client's bound wl_seat resource and can be used + * to issue input events to that client. The lifetime of these objects is + * managed by struct wlr_seat; some may be NULL. + */ +struct wlr_seat_client { + struct wl_client *client; + struct wlr_seat *seat; + struct wl_list link; + + // lists of wl_resource + struct wl_list resources; + struct wl_list pointers; + struct wl_list keyboards; + struct wl_list touches; + struct wl_list data_devices; + + struct { + struct wl_signal destroy; + } events; + + // set of serials which were sent to the client on this seat + // for use by wlr_seat_client_{next_serial,validate_event_serial} + struct wlr_serial_ringset serials; + bool needs_touch_frame; + + // When the client doesn't support high-resolution scroll, accumulate deltas + // until we can notify a discrete event. + // Some mice have a free spinning wheel, making possible to lock the wheel + // when the accumulator value is not 0. To avoid synchronization issues + // between the mouse wheel and the accumulators, store the last delta and + // when the scroll direction changes, reset the accumulator. + // Indexed by wlr_axis_orientation. + struct { + int32_t acc_discrete[2]; + int32_t last_discrete[2]; + double acc_axis[2]; + } value120; +}; + +struct wlr_touch_point { + int32_t touch_id; + struct wlr_surface *surface; // may be NULL if destroyed + struct wlr_seat_client *client; + + struct wlr_surface *focus_surface; + struct wlr_seat_client *focus_client; + double sx, sy; + + struct wl_listener surface_destroy; + struct wl_listener focus_surface_destroy; + struct wl_listener client_destroy; + + struct { + struct wl_signal destroy; + } events; + + struct wl_list link; +}; + +struct wlr_seat_pointer_grab; + +struct wlr_pointer_grab_interface { + void (*enter)(struct wlr_seat_pointer_grab *grab, + struct wlr_surface *surface, double sx, double sy); + void (*clear_focus)(struct wlr_seat_pointer_grab *grab); + void (*motion)(struct wlr_seat_pointer_grab *grab, uint32_t time_msec, + double sx, double sy); + uint32_t (*button)(struct wlr_seat_pointer_grab *grab, uint32_t time_msec, + uint32_t button, enum wl_pointer_button_state state); + void (*axis)(struct wlr_seat_pointer_grab *grab, uint32_t time_msec, + enum wl_pointer_axis orientation, double value, + int32_t value_discrete, enum wl_pointer_axis_source source, + enum wl_pointer_axis_relative_direction relative_direction); + void (*frame)(struct wlr_seat_pointer_grab *grab); + void (*cancel)(struct wlr_seat_pointer_grab *grab); +}; + +struct wlr_seat_keyboard_grab; + +struct wlr_keyboard_grab_interface { + void (*enter)(struct wlr_seat_keyboard_grab *grab, + struct wlr_surface *surface, const uint32_t keycodes[], + size_t num_keycodes, const struct wlr_keyboard_modifiers *modifiers); + void (*clear_focus)(struct wlr_seat_keyboard_grab *grab); + void (*key)(struct wlr_seat_keyboard_grab *grab, uint32_t time_msec, + uint32_t key, uint32_t state); + void (*modifiers)(struct wlr_seat_keyboard_grab *grab, + const struct wlr_keyboard_modifiers *modifiers); + void (*cancel)(struct wlr_seat_keyboard_grab *grab); +}; + +struct wlr_seat_touch_grab; + +struct wlr_touch_grab_interface { + uint32_t (*down)(struct wlr_seat_touch_grab *grab, uint32_t time_msec, + struct wlr_touch_point *point); + void (*up)(struct wlr_seat_touch_grab *grab, uint32_t time_msec, + struct wlr_touch_point *point); + void (*motion)(struct wlr_seat_touch_grab *grab, uint32_t time_msec, + struct wlr_touch_point *point); + void (*enter)(struct wlr_seat_touch_grab *grab, uint32_t time_msec, + struct wlr_touch_point *point); + void (*frame)(struct wlr_seat_touch_grab *grab); + // Cancel grab + void (*cancel)(struct wlr_seat_touch_grab *grab); + // Send wl_touch.cancel + void (*wl_cancel)(struct wlr_seat_touch_grab *grab, + struct wlr_surface *surface); +}; + +/** + * Passed to wlr_seat_touch_start_grab() to start a grab of the touch device. + * The grabber is responsible for handling touch events for the seat. + */ +struct wlr_seat_touch_grab { + const struct wlr_touch_grab_interface *interface; + struct wlr_seat *seat; + void *data; +}; + +/** + * Passed to wlr_seat_keyboard_start_grab() to start a grab of the keyboard. + * The grabber is responsible for handling keyboard events for the seat. + */ +struct wlr_seat_keyboard_grab { + const struct wlr_keyboard_grab_interface *interface; + struct wlr_seat *seat; + void *data; +}; + +/** + * Passed to wlr_seat_pointer_start_grab() to start a grab of the pointer. The + * grabber is responsible for handling pointer events for the seat. + */ +struct wlr_seat_pointer_grab { + const struct wlr_pointer_grab_interface *interface; + struct wlr_seat *seat; + void *data; +}; + +#define WLR_POINTER_BUTTONS_CAP 16 + +struct wlr_seat_pointer_state { + struct wlr_seat *seat; + struct wlr_seat_client *focused_client; + struct wlr_surface *focused_surface; + double sx, sy; + + struct wlr_seat_pointer_grab *grab; + struct wlr_seat_pointer_grab *default_grab; + + bool sent_axis_source; + enum wl_pointer_axis_source cached_axis_source; + + uint32_t buttons[WLR_POINTER_BUTTONS_CAP]; + size_t button_count; + uint32_t grab_button; + uint32_t grab_serial; + uint32_t grab_time; + + struct wl_listener surface_destroy; + + struct { + struct wl_signal focus_change; // struct wlr_seat_pointer_focus_change_event + } events; +}; + +struct wlr_seat_keyboard_state { + struct wlr_seat *seat; + struct wlr_keyboard *keyboard; + + struct wlr_seat_client *focused_client; + struct wlr_surface *focused_surface; + + struct wl_listener keyboard_destroy; + struct wl_listener keyboard_keymap; + struct wl_listener keyboard_repeat_info; + + struct wl_listener surface_destroy; + + struct wlr_seat_keyboard_grab *grab; + struct wlr_seat_keyboard_grab *default_grab; + + struct { + struct wl_signal focus_change; // struct wlr_seat_keyboard_focus_change_event + } events; +}; + +struct wlr_seat_touch_state { + struct wlr_seat *seat; + struct wl_list touch_points; // wlr_touch_point.link + + uint32_t grab_serial; + uint32_t grab_id; + + struct wlr_seat_touch_grab *grab; + struct wlr_seat_touch_grab *default_grab; +}; + +struct wlr_primary_selection_source; + +struct wlr_seat { + struct wl_global *global; + struct wl_display *display; + struct wl_list clients; + + char *name; + uint32_t capabilities; + uint32_t accumulated_capabilities; + struct timespec last_event; + + struct wlr_data_source *selection_source; + uint32_t selection_serial; + struct wl_list selection_offers; // wlr_data_offer.link + + struct wlr_primary_selection_source *primary_selection_source; + uint32_t primary_selection_serial; + + // `drag` goes away before `drag_source`, when the implicit grab ends + struct wlr_drag *drag; + struct wlr_data_source *drag_source; + uint32_t drag_serial; + struct wl_list drag_offers; // wlr_data_offer.link + + struct wlr_seat_pointer_state pointer_state; + struct wlr_seat_keyboard_state keyboard_state; + struct wlr_seat_touch_state touch_state; + + struct wl_listener display_destroy; + struct wl_listener selection_source_destroy; + struct wl_listener primary_selection_source_destroy; + struct wl_listener drag_source_destroy; + + struct { + struct wl_signal pointer_grab_begin; + struct wl_signal pointer_grab_end; + + struct wl_signal keyboard_grab_begin; + struct wl_signal keyboard_grab_end; + + struct wl_signal touch_grab_begin; + struct wl_signal touch_grab_end; + + // struct wlr_seat_pointer_request_set_cursor_event + struct wl_signal request_set_cursor; + + // Called when an application _wants_ to set the selection (user copies some data). + // Compositors should listen to this event and call wlr_seat_set_selection() + // if they want to accept the client's request. + struct wl_signal request_set_selection; // struct wlr_seat_request_set_selection_event + // Called after the data source is set for the selection. + struct wl_signal set_selection; + + // Called when an application _wants_ to set the primary selection (user selects some data). + // Compositors should listen to this event and call wlr_seat_set_primary_selection() + // if they want to accept the client's request. + struct wl_signal request_set_primary_selection; // struct wlr_seat_request_set_primary_selection_event + // Called after the primary selection source object is set. + struct wl_signal set_primary_selection; + + // struct wlr_seat_request_start_drag_event + struct wl_signal request_start_drag; + struct wl_signal start_drag; // struct wlr_drag + + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_seat_pointer_request_set_cursor_event { + struct wlr_seat_client *seat_client; + struct wlr_surface *surface; + uint32_t serial; + int32_t hotspot_x, hotspot_y; +}; + +struct wlr_seat_request_set_selection_event { + struct wlr_data_source *source; + uint32_t serial; +}; + +struct wlr_seat_request_set_primary_selection_event { + struct wlr_primary_selection_source *source; + uint32_t serial; +}; + +struct wlr_seat_request_start_drag_event { + struct wlr_drag *drag; + struct wlr_surface *origin; + uint32_t serial; +}; + +struct wlr_seat_pointer_focus_change_event { + struct wlr_seat *seat; + struct wlr_surface *old_surface, *new_surface; + double sx, sy; +}; + +struct wlr_seat_keyboard_focus_change_event { + struct wlr_seat *seat; + struct wlr_surface *old_surface, *new_surface; +}; + +/** + * Allocates a new struct wlr_seat and adds a wl_seat global to the display. + */ +struct wlr_seat *wlr_seat_create(struct wl_display *display, const char *name); +/** + * Destroys a seat, removes its wl_seat global and clears focus for all + * devices belonging to the seat. + */ +void wlr_seat_destroy(struct wlr_seat *wlr_seat); +/** + * Gets a struct wlr_seat_client for the specified client, or returns NULL if no + * client is bound for that client. + */ +struct wlr_seat_client *wlr_seat_client_for_wl_client(struct wlr_seat *wlr_seat, + struct wl_client *wl_client); +/** + * Updates the capabilities available on this seat. + * Will automatically send them to all clients. + */ +void wlr_seat_set_capabilities(struct wlr_seat *wlr_seat, + uint32_t capabilities); +/** + * Updates the name of this seat. + * Will automatically send it to all clients. + */ +void wlr_seat_set_name(struct wlr_seat *wlr_seat, const char *name); + +/** + * Whether or not the surface has pointer focus + */ +bool wlr_seat_pointer_surface_has_focus(struct wlr_seat *wlr_seat, + struct wlr_surface *surface); + +/** + * Send a pointer enter event to the given surface and consider it to be the + * focused surface for the pointer. This will send a leave event to the last + * surface that was entered. Coordinates for the enter event are surface-local. + * This function does not respect pointer grabs: you probably want + * wlr_seat_pointer_notify_enter() instead. + */ +void wlr_seat_pointer_enter(struct wlr_seat *wlr_seat, + struct wlr_surface *surface, double sx, double sy); + +/** + * Clear the focused surface for the pointer and leave all entered surfaces. + * This function does not respect pointer grabs: you probably want + * wlr_seat_pointer_notify_clear_focus() instead. + */ +void wlr_seat_pointer_clear_focus(struct wlr_seat *wlr_seat); + +/** + * Send a motion event to the surface with pointer focus. Coordinates for the + * motion event are surface-local. This function does not respect pointer grabs: + * you probably want wlr_seat_pointer_notify_motion() instead. + */ +void wlr_seat_pointer_send_motion(struct wlr_seat *wlr_seat, uint32_t time_msec, + double sx, double sy); + +/** + * Send a button event to the surface with pointer focus. Coordinates for the + * button event are surface-local. Returns the serial. This function does not + * respect pointer grabs: you probably want wlr_seat_pointer_notify_button() + * instead. + */ +uint32_t wlr_seat_pointer_send_button(struct wlr_seat *wlr_seat, + uint32_t time_msec, uint32_t button, enum wl_pointer_button_state state); + +/** + * Send an axis event to the surface with pointer focus. This function does not + * respect pointer grabs: you probably want wlr_seat_pointer_notify_axis() + * instead. + */ +void wlr_seat_pointer_send_axis(struct wlr_seat *wlr_seat, uint32_t time_msec, + enum wl_pointer_axis orientation, double value, + int32_t value_discrete, enum wl_pointer_axis_source source, + enum wl_pointer_axis_relative_direction relative_direction); + +/** + * Send a frame event to the surface with pointer focus. This function does not + * respect pointer grabs: you probably want wlr_seat_pointer_notify_frame() + * instead. + */ +void wlr_seat_pointer_send_frame(struct wlr_seat *wlr_seat); + +/** + * Notify the seat of a pointer enter event to the given surface and request it + * to be the focused surface for the pointer. Pass surface-local coordinates + * where the enter occurred. This will send a leave event to the currently- + * focused surface. Defers to any grab of the pointer. + */ +void wlr_seat_pointer_notify_enter(struct wlr_seat *wlr_seat, + struct wlr_surface *surface, double sx, double sy); + +/** + * Notify the seat of a pointer leave event to the currently-focused surface. + * Defers to any grab of the pointer. + */ +void wlr_seat_pointer_notify_clear_focus(struct wlr_seat *wlr_seat); + +/** + * Warp the pointer of this seat to the given surface-local coordinates, without + * generating motion events. + */ +void wlr_seat_pointer_warp(struct wlr_seat *wlr_seat, double sx, double sy); + +/** + * Notify the seat of motion over the given surface. Pass surface-local + * coordinates where the pointer motion occurred. Defers to any grab of the + * pointer. + */ +void wlr_seat_pointer_notify_motion(struct wlr_seat *wlr_seat, + uint32_t time_msec, double sx, double sy); + +/** + * Notify the seat that a button has been pressed. Returns the serial of the + * button press or zero if no button press was sent. Defers to any grab of the + * pointer. + */ +uint32_t wlr_seat_pointer_notify_button(struct wlr_seat *wlr_seat, + uint32_t time_msec, uint32_t button, enum wl_pointer_button_state state); + +/** + * Notify the seat of an axis event. Defers to any grab of the pointer. + */ +void wlr_seat_pointer_notify_axis(struct wlr_seat *wlr_seat, uint32_t time_msec, + enum wl_pointer_axis orientation, double value, + int32_t value_discrete, enum wl_pointer_axis_source source, + enum wl_pointer_axis_relative_direction relative_direction); + +/** + * Notify the seat of a frame event. Frame events are sent to end a group of + * events that logically belong together. Motion, button and axis events should + * all be followed by a frame event. Defers to any grab of the pointer. + */ +void wlr_seat_pointer_notify_frame(struct wlr_seat *wlr_seat); + +/** + * Start a grab of the pointer of this seat. The grabber is responsible for + * handling all pointer events until the grab ends. + */ +void wlr_seat_pointer_start_grab(struct wlr_seat *wlr_seat, + struct wlr_seat_pointer_grab *grab); + +/** + * End the grab of the pointer of this seat. This reverts the grab back to the + * default grab for the pointer. + */ +void wlr_seat_pointer_end_grab(struct wlr_seat *wlr_seat); + +/** + * Whether or not the pointer has a grab other than the default grab. + */ +bool wlr_seat_pointer_has_grab(struct wlr_seat *seat); + +/** + * Set this keyboard as the active keyboard for the seat. + */ +void wlr_seat_set_keyboard(struct wlr_seat *seat, struct wlr_keyboard *keyboard); + +/** + * Get the active keyboard for the seat. + */ +struct wlr_keyboard *wlr_seat_get_keyboard(struct wlr_seat *seat); + +/** + * Send the keyboard key to focused keyboard resources. This function does not + * respect keyboard grabs: you probably want wlr_seat_keyboard_notify_key() + * instead. + */ +void wlr_seat_keyboard_send_key(struct wlr_seat *seat, uint32_t time_msec, + uint32_t key, uint32_t state); + +/** + * Send the modifier state to focused keyboard resources. This function does + * not respect keyboard grabs: you probably want + * wlr_seat_keyboard_notify_modifiers() instead. + */ +void wlr_seat_keyboard_send_modifiers(struct wlr_seat *seat, + const struct wlr_keyboard_modifiers *modifiers); + +/** + * Send a keyboard enter event to the given surface and consider it to be the + * focused surface for the keyboard. This will send a leave event to the last + * surface that was entered. This function does not respect keyboard grabs: you + * probably want wlr_seat_keyboard_notify_enter() instead. + */ +void wlr_seat_keyboard_enter(struct wlr_seat *seat, + struct wlr_surface *surface, const uint32_t keycodes[], size_t num_keycodes, + const struct wlr_keyboard_modifiers *modifiers); + +/** + * Clear the focused surface for the keyboard and leave all entered surfaces. + * This function does not respect keyboard grabs: you probably want + * wlr_seat_keyboard_notify_clear_focus() instead. + */ +void wlr_seat_keyboard_clear_focus(struct wlr_seat *wlr_seat); + +/** + * Notify the seat that a key has been pressed on the keyboard. Defers to any + * keyboard grabs. + */ +void wlr_seat_keyboard_notify_key(struct wlr_seat *seat, uint32_t time_msec, + uint32_t key, uint32_t state); + +/** + * Notify the seat that the modifiers for the keyboard have changed. Defers to + * any keyboard grabs. + */ +void wlr_seat_keyboard_notify_modifiers(struct wlr_seat *seat, + const struct wlr_keyboard_modifiers *modifiers); + +/** + * Notify the seat that the keyboard focus has changed and request it to be the + * focused surface for this keyboard. Defers to any current grab of the seat's + * keyboard. + */ +void wlr_seat_keyboard_notify_enter(struct wlr_seat *seat, + struct wlr_surface *surface, const uint32_t keycodes[], size_t num_keycodes, + const struct wlr_keyboard_modifiers *modifiers); + +/** + * Notify the seat of a keyboard leave event to the currently-focused surface. + * Defers to any keyboard grabs. + */ +void wlr_seat_keyboard_notify_clear_focus(struct wlr_seat *wlr_seat); + +/** + * Start a grab of the keyboard of this seat. The grabber is responsible for + * handling all keyboard events until the grab ends. + */ +void wlr_seat_keyboard_start_grab(struct wlr_seat *wlr_seat, + struct wlr_seat_keyboard_grab *grab); + +/** + * End the grab of the keyboard of this seat. This reverts the grab back to the + * default grab for the keyboard. + */ +void wlr_seat_keyboard_end_grab(struct wlr_seat *wlr_seat); + +/** + * Whether or not the keyboard has a grab other than the default grab + */ +bool wlr_seat_keyboard_has_grab(struct wlr_seat *seat); + +/** + * Get the active touch point with the given `touch_id`. If the touch point does + * not exist or is no longer active, returns NULL. + */ +struct wlr_touch_point *wlr_seat_touch_get_point(struct wlr_seat *seat, + int32_t touch_id); + +/** + * Notify the seat that the touch point given by `touch_id` has entered a new + * surface. The surface is required. To clear focus, use + * wlr_seat_touch_point_clear_focus(). + */ +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); + +/** + * Clear the focused surface for the touch point given by `touch_id`. + */ +void wlr_seat_touch_point_clear_focus(struct wlr_seat *seat, uint32_t time_msec, + int32_t touch_id); + +/** + * Send a touch down event to the client of the given surface. All future touch + * events for this point will go to this surface. If the touch down is valid, + * this will add a new touch point with the given `touch_id`. The touch down may + * not be valid if the surface seat client does not accept touch input. + * Coordinates are surface-local. This function does not respect touch grabs: + * you probably want wlr_seat_touch_notify_down() instead. + */ +uint32_t wlr_seat_touch_send_down(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t time_msec, + int32_t touch_id, double sx, double sy); + +/** + * Send a touch up event for the touch point given by the `touch_id`. The event + * will go to the client for the surface given in the corresponding touch down + * event. This will remove the touch point. This function does not respect touch + * grabs: you probably want wlr_seat_touch_notify_up() instead. + */ +void wlr_seat_touch_send_up(struct wlr_seat *seat, uint32_t time_msec, + int32_t touch_id); + +/** + * Send a touch motion event for the touch point given by the `touch_id`. The + * event will go to the client for the surface given in the corresponding touch + * down event. This function does not respect touch grabs: you probably want + * wlr_seat_touch_notify_motion() instead. + */ +void wlr_seat_touch_send_motion(struct wlr_seat *seat, uint32_t time_msec, + int32_t touch_id, double sx, double sy); + +/** + * Notify the seat that this is a global gesture and the client should cancel + * processing it. The event will go to the client for the surface given. + * This function does not respect touch grabs: you probably want + * wlr_seat_touch_notify_cancel() instead. + */ +void wlr_seat_touch_send_cancel(struct wlr_seat *seat, struct wlr_surface *surface); + +void wlr_seat_touch_send_frame(struct wlr_seat *seat); + +/** + * Notify the seat of a touch down on the given surface. Defers to any grab of + * the touch device. + */ +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); + +/** + * Notify the seat that the touch point given by `touch_id` is up. Defers to any + * grab of the touch device. + */ +void wlr_seat_touch_notify_up(struct wlr_seat *seat, uint32_t time_msec, + int32_t touch_id); + +/** + * Notify the seat that the touch point given by `touch_id` has moved. Defers to + * any grab of the touch device. The seat should be notified of touch motion + * even if the surface is not the owner of the touch point for processing by + * grabs. + */ +void wlr_seat_touch_notify_motion(struct wlr_seat *seat, uint32_t time_msec, + int32_t touch_id, double sx, double sy); + +/** + * Notify the seat that this is a global gesture and the client should + * cancel processing it. Defers to any grab of the touch device. + */ +void wlr_seat_touch_notify_cancel(struct wlr_seat *seat, + struct wlr_surface *surface); + +void wlr_seat_touch_notify_frame(struct wlr_seat *seat); + +/** + * How many touch points are currently down for the seat. + */ +int wlr_seat_touch_num_points(struct wlr_seat *seat); + +/** + * Start a grab of the touch device of this seat. The grabber is responsible for + * handling all touch events until the grab ends. + */ +void wlr_seat_touch_start_grab(struct wlr_seat *wlr_seat, + struct wlr_seat_touch_grab *grab); + +/** + * End the grab of the touch device of this seat. This reverts the grab back to + * the default grab for the touch device. + */ +void wlr_seat_touch_end_grab(struct wlr_seat *wlr_seat); + +/** + * Whether or not the seat has a touch grab other than the default grab. + */ +bool wlr_seat_touch_has_grab(struct wlr_seat *seat); + +/** + * Check whether this serial is valid to start a pointer grab action. + */ +bool wlr_seat_validate_pointer_grab_serial(struct wlr_seat *seat, + struct wlr_surface *origin, uint32_t serial); + +/** + * Check whether this serial is valid to start a touch grab action. If it's the + * case and point_ptr is non-NULL, `*point_ptr` is set to the touch point matching + * the serial. + */ +bool wlr_seat_validate_touch_grab_serial(struct wlr_seat *seat, + struct wlr_surface *origin, uint32_t serial, + struct wlr_touch_point **point_ptr); + +/** + * Return a new serial (from wl_display_serial_next()) for the client, and + * update the seat client's set of valid serials. Use this for all input + * events; otherwise wlr_seat_client_validate_event_serial() may fail when + * handed a correctly functioning client's request serials. + */ +uint32_t wlr_seat_client_next_serial(struct wlr_seat_client *client); + +/** + * Return true if the serial number could have been produced by + * wlr_seat_client_next_serial() and is "older" (by less than UINT32_MAX/2) than + * the current display serial value. + * + * This function should have no false negatives, and the only false positive + * responses allowed are for elements that are still "older" than the current + * display serial value and also older than all serial values remaining in + * the seat client's serial ring buffer, if that buffer is also full. + */ +bool wlr_seat_client_validate_event_serial(struct wlr_seat_client *client, + uint32_t serial); + +/** + * Get a seat client from a seat resource. Returns NULL if inert. + */ +struct wlr_seat_client *wlr_seat_client_from_resource( + struct wl_resource *resource); + +/** + * Get a seat client from a pointer resource. Returns NULL if inert. + */ +struct wlr_seat_client *wlr_seat_client_from_pointer_resource( + struct wl_resource *resource); + +/** + * Check whether a surface has bound to touch events. + */ +bool wlr_surface_accepts_touch(struct wlr_seat *wlr_seat, struct wlr_surface *surface); + +#endif diff --git a/include/wlr/types/wlr_security_context_v1.h b/include/wlr/types/wlr_security_context_v1.h new file mode 100644 index 0000000..028e9bd --- /dev/null +++ b/include/wlr/types/wlr_security_context_v1.h @@ -0,0 +1,55 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SECURITY_CONTEXT_V1_H +#define WLR_TYPES_WLR_SECURITY_CONTEXT_V1_H + +#include + +/** + * An implementation of the security context protocol. + * + * Compositors can create this manager, setup a filter for Wayland globals via + * wl_display_set_global_filter(), and inside the filter query the security + * context state via wlr_security_context_manager_v1_lookup_client(). + */ +struct wlr_security_context_manager_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + struct wl_signal commit; // struct wlr_security_context_v1_commit_event + } events; + + void *data; + + // private state + + struct wl_list contexts; // wlr_security_context_v1.link + + struct wl_listener display_destroy; +}; + +struct wlr_security_context_v1_state { + char *sandbox_engine; // may be NULL + char *app_id; // may be NULL + char *instance_id; // may be NULL +}; + +struct wlr_security_context_v1_commit_event { + const struct wlr_security_context_v1_state *state; + // Client which created the security context + struct wl_client *parent_client; +}; + +struct wlr_security_context_manager_v1 *wlr_security_context_manager_v1_create( + struct wl_display *display); +const struct wlr_security_context_v1_state *wlr_security_context_manager_v1_lookup_client( + struct wlr_security_context_manager_v1 *manager, struct wl_client *client); + +#endif diff --git a/include/wlr/types/wlr_server_decoration.h b/include/wlr/types/wlr_server_decoration.h new file mode 100644 index 0000000..7e78c5f --- /dev/null +++ b/include/wlr/types/wlr_server_decoration.h @@ -0,0 +1,84 @@ +/* + * This protocol is obsolete and will be removed in a future version. The + * recommended replacement is xdg-decoration. + */ + +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SERVER_DECORATION_H +#define WLR_TYPES_WLR_SERVER_DECORATION_H + +#include + +/** + * Possible values to use in request_mode and the event mode. Same as + * org_kde_kwin_server_decoration_manager_mode. + */ +enum wlr_server_decoration_manager_mode { + /** + * Undecorated: The surface is not decorated at all, neither server nor + * client-side. An example is a popup surface which should not be + * decorated. + */ + WLR_SERVER_DECORATION_MANAGER_MODE_NONE = 0, + /** + * Client-side decoration: The decoration is part of the surface and the + * client. + */ + WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT = 1, + /** + * Server-side decoration: The server embeds the surface into a decoration + * frame. + */ + WLR_SERVER_DECORATION_MANAGER_MODE_SERVER = 2, +}; + +/** + * A decoration negotiation interface which implements the KDE protocol. + */ +struct wlr_server_decoration_manager { + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link() + struct wl_list decorations; // wlr_server_decoration.link + + uint32_t default_mode; // enum wlr_server_decoration_manager_mode + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_decoration; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_server_decoration { + struct wl_resource *resource; + struct wlr_surface *surface; + struct wl_list link; + + uint32_t mode; // enum wlr_server_decoration_manager_mode + + struct { + struct wl_signal destroy; + struct wl_signal mode; + } events; + + struct wl_listener surface_destroy_listener; + + void *data; +}; + +struct wlr_server_decoration_manager *wlr_server_decoration_manager_create( + struct wl_display *display); +void wlr_server_decoration_manager_set_default_mode( + struct wlr_server_decoration_manager *manager, uint32_t default_mode); + +#endif diff --git a/include/wlr/types/wlr_session_lock_v1.h b/include/wlr/types/wlr_session_lock_v1.h new file mode 100644 index 0000000..058bf0d --- /dev/null +++ b/include/wlr/types/wlr_session_lock_v1.h @@ -0,0 +1,108 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SESSION_LOCK_H +#define WLR_TYPES_WLR_SESSION_LOCK_H + +#include +#include +#include +#include + +struct wlr_session_lock_manager_v1 { + struct wl_global *global; + + struct { + struct wl_signal new_lock; // struct wlr_session_lock_v1 + struct wl_signal destroy; + } events; + + void *data; + + // private state + + struct wl_listener display_destroy; +}; + +struct wlr_session_lock_v1 { + struct wl_resource *resource; + + struct wl_list surfaces; // struct wlr_session_lock_surface_v1.link + + struct { + struct wl_signal new_surface; // struct wlr_session_lock_surface_v1 + struct wl_signal unlock; + struct wl_signal destroy; + } events; + + void *data; + + // private state + + bool locked_sent; +}; + +struct wlr_session_lock_surface_v1_state { + 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; + + uint32_t width, height; +}; + +struct wlr_session_lock_surface_v1 { + struct wl_resource *resource; + struct wl_list link; // wlr_session_lock_v1.surfaces + + struct wlr_output *output; + struct wlr_surface *surface; + + bool configured; + + struct wl_list configure_list; // wlr_session_lock_surface_v1_configure.link + + struct wlr_session_lock_surface_v1_state current; + struct wlr_session_lock_surface_v1_state pending; + + struct { + struct wl_signal destroy; + } events; + + void *data; + + // private state + + struct wlr_surface_synced synced; + + struct wl_listener output_destroy; +}; + +struct wlr_session_lock_manager_v1 *wlr_session_lock_manager_v1_create( + struct wl_display *display); + +void wlr_session_lock_v1_send_locked(struct wlr_session_lock_v1 *lock); +void wlr_session_lock_v1_destroy(struct wlr_session_lock_v1 *lock); + +uint32_t wlr_session_lock_surface_v1_configure( + struct wlr_session_lock_surface_v1 *lock_surface, + uint32_t width, uint32_t height); + +/** + * Get a struct wlr_session_lock_surface_v1 from a struct wlr_surface. + * + * Returns NULL if the surface has a different role or if the lock surface + * has been destroyed. + */ +struct wlr_session_lock_surface_v1 *wlr_session_lock_surface_v1_try_from_wlr_surface( + struct wlr_surface *surface); + +#endif diff --git a/include/wlr/types/wlr_shm.h b/include/wlr/types/wlr_shm.h new file mode 100644 index 0000000..396fc83 --- /dev/null +++ b/include/wlr/types/wlr_shm.h @@ -0,0 +1,44 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SHM_H +#define WLR_TYPES_WLR_SHM_H + +#include + +struct wlr_renderer; + +/** + * Shared memory buffer interface. + * + * The buffers created via this interface are not safe to use from different + * threads. + * + * Currently, accessing two buffers concurrently via + * wlr_buffer_begin_data_ptr_access() will return an error. + */ +struct wlr_shm; + +/** + * Create the wl_shm global. + * + * Compositors using struct wlr_renderer should use wlr_shm_create_with_renderer() + * instead. + */ +struct wlr_shm *wlr_shm_create(struct wl_display *display, uint32_t version, + const uint32_t *formats, size_t formats_len); + +/** + * Create the wl_shm global. + * + * The pixel formats advertised to clients are taken from the struct wlr_renderer. + */ +struct wlr_shm *wlr_shm_create_with_renderer(struct wl_display *display, + uint32_t version, struct wlr_renderer *renderer); + +#endif diff --git a/include/wlr/types/wlr_single_pixel_buffer_v1.h b/include/wlr/types/wlr_single_pixel_buffer_v1.h new file mode 100644 index 0000000..747d2a6 --- /dev/null +++ b/include/wlr/types/wlr_single_pixel_buffer_v1.h @@ -0,0 +1,19 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SINGLE_PIXEL_BUFFER_V1_H +#define WLR_TYPES_WLR_SINGLE_PIXEL_BUFFER_V1_H + +#include + +struct wlr_single_pixel_buffer_manager_v1; + +struct wlr_single_pixel_buffer_manager_v1 *wlr_single_pixel_buffer_manager_v1_create( + struct wl_display *display); + +#endif diff --git a/include/wlr/types/wlr_subcompositor.h b/include/wlr/types/wlr_subcompositor.h new file mode 100644 index 0000000..bd3899b --- /dev/null +++ b/include/wlr/types/wlr_subcompositor.h @@ -0,0 +1,83 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SUBCOMPOSITOR_H +#define WLR_TYPES_WLR_SUBCOMPOSITOR_H + +#include +#include +#include +#include + +/** + * The sub-surface state describing the sub-surface's relationship with its + * parent. Contrary to other states, this one is not applied on surface commit. + * Instead, it's applied on parent surface commit. + */ +struct wlr_subsurface_parent_state { + int32_t x, y; + struct wl_list link; + + // private state + + struct wlr_surface_synced *synced; +}; + +struct wlr_subsurface { + struct wl_resource *resource; + struct wlr_surface *surface; + struct wlr_surface *parent; + + struct wlr_subsurface_parent_state current, pending; + + uint32_t cached_seq; + bool has_cache; + + bool synchronized; + bool added; + + struct wl_listener surface_client_commit; + struct wl_listener parent_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; + + // private state + + struct wlr_surface_synced parent_synced; + + struct { + int32_t x, y; + } previous; +}; + +struct wlr_subcompositor { + struct wl_global *global; + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; +}; + +/** + * Get a struct wlr_subsurface from a struct wlr_surface. + * + * Returns NULL if the surface doesn't have the subsurface role or if + * the subsurface has been destroyed. + */ +struct wlr_subsurface *wlr_subsurface_try_from_wlr_surface( + struct wlr_surface *surface); + +struct wlr_subcompositor *wlr_subcompositor_create(struct wl_display *display); + +#endif diff --git a/include/wlr/types/wlr_switch.h b/include/wlr/types/wlr_switch.h new file mode 100644 index 0000000..641df19 --- /dev/null +++ b/include/wlr/types/wlr_switch.h @@ -0,0 +1,60 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SWITCH_H +#define WLR_TYPES_WLR_SWITCH_H + +#include +#include +#include + +struct wlr_switch_impl; + +/** + * A switch input device. Typically a switch input device can indicate whether + * a laptop lid is opened or closed, or whether tablet mode is enabled. + * + * See https://wayland.freedesktop.org/libinput/doc/latest/switches.html + */ +struct wlr_switch { + struct wlr_input_device base; + + const struct wlr_switch_impl *impl; + + struct { + struct wl_signal toggle; // struct wlr_switch_toggle_event + } events; + + void *data; +}; + +enum wlr_switch_type { + WLR_SWITCH_TYPE_LID, + WLR_SWITCH_TYPE_TABLET_MODE, +}; + +enum wlr_switch_state { + WLR_SWITCH_STATE_OFF = 0, + WLR_SWITCH_STATE_ON, +}; + +struct wlr_switch_toggle_event { + uint32_t time_msec; + enum wlr_switch_type switch_type; + enum wlr_switch_state switch_state; +}; + +/** + * Get a struct wlr_switch from a struct wlr_input_device. + * + * Asserts that the input device is a switch. + */ +struct wlr_switch *wlr_switch_from_input_device( + struct wlr_input_device *input_device); + +#endif diff --git a/include/wlr/types/wlr_tablet_pad.h b/include/wlr/types/wlr_tablet_pad.h new file mode 100644 index 0000000..f19505a --- /dev/null +++ b/include/wlr/types/wlr_tablet_pad.h @@ -0,0 +1,103 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_TABLET_PAD_H +#define WLR_TYPES_WLR_TABLET_PAD_H + +#include +#include +#include + +/* + * NOTE: the wlr tablet pad implementation does not currently support tablets + * with more than one mode. I don't own any such hardware so I cannot test it + * and it is too complicated to make a meaningful implementation of blindly. + */ + +struct wlr_tablet_pad_impl; + +struct wlr_tablet_pad { + struct wlr_input_device base; + + const struct wlr_tablet_pad_impl *impl; + + struct { + struct wl_signal button; + struct wl_signal ring; + struct wl_signal strip; + struct wl_signal attach_tablet; // struct wlr_tablet_tool + } events; + + size_t button_count; + size_t ring_count; + size_t strip_count; + + struct wl_list groups; // wlr_tablet_pad_group.link + struct wl_array paths; // char * + + void *data; +}; + +struct wlr_tablet_pad_group { + struct wl_list link; + + size_t button_count; + unsigned int *buttons; + + size_t strip_count; + unsigned int *strips; + + size_t ring_count; + unsigned int *rings; + + unsigned int mode_count; +}; + +struct wlr_tablet_pad_button_event { + uint32_t time_msec; + uint32_t button; + enum wlr_button_state state; + unsigned int mode; + unsigned int group; +}; + +enum wlr_tablet_pad_ring_source { + WLR_TABLET_PAD_RING_SOURCE_UNKNOWN, + WLR_TABLET_PAD_RING_SOURCE_FINGER, +}; + +struct wlr_tablet_pad_ring_event { + uint32_t time_msec; + enum wlr_tablet_pad_ring_source source; + uint32_t ring; + double position; + unsigned int mode; +}; + +enum wlr_tablet_pad_strip_source { + WLR_TABLET_PAD_STRIP_SOURCE_UNKNOWN, + WLR_TABLET_PAD_STRIP_SOURCE_FINGER, +}; + +struct wlr_tablet_pad_strip_event { + uint32_t time_msec; + enum wlr_tablet_pad_strip_source source; + uint32_t strip; + double position; + unsigned int mode; +}; + +/** + * Get a struct wlr_tablet_pad from a struct wlr_input_device. + * + * Asserts that the input device is a tablet pad. + */ +struct wlr_tablet_pad *wlr_tablet_pad_from_input_device( + struct wlr_input_device *); + +#endif diff --git a/include/wlr/types/wlr_tablet_tool.h b/include/wlr/types/wlr_tablet_tool.h new file mode 100644 index 0000000..e71c923 --- /dev/null +++ b/include/wlr/types/wlr_tablet_tool.h @@ -0,0 +1,156 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_TABLET_TOOL_H +#define WLR_TYPES_TABLET_TOOL_H + +#include +#include +#include + +/* + * Copy+Paste from libinput, but this should neither use libinput, nor + * tablet-unstable-v2 headers, so we can't include them + */ +enum wlr_tablet_tool_type { + /** A generic pen */ + WLR_TABLET_TOOL_TYPE_PEN = 1, + /** Eraser */ + WLR_TABLET_TOOL_TYPE_ERASER, + /** A paintbrush-like tool */ + WLR_TABLET_TOOL_TYPE_BRUSH, + /** Physical drawing tool, e.g. Wacom Inking Pen */ + WLR_TABLET_TOOL_TYPE_PENCIL, + /** An airbrush-like tool */ + WLR_TABLET_TOOL_TYPE_AIRBRUSH, + /** A mouse bound to the tablet */ + WLR_TABLET_TOOL_TYPE_MOUSE, + /** A mouse tool with a lens */ + WLR_TABLET_TOOL_TYPE_LENS, + /** A rotary device with positional and rotation data */ + WLR_TABLET_TOOL_TYPE_TOTEM, + +}; + +struct wlr_tablet_tool { + enum wlr_tablet_tool_type type; + uint64_t hardware_serial; + uint64_t hardware_wacom; + + // Capabilities + bool tilt; + bool pressure; + bool distance; + bool rotation; + bool slider; + bool wheel; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_tablet_impl; + +struct wlr_tablet { + struct wlr_input_device base; + + const struct wlr_tablet_impl *impl; + + uint16_t usb_vendor_id, usb_product_id; // zero if unset + double width_mm, height_mm; + + struct { + struct wl_signal axis; + struct wl_signal proximity; + struct wl_signal tip; + struct wl_signal button; + } events; + + struct wl_array paths; // char * + + void *data; +}; + +enum wlr_tablet_tool_axes { + WLR_TABLET_TOOL_AXIS_X = 1 << 0, + WLR_TABLET_TOOL_AXIS_Y = 1 << 1, + WLR_TABLET_TOOL_AXIS_DISTANCE = 1 << 2, + WLR_TABLET_TOOL_AXIS_PRESSURE = 1 << 3, + WLR_TABLET_TOOL_AXIS_TILT_X = 1 << 4, + WLR_TABLET_TOOL_AXIS_TILT_Y = 1 << 5, + WLR_TABLET_TOOL_AXIS_ROTATION = 1 << 6, + WLR_TABLET_TOOL_AXIS_SLIDER = 1 << 7, + WLR_TABLET_TOOL_AXIS_WHEEL = 1 << 8, +}; + +struct wlr_tablet_tool_axis_event { + struct wlr_tablet *tablet; + struct wlr_tablet_tool *tool; + + uint32_t time_msec; + uint32_t updated_axes; + // From 0..1 + double x, y; + // Relative to last event + double dx, dy; + double pressure; + double distance; + double tilt_x, tilt_y; + double rotation; + double slider; + double wheel_delta; +}; + +enum wlr_tablet_tool_proximity_state { + WLR_TABLET_TOOL_PROXIMITY_OUT, + WLR_TABLET_TOOL_PROXIMITY_IN, +}; + +struct wlr_tablet_tool_proximity_event { + struct wlr_tablet *tablet; + struct wlr_tablet_tool *tool; + uint32_t time_msec; + // From 0..1 + double x, y; + enum wlr_tablet_tool_proximity_state state; +}; + +enum wlr_tablet_tool_tip_state { + WLR_TABLET_TOOL_TIP_UP, + WLR_TABLET_TOOL_TIP_DOWN, +}; + +struct wlr_tablet_tool_tip_event { + struct wlr_tablet *tablet; + struct wlr_tablet_tool *tool; + uint32_t time_msec; + // From 0..1 + double x, y; + enum wlr_tablet_tool_tip_state state; +}; + +struct wlr_tablet_tool_button_event { + struct wlr_tablet *tablet; + struct wlr_tablet_tool *tool; + uint32_t time_msec; + uint32_t button; + enum wlr_button_state state; +}; + +/** + * Get a struct wlr_tablet from a struct wlr_input_device. + * + * Asserts that the input device is a tablet tool. + */ +struct wlr_tablet *wlr_tablet_from_input_device( + struct wlr_input_device *input_device); + +#endif diff --git a/include/wlr/types/wlr_tablet_v2.h b/include/wlr/types/wlr_tablet_v2.h new file mode 100644 index 0000000..d3a6594 --- /dev/null +++ b/include/wlr/types/wlr_tablet_v2.h @@ -0,0 +1,332 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_TABLET_V2_H +#define WLR_TYPES_WLR_TABLET_V2_H + +#include +#include + +#include "tablet-unstable-v2-protocol.h" + +/* This can probably be even lower,the tools don't have a lot of buttons */ +#define WLR_TABLET_V2_TOOL_BUTTONS_CAP 16 + +struct wlr_input_device; + +struct wlr_tablet_pad_v2_grab_interface; + +struct wlr_tablet_pad_v2_grab { + const struct wlr_tablet_pad_v2_grab_interface *interface; + struct wlr_tablet_v2_tablet_pad *pad; + void *data; +}; + +struct wlr_tablet_tool_v2_grab_interface; + +struct wlr_tablet_tool_v2_grab { + const struct wlr_tablet_tool_v2_grab_interface *interface; + struct wlr_tablet_v2_tablet_tool *tool; + void *data; +}; + +struct wlr_tablet_client_v2; +struct wlr_tablet_tool_client_v2; +struct wlr_tablet_pad_client_v2; + +struct wlr_tablet_manager_v2 { + struct wl_global *wl_global; + struct wl_list clients; // wlr_tablet_manager_client_v2.link + struct wl_list seats; // wlr_tablet_seat_v2.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_tablet_v2_tablet { + struct wl_list link; // wlr_tablet_seat_v2.tablets + struct wlr_tablet *wlr_tablet; + struct wlr_input_device *wlr_device; + struct wl_list clients; // wlr_tablet_client_v2.tablet_link + + struct wl_listener tablet_destroy; + + struct wlr_tablet_client_v2 *current_client; +}; + +struct wlr_tablet_v2_tablet_tool { + struct wl_list link; // wlr_tablet_seat_v2.tablets + struct wlr_tablet_tool *wlr_tool; + struct wl_list clients; // wlr_tablet_tool_client_v2.tool_link + + struct wl_listener tool_destroy; + + struct wlr_tablet_tool_client_v2 *current_client; + struct wlr_surface *focused_surface; + struct wl_listener surface_destroy; + + struct wlr_tablet_tool_v2_grab *grab; + struct wlr_tablet_tool_v2_grab default_grab; + + uint32_t proximity_serial; + bool is_down; + uint32_t down_serial; + size_t num_buttons; + uint32_t pressed_buttons[WLR_TABLET_V2_TOOL_BUTTONS_CAP]; + uint32_t pressed_serials[WLR_TABLET_V2_TOOL_BUTTONS_CAP]; + + struct { + struct wl_signal set_cursor; // struct wlr_tablet_v2_event_cursor + } events; +}; + +struct wlr_tablet_v2_tablet_pad { + struct wl_list link; // wlr_tablet_seat_v2.pads + struct wlr_tablet_pad *wlr_pad; + struct wlr_input_device *wlr_device; + struct wl_list clients; // wlr_tablet_pad_client_v2.pad_link + + size_t group_count; + uint32_t *groups; + + struct wl_listener pad_destroy; + + struct wlr_tablet_pad_client_v2 *current_client; + struct wlr_tablet_pad_v2_grab *grab; + struct wlr_tablet_pad_v2_grab default_grab; + + struct { + struct wl_signal button_feedback; // struct wlr_tablet_v2_event_feedback + struct wl_signal strip_feedback; // struct wlr_tablet_v2_event_feedback + struct wl_signal ring_feedback; // struct wlr_tablet_v2_event_feedback + } events; +}; + +struct wlr_tablet_v2_event_cursor { + struct wlr_surface *surface; + uint32_t serial; + int32_t hotspot_x; + int32_t hotspot_y; + struct wlr_seat_client *seat_client; +}; + +struct wlr_tablet_v2_event_feedback { + const char *description; + size_t index; + uint32_t serial; +}; + +struct wlr_tablet_v2_tablet *wlr_tablet_create( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat, + struct wlr_input_device *wlr_device); + +struct wlr_tablet_v2_tablet_pad *wlr_tablet_pad_create( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat, + struct wlr_input_device *wlr_device); + +struct wlr_tablet_v2_tablet_tool *wlr_tablet_tool_create( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat, + struct wlr_tablet_tool *wlr_tool); + +struct wlr_tablet_manager_v2 *wlr_tablet_v2_create(struct wl_display *display); + +void wlr_send_tablet_v2_tablet_tool_proximity_in( + struct wlr_tablet_v2_tablet_tool *tool, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); + +void wlr_send_tablet_v2_tablet_tool_down(struct wlr_tablet_v2_tablet_tool *tool); +void wlr_send_tablet_v2_tablet_tool_up(struct wlr_tablet_v2_tablet_tool *tool); + +void wlr_send_tablet_v2_tablet_tool_motion( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y); + +void wlr_send_tablet_v2_tablet_tool_pressure( + struct wlr_tablet_v2_tablet_tool *tool, double pressure); + +void wlr_send_tablet_v2_tablet_tool_distance( + struct wlr_tablet_v2_tablet_tool *tool, double distance); + +void wlr_send_tablet_v2_tablet_tool_tilt( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y); + +void wlr_send_tablet_v2_tablet_tool_rotation( + struct wlr_tablet_v2_tablet_tool *tool, double degrees); + +void wlr_send_tablet_v2_tablet_tool_slider( + struct wlr_tablet_v2_tablet_tool *tool, double position); + +void wlr_send_tablet_v2_tablet_tool_wheel( + struct wlr_tablet_v2_tablet_tool *tool, double degrees, int32_t clicks); + +void wlr_send_tablet_v2_tablet_tool_proximity_out( + struct wlr_tablet_v2_tablet_tool *tool); + +void wlr_send_tablet_v2_tablet_tool_button( + struct wlr_tablet_v2_tablet_tool *tool, uint32_t button, + enum zwp_tablet_pad_v2_button_state state); + + + +void wlr_tablet_v2_tablet_tool_notify_proximity_in( + struct wlr_tablet_v2_tablet_tool *tool, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); + +void wlr_tablet_v2_tablet_tool_notify_down(struct wlr_tablet_v2_tablet_tool *tool); +void wlr_tablet_v2_tablet_tool_notify_up(struct wlr_tablet_v2_tablet_tool *tool); + +void wlr_tablet_v2_tablet_tool_notify_motion( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y); + +void wlr_tablet_v2_tablet_tool_notify_pressure( + struct wlr_tablet_v2_tablet_tool *tool, double pressure); + +void wlr_tablet_v2_tablet_tool_notify_distance( + struct wlr_tablet_v2_tablet_tool *tool, double distance); + +void wlr_tablet_v2_tablet_tool_notify_tilt( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y); + +void wlr_tablet_v2_tablet_tool_notify_rotation( + struct wlr_tablet_v2_tablet_tool *tool, double degrees); + +void wlr_tablet_v2_tablet_tool_notify_slider( + struct wlr_tablet_v2_tablet_tool *tool, double position); + +void wlr_tablet_v2_tablet_tool_notify_wheel( + struct wlr_tablet_v2_tablet_tool *tool, double degrees, int32_t clicks); + +void wlr_tablet_v2_tablet_tool_notify_proximity_out( + struct wlr_tablet_v2_tablet_tool *tool); + +void wlr_tablet_v2_tablet_tool_notify_button( + struct wlr_tablet_v2_tablet_tool *tool, uint32_t button, + enum zwp_tablet_pad_v2_button_state state); + + +struct wlr_tablet_tool_v2_grab_interface { + void (*proximity_in)( + struct wlr_tablet_tool_v2_grab *grab, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); + + void (*down)(struct wlr_tablet_tool_v2_grab *grab); + void (*up)(struct wlr_tablet_tool_v2_grab *grab); + + void (*motion)(struct wlr_tablet_tool_v2_grab *grab, double x, double y); + + void (*pressure)(struct wlr_tablet_tool_v2_grab *grab, double pressure); + + void (*distance)(struct wlr_tablet_tool_v2_grab *grab, double distance); + + void (*tilt)(struct wlr_tablet_tool_v2_grab *grab, double x, double y); + + void (*rotation)(struct wlr_tablet_tool_v2_grab *grab, double degrees); + + void (*slider)(struct wlr_tablet_tool_v2_grab *grab, double position); + + void (*wheel)(struct wlr_tablet_tool_v2_grab *grab, double degrees, int32_t clicks); + + void (*proximity_out)(struct wlr_tablet_tool_v2_grab *grab); + + void (*button)( + struct wlr_tablet_tool_v2_grab *grab, uint32_t button, + enum zwp_tablet_pad_v2_button_state state); + void (*cancel)(struct wlr_tablet_tool_v2_grab *grab); +}; + +void wlr_tablet_tool_v2_start_grab(struct wlr_tablet_v2_tablet_tool *tool, struct wlr_tablet_tool_v2_grab *grab); +void wlr_tablet_tool_v2_end_grab(struct wlr_tablet_v2_tablet_tool *tool); + +void wlr_tablet_tool_v2_start_implicit_grab(struct wlr_tablet_v2_tablet_tool *tool); + +bool wlr_tablet_tool_v2_has_implicit_grab( + struct wlr_tablet_v2_tablet_tool *tool); + +uint32_t wlr_send_tablet_v2_tablet_pad_enter( + struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); + +void wlr_send_tablet_v2_tablet_pad_button( + struct wlr_tablet_v2_tablet_pad *pad, size_t button, + uint32_t time, enum zwp_tablet_pad_v2_button_state state); + +void wlr_send_tablet_v2_tablet_pad_strip(struct wlr_tablet_v2_tablet_pad *pad, + uint32_t strip, double position, bool finger, uint32_t time); +void wlr_send_tablet_v2_tablet_pad_ring(struct wlr_tablet_v2_tablet_pad *pad, + uint32_t ring, double position, bool finger, uint32_t time); + +uint32_t wlr_send_tablet_v2_tablet_pad_leave(struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_surface *surface); + +uint32_t wlr_send_tablet_v2_tablet_pad_mode(struct wlr_tablet_v2_tablet_pad *pad, + size_t group, uint32_t mode, uint32_t time); + + +uint32_t wlr_tablet_v2_tablet_pad_notify_enter( + struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); + +void wlr_tablet_v2_tablet_pad_notify_button( + struct wlr_tablet_v2_tablet_pad *pad, size_t button, + uint32_t time, enum zwp_tablet_pad_v2_button_state state); + +void wlr_tablet_v2_tablet_pad_notify_strip( + struct wlr_tablet_v2_tablet_pad *pad, + uint32_t strip, double position, bool finger, uint32_t time); +void wlr_tablet_v2_tablet_pad_notify_ring( + struct wlr_tablet_v2_tablet_pad *pad, + uint32_t ring, double position, bool finger, uint32_t time); + +uint32_t wlr_tablet_v2_tablet_pad_notify_leave( + struct wlr_tablet_v2_tablet_pad *pad, struct wlr_surface *surface); + +uint32_t wlr_tablet_v2_tablet_pad_notify_mode( + struct wlr_tablet_v2_tablet_pad *pad, + size_t group, uint32_t mode, uint32_t time); + +struct wlr_tablet_pad_v2_grab_interface { + uint32_t (*enter)( + struct wlr_tablet_pad_v2_grab *grab, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); + + void (*button)(struct wlr_tablet_pad_v2_grab *grab,size_t button, + uint32_t time, enum zwp_tablet_pad_v2_button_state state); + + void (*strip)(struct wlr_tablet_pad_v2_grab *grab, + uint32_t strip, double position, bool finger, uint32_t time); + void (*ring)(struct wlr_tablet_pad_v2_grab *grab, + uint32_t ring, double position, bool finger, uint32_t time); + + uint32_t (*leave)(struct wlr_tablet_pad_v2_grab *grab, + struct wlr_surface *surface); + + uint32_t (*mode)(struct wlr_tablet_pad_v2_grab *grab, + size_t group, uint32_t mode, uint32_t time); + + void (*cancel)(struct wlr_tablet_pad_v2_grab *grab); +}; + +void wlr_tablet_v2_end_grab(struct wlr_tablet_v2_tablet_pad *pad); +void wlr_tablet_v2_start_grab(struct wlr_tablet_v2_tablet_pad *pad, struct wlr_tablet_pad_v2_grab *grab); + +bool wlr_surface_accepts_tablet_v2(struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); +#endif /* WLR_TYPES_WLR_TABLET_V2_H */ diff --git a/include/wlr/types/wlr_tearing_control_v1.h b/include/wlr/types/wlr_tearing_control_v1.h new file mode 100644 index 0000000..76c5c60 --- /dev/null +++ b/include/wlr/types/wlr_tearing_control_v1.h @@ -0,0 +1,67 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_TEARING_CONTROL_MANAGER_V1_H +#define WLR_TYPES_WLR_TEARING_CONTROL_MANAGER_V1_H + +#include +#include +#include +#include + +#include "tearing-control-v1-protocol.h" + +struct wlr_tearing_control_v1 { + struct wl_client *client; + struct wl_list link; + struct wl_resource *resource; + + enum wp_tearing_control_v1_presentation_hint current, pending; + + struct { + struct wl_signal set_hint; + struct wl_signal destroy; + } events; + + struct wlr_surface *surface; + + // private state + + enum wp_tearing_control_v1_presentation_hint previous; + struct wlr_addon addon; + struct wlr_surface_synced synced; + + struct wl_listener surface_commit; +}; + +struct wlr_tearing_control_manager_v1 { + struct wl_global *global; + + struct wl_list surface_hints; // wlr_tearing_control_v1.link + + struct wl_listener display_destroy; + struct { + struct wl_signal new_object; // struct wlr_tearing_control_v1* + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_tearing_control_manager_v1 *wlr_tearing_control_manager_v1_create( + struct wl_display *display, uint32_t version); + +/** + * Returns the tearing hint for a given surface + */ +enum wp_tearing_control_v1_presentation_hint +wlr_tearing_control_manager_v1_surface_hint_from_surface( + struct wlr_tearing_control_manager_v1 *manager, + struct wlr_surface *surface); + +#endif diff --git a/include/wlr/types/wlr_text_input_v3.h b/include/wlr/types/wlr_text_input_v3.h new file mode 100644 index 0000000..37397c1 --- /dev/null +++ b/include/wlr/types/wlr_text_input_v3.h @@ -0,0 +1,99 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_TEXT_INPUT_V3_H +#define WLR_TYPES_WLR_TEXT_INPUT_V3_H + +#include +#include +#include + +struct wlr_surface; + +enum wlr_text_input_v3_features { + WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT = 1 << 0, + WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE = 1 << 1, + WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE = 1 << 2, +}; + +struct wlr_text_input_v3_state { + struct { + char *text; // NULL is allowed and equivalent to empty string + uint32_t cursor; + uint32_t anchor; + } surrounding; + + uint32_t text_change_cause; + + struct { + uint32_t hint; + uint32_t purpose; + } content_type; + + struct wlr_box cursor_rectangle; + + // Tracks which features were used in the current commit. + // Useful in the enabling commit, where usage means support. + uint32_t features; // bitfield of enum wlr_text_input_v3_features +}; + +struct wlr_text_input_v3 { + struct wlr_seat *seat; // becomes null when seat destroyed + struct wl_resource *resource; + struct wlr_surface *focused_surface; + struct wlr_text_input_v3_state pending; + struct wlr_text_input_v3_state current; + uint32_t current_serial; // next in line to send + bool pending_enabled; + bool current_enabled; + // supported in the current text input, more granular than surface + uint32_t active_features; // bitfield of enum wlr_text_input_v3_features + + struct wl_list link; + + struct wl_listener surface_destroy; + struct wl_listener seat_destroy; + + struct { + struct wl_signal enable; // struct wlr_text_input_v3 + struct wl_signal commit; // struct wlr_text_input_v3 + struct wl_signal disable; // struct wlr_text_input_v3 + struct wl_signal destroy; // struct wlr_text_input_v3 + } events; +}; + +struct wlr_text_input_manager_v3 { + struct wl_global *global; + struct wl_list text_inputs; // struct wlr_text_input_v3.resource.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal text_input; // struct wlr_text_input_v3 + struct wl_signal destroy; // struct wlr_input_method_manager_v3 + } events; +}; + +struct wlr_text_input_manager_v3 *wlr_text_input_manager_v3_create( + struct wl_display *wl_display); + +// Sends enter to the surface and saves it +void wlr_text_input_v3_send_enter(struct wlr_text_input_v3 *text_input, + struct wlr_surface *wlr_surface); +// Sends leave to the currently focused surface and clears it +void wlr_text_input_v3_send_leave(struct wlr_text_input_v3 *text_input); +void wlr_text_input_v3_send_preedit_string(struct wlr_text_input_v3 *text_input, + const char *text, int32_t cursor_begin, int32_t cursor_end); +void wlr_text_input_v3_send_commit_string(struct wlr_text_input_v3 *text_input, + const char *text); +void wlr_text_input_v3_send_delete_surrounding_text( + struct wlr_text_input_v3 *text_input, uint32_t before_length, + uint32_t after_length); +void wlr_text_input_v3_send_done(struct wlr_text_input_v3 *text_input); + +#endif diff --git a/include/wlr/types/wlr_touch.h b/include/wlr/types/wlr_touch.h new file mode 100644 index 0000000..82c4b86 --- /dev/null +++ b/include/wlr/types/wlr_touch.h @@ -0,0 +1,73 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_TOUCH_H +#define WLR_TYPES_WLR_TOUCH_H + +#include +#include +#include + +struct wlr_touch_impl; + +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; + + 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_up_event { + 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_cancel_event { + struct wlr_touch *touch; + uint32_t time_msec; + int32_t touch_id; +}; + +/** + * Get a struct wlr_touch from a struct wlr_input_device. + * + * Asserts that the input device is a touch device. + */ +struct wlr_touch *wlr_touch_from_input_device( + struct wlr_input_device *input_device); + +#endif diff --git a/include/wlr/types/wlr_transient_seat_v1.h b/include/wlr/types/wlr_transient_seat_v1.h new file mode 100644 index 0000000..84d419b --- /dev/null +++ b/include/wlr/types/wlr_transient_seat_v1.h @@ -0,0 +1,63 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_TRANSIENT_SEAT_V1_H +#define WLR_TYPES_WLR_TRANSIENT_SEAT_V1_H + +#include + +struct wlr_seat; + +struct wlr_transient_seat_v1 { + struct wl_resource *resource; + struct wlr_seat *seat; + + // private state + struct wl_listener seat_destroy; +}; + +struct wlr_transient_seat_manager_v1 { + struct wl_global *global; + struct wl_listener display_destroy; + + struct { + /** + * Upon receiving this signal, call + * wlr_transient_seat_v1_ready() to pass a newly created seat + * to the manager, or + * wlr_transient_seat_v1_deny() to deny the request to create + * a seat. + */ + struct wl_signal create_seat; // struct wlr_transient_seat_v1 + } events; +}; + +struct wlr_transient_seat_manager_v1 *wlr_transient_seat_manager_v1_create( + struct wl_display *display); + +/** + * To be called when the create_seat event is received. + * + * This signals that the seat was successfully added and is ready. + * + * When the transient seat is destroyed by the client, the wlr_seat will be + * destroyed. The wlr_seat may also be destroyed from elsewhere, in which case + * the transient seat will become inert. + */ +void wlr_transient_seat_v1_ready(struct wlr_transient_seat_v1 *seat, + struct wlr_seat *wlr_seat); + +/** + * To be called when the create_seat event is received. + * + * This signals that the compositor has denied the user's request to create a + * transient seat. + */ +void wlr_transient_seat_v1_deny(struct wlr_transient_seat_v1 *seat); + +#endif /* WLR_TYPES_WLR_TRANSIENT_SEAT_V1_H */ diff --git a/include/wlr/types/wlr_viewporter.h b/include/wlr/types/wlr_viewporter.h new file mode 100644 index 0000000..8a17936 --- /dev/null +++ b/include/wlr/types/wlr_viewporter.h @@ -0,0 +1,37 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_VIEWPORTER_H +#define WLR_TYPES_WLR_VIEWPORTER_H + +#include + +/** + * Implementation for the viewporter protocol. + * + * When enabling viewporter, compositors need to update their rendering logic: + * + * - The size of the surface texture may not match the surface size anymore. + * Compositors must use the surface size only. + * - Compositors must call wlr_render_subtexture_with_matrix() when rendering a + * surface texture with the source box returned by + * wlr_surface_get_buffer_source_box(). + */ +struct wlr_viewporter { + struct wl_global *global; + + struct { + struct wl_signal destroy; + } events; + + struct wl_listener display_destroy; +}; + +struct wlr_viewporter *wlr_viewporter_create(struct wl_display *display); + +#endif diff --git a/include/wlr/types/wlr_virtual_keyboard_v1.h b/include/wlr/types/wlr_virtual_keyboard_v1.h new file mode 100644 index 0000000..cb0bb98 --- /dev/null +++ b/include/wlr/types/wlr_virtual_keyboard_v1.h @@ -0,0 +1,42 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_VIRTUAL_KEYBOARD_V1_H +#define WLR_TYPES_WLR_VIRTUAL_KEYBOARD_V1_H + +#include +#include + +struct wlr_virtual_keyboard_manager_v1 { + struct wl_global *global; + struct wl_list virtual_keyboards; // wlr_virtual_keyboard_v1.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_virtual_keyboard; // struct wlr_virtual_keyboard_v1 + struct wl_signal destroy; + } events; +}; + +struct wlr_virtual_keyboard_v1 { + struct wlr_keyboard keyboard; + struct wl_resource *resource; + struct wlr_seat *seat; + bool has_keymap; + + struct wl_list link; // wlr_virtual_keyboard_manager_v1.virtual_keyboards +}; + +struct wlr_virtual_keyboard_manager_v1* wlr_virtual_keyboard_manager_v1_create( + struct wl_display *display); + +struct wlr_virtual_keyboard_v1 *wlr_input_device_get_virtual_keyboard( + struct wlr_input_device *wlr_dev); + +#endif diff --git a/include/wlr/types/wlr_virtual_pointer_v1.h b/include/wlr/types/wlr_virtual_pointer_v1.h new file mode 100644 index 0000000..a052685 --- /dev/null +++ b/include/wlr/types/wlr_virtual_pointer_v1.h @@ -0,0 +1,50 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_VIRTUAL_POINTER_V1_H +#define WLR_TYPES_WLR_VIRTUAL_POINTER_V1_H + +#include +#include +#include +#include + +struct wlr_virtual_pointer_manager_v1 { + struct wl_global *global; + struct wl_list virtual_pointers; // wlr_virtual_pointer_v1.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_virtual_pointer; // struct wlr_virtual_pointer_v1_new_pointer_event + struct wl_signal destroy; + } events; +}; + +struct wlr_virtual_pointer_v1 { + struct wlr_pointer pointer; + struct wl_resource *resource; + /* Vertical and horizontal */ + struct wlr_pointer_axis_event axis_event[2]; + enum wl_pointer_axis axis; + bool axis_valid[2]; + + struct wl_list link; // wlr_virtual_pointer_manager_v1.virtual_pointers +}; + +struct wlr_virtual_pointer_v1_new_pointer_event { + struct wlr_virtual_pointer_v1 *new_pointer; + /** Suggested by client; may be NULL. */ + struct wlr_seat *suggested_seat; + struct wlr_output *suggested_output; +}; + +struct wlr_virtual_pointer_manager_v1* wlr_virtual_pointer_manager_v1_create( + struct wl_display *display); + +#endif diff --git a/include/wlr/types/wlr_xcursor_manager.h b/include/wlr/types/wlr_xcursor_manager.h new file mode 100644 index 0000000..5399315 --- /dev/null +++ b/include/wlr/types/wlr_xcursor_manager.h @@ -0,0 +1,59 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XCURSOR_MANAGER_H +#define WLR_TYPES_WLR_XCURSOR_MANAGER_H + +#include +#include + +/** + * An XCursor theme at a particular scale factor of the base size. + */ +struct wlr_xcursor_manager_theme { + float scale; + struct wlr_xcursor_theme *theme; + struct wl_list link; +}; + +/** + * struct wlr_xcursor_manager dynamically loads xcursor themes at sizes necessary + * for use on outputs at arbitrary scale factors. You should call + * wlr_xcursor_manager_load() for each output you will show your cursor on, with + * the scale factor parameter set to that output's scale factor. + */ +struct wlr_xcursor_manager { + char *name; + uint32_t size; + struct wl_list scaled_themes; // wlr_xcursor_manager_theme.link +}; + +/** + * Creates a new XCursor manager with the given xcursor theme name and base size + * (for use when scale=1). + */ +struct wlr_xcursor_manager *wlr_xcursor_manager_create(const char *name, + uint32_t size); + +void wlr_xcursor_manager_destroy(struct wlr_xcursor_manager *manager); + +/** + * Ensures an xcursor theme at the given scale factor is loaded in the manager. + */ +bool wlr_xcursor_manager_load(struct wlr_xcursor_manager *manager, + float scale); + +/** + * Retrieves a wlr_xcursor reference for the given cursor name at the given + * scale factor, or NULL if this struct wlr_xcursor_manager has not loaded a + * cursor theme at the requested scale. + */ +struct wlr_xcursor *wlr_xcursor_manager_get_xcursor( + struct wlr_xcursor_manager *manager, const char *name, float scale); + +#endif diff --git a/include/wlr/types/wlr_xdg_activation_v1.h b/include/wlr/types/wlr_xdg_activation_v1.h new file mode 100644 index 0000000..f7b038b --- /dev/null +++ b/include/wlr/types/wlr_xdg_activation_v1.h @@ -0,0 +1,89 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_ACTIVATION_V1 +#define WLR_TYPES_WLR_XDG_ACTIVATION_V1 + +#include + +struct wlr_xdg_activation_token_v1 { + struct wlr_xdg_activation_v1 *activation; + // The source surface that created the token. + struct wlr_surface *surface; // can be NULL + struct wlr_seat *seat; // can be NULL + // The serial for the input event that created the token. + uint32_t serial; // invalid if seat is NULL + // The application ID to be activated. This is just a hint. + char *app_id; // can be NULL + struct wl_list link; // wlr_xdg_activation_v1.tokens + + void *data; + + struct { + struct wl_signal destroy; + } events; + + // private state + + char *token; + struct wl_resource *resource; // can be NULL + struct wl_event_source *timeout; // can be NULL + + struct wl_listener seat_destroy; + struct wl_listener surface_destroy; +}; + +struct wlr_xdg_activation_v1 { + uint32_t token_timeout_msec; // token timeout in milliseconds (0 to disable) + + struct wl_list tokens; // wlr_xdg_activation_token_v1.link + + struct { + struct wl_signal destroy; + struct wl_signal request_activate; // struct wlr_xdg_activation_v1_request_activate_event + struct wl_signal new_token; // struct wlr_xdg_activation_token_v1 + } events; + + // private state + + struct wl_display *display; + + struct wl_global *global; + + struct wl_listener display_destroy; +}; + +struct wlr_xdg_activation_v1_request_activate_event { + struct wlr_xdg_activation_v1 *activation; + // The token used to request activation. + struct wlr_xdg_activation_token_v1 *token; + // The surface requesting for activation. + struct wlr_surface *surface; +}; + +struct wlr_xdg_activation_v1 *wlr_xdg_activation_v1_create( + struct wl_display *display); + +struct wlr_xdg_activation_token_v1 *wlr_xdg_activation_token_v1_create( + struct wlr_xdg_activation_v1 *activation); + +void wlr_xdg_activation_token_v1_destroy( + struct wlr_xdg_activation_token_v1 *token); + +struct wlr_xdg_activation_token_v1 *wlr_xdg_activation_v1_find_token( + struct wlr_xdg_activation_v1 *activation, const char *token_str); + +// Get a string suitable for exporting to launched clients +const char *wlr_xdg_activation_token_v1_get_name( + struct wlr_xdg_activation_token_v1 *token); + +// Add a token to the pool of known tokens +struct wlr_xdg_activation_token_v1 *wlr_xdg_activation_v1_add_token( + struct wlr_xdg_activation_v1 *activation, const char *token_str); + +#endif diff --git a/include/wlr/types/wlr_xdg_decoration_v1.h b/include/wlr/types/wlr_xdg_decoration_v1.h new file mode 100644 index 0000000..f1a8bc5 --- /dev/null +++ b/include/wlr/types/wlr_xdg_decoration_v1.h @@ -0,0 +1,73 @@ +#ifndef WLR_TYPES_WLR_XDG_DECORATION_V1 +#define WLR_TYPES_WLR_XDG_DECORATION_V1 + +#include +#include + +enum wlr_xdg_toplevel_decoration_v1_mode { + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE = 0, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE = 1, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE = 2, +}; + +struct wlr_xdg_decoration_manager_v1 { + struct wl_global *global; + struct wl_list decorations; // wlr_xdg_toplevel_decoration.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_toplevel_decoration; // struct wlr_xdg_toplevel_decoration + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_xdg_toplevel_decoration_v1_configure { + struct wl_list link; // wlr_xdg_toplevel_decoration.configure_list + struct wlr_xdg_surface_configure *surface_configure; + enum wlr_xdg_toplevel_decoration_v1_mode mode; +}; + +struct wlr_xdg_toplevel_decoration_v1_state { + enum wlr_xdg_toplevel_decoration_v1_mode mode; +}; + +struct wlr_xdg_toplevel_decoration_v1 { + struct wl_resource *resource; + struct wlr_xdg_toplevel *toplevel; + struct wlr_xdg_decoration_manager_v1 *manager; + struct wl_list link; // wlr_xdg_decoration_manager_v1.link + + struct wlr_xdg_toplevel_decoration_v1_state current, pending; + + enum wlr_xdg_toplevel_decoration_v1_mode scheduled_mode; + enum wlr_xdg_toplevel_decoration_v1_mode requested_mode; + + struct wl_list configure_list; // wlr_xdg_toplevel_decoration_v1_configure.link + + struct { + struct wl_signal destroy; + struct wl_signal request_mode; + } events; + + void *data; + + // private state + + struct wl_listener toplevel_destroy; + struct wl_listener surface_configure; + struct wl_listener surface_ack_configure; + + struct wlr_surface_synced synced; +}; + +struct wlr_xdg_decoration_manager_v1 * + wlr_xdg_decoration_manager_v1_create(struct wl_display *display); + +uint32_t wlr_xdg_toplevel_decoration_v1_set_mode( + struct wlr_xdg_toplevel_decoration_v1 *decoration, + enum wlr_xdg_toplevel_decoration_v1_mode mode); + +#endif diff --git a/include/wlr/types/wlr_xdg_foreign_registry.h b/include/wlr/types/wlr_xdg_foreign_registry.h new file mode 100644 index 0000000..54c91e4 --- /dev/null +++ b/include/wlr/types/wlr_xdg_foreign_registry.h @@ -0,0 +1,75 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_FOREIGN_REGISTRY_H +#define WLR_TYPES_WLR_XDG_FOREIGN_REGISTRY_H + +#include + +#define WLR_XDG_FOREIGN_HANDLE_SIZE 37 + +/** + * struct wlr_xdg_foreign_registry is used for storing a list of exported + * surfaces with the xdg-foreign family of protocols. + * + * It can be used to allow interoperability between clients using different + * versions of the protocol (if all versions use the same registry). + */ +struct wlr_xdg_foreign_registry { + struct wl_list exported_surfaces; // struct wlr_xdg_foreign_exported_surface + + struct wl_listener display_destroy; + struct { + struct wl_signal destroy; + } events; +}; + +struct wlr_xdg_foreign_exported { + struct wl_list link; // wlr_xdg_foreign_registry.exported_surfaces + struct wlr_xdg_foreign_registry *registry; + + struct wlr_surface *surface; + + char handle[WLR_XDG_FOREIGN_HANDLE_SIZE]; + + struct { + struct wl_signal destroy; + } events; +}; + +/** + * Create an empty struct wlr_xdg_foreign_registry. + * + * It will be destroyed when the associated display is destroyed. + */ +struct wlr_xdg_foreign_registry *wlr_xdg_foreign_registry_create( + struct wl_display *display); + +/** + * Add the given exported surface to the registry and assign it a unique handle. + * The caller is responsible for removing the exported surface from the repository + * if it is destroyed. + * + * Returns true if the initialization was successful. + */ +bool wlr_xdg_foreign_exported_init(struct wlr_xdg_foreign_exported *surface, + struct wlr_xdg_foreign_registry *registry); + +/** + * Find an exported surface with the given handle, or NULL if such a surface + * does not exist. + */ +struct wlr_xdg_foreign_exported *wlr_xdg_foreign_registry_find_by_handle( + struct wlr_xdg_foreign_registry *registry, const char *handle); + +/** + * Remove the given surface from the registry it was previously added in. + */ +void wlr_xdg_foreign_exported_finish(struct wlr_xdg_foreign_exported *surface); + +#endif diff --git a/include/wlr/types/wlr_xdg_foreign_v1.h b/include/wlr/types/wlr_xdg_foreign_v1.h new file mode 100644 index 0000000..bb26fcf --- /dev/null +++ b/include/wlr/types/wlr_xdg_foreign_v1.h @@ -0,0 +1,64 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_FOREIGN_V1_H +#define WLR_TYPES_WLR_XDG_FOREIGN_V1_H + +#include +#include + +struct wlr_xdg_foreign_v1 { + struct { + struct wl_global *global; + struct wl_list objects; // wlr_xdg_exported_v1.link or wlr_xdg_imported_v1.link + } exporter, importer; + + struct wl_listener foreign_registry_destroy; + struct wl_listener display_destroy; + + struct wlr_xdg_foreign_registry *registry; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_xdg_exported_v1 { + struct wlr_xdg_foreign_exported base; + + struct wl_resource *resource; + struct wl_listener xdg_toplevel_destroy; + + struct wl_list link; // wlr_xdg_foreign_v1.exporter.objects +}; + +struct wlr_xdg_imported_v1 { + struct wlr_xdg_foreign_exported *exported; + struct wl_listener exported_destroyed; + + struct wl_resource *resource; + struct wl_list link; // wlr_xdg_foreign_v1.importer.objects + struct wl_list children; +}; + +struct wlr_xdg_imported_child_v1 { + struct wlr_xdg_imported_v1 *imported; + struct wlr_surface *surface; + + struct wl_list link; // wlr_xdg_imported_v1.children + + struct wl_listener xdg_toplevel_destroy; + struct wl_listener xdg_toplevel_set_parent; +}; + +struct wlr_xdg_foreign_v1 *wlr_xdg_foreign_v1_create( + struct wl_display *display, struct wlr_xdg_foreign_registry *registry); + +#endif diff --git a/include/wlr/types/wlr_xdg_foreign_v2.h b/include/wlr/types/wlr_xdg_foreign_v2.h new file mode 100644 index 0000000..d82854b --- /dev/null +++ b/include/wlr/types/wlr_xdg_foreign_v2.h @@ -0,0 +1,64 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_FOREIGN_V2_H +#define WLR_TYPES_WLR_XDG_FOREIGN_V2_H + +#include +#include + +struct wlr_xdg_foreign_v2 { + struct { + struct wl_global *global; + struct wl_list objects; // wlr_xdg_exported_v2.link or wlr_xdg_imported_v2.link + } exporter, importer; + + struct wl_listener foreign_registry_destroy; + struct wl_listener display_destroy; + + struct wlr_xdg_foreign_registry *registry; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_xdg_exported_v2 { + struct wlr_xdg_foreign_exported base; + + struct wl_resource *resource; + struct wl_listener xdg_toplevel_destroy; + + struct wl_list link; // wlr_xdg_foreign_v2.exporter.objects +}; + +struct wlr_xdg_imported_v2 { + struct wlr_xdg_foreign_exported *exported; + struct wl_listener exported_destroyed; + + struct wl_resource *resource; + struct wl_list link; // wlr_xdg_foreign_v2.importer.objects + struct wl_list children; +}; + +struct wlr_xdg_imported_child_v2 { + struct wlr_xdg_imported_v2 *imported; + struct wlr_surface *surface; + + struct wl_list link; // wlr_xdg_imported_v2.children + + struct wl_listener xdg_toplevel_destroy; + struct wl_listener xdg_toplevel_set_parent; +}; + +struct wlr_xdg_foreign_v2 *wlr_xdg_foreign_v2_create( + struct wl_display *display, struct wlr_xdg_foreign_registry *registry); + +#endif diff --git a/include/wlr/types/wlr_xdg_output_v1.h b/include/wlr/types/wlr_xdg_output_v1.h new file mode 100644 index 0000000..340252d --- /dev/null +++ b/include/wlr/types/wlr_xdg_output_v1.h @@ -0,0 +1,47 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_OUTPUT_V1_H +#define WLR_TYPES_WLR_XDG_OUTPUT_V1_H +#include +#include + +struct wlr_xdg_output_v1 { + struct wlr_xdg_output_manager_v1 *manager; + struct wl_list resources; + struct wl_list link; + + struct wlr_output_layout_output *layout_output; + + int32_t x, y; + int32_t width, height; + + struct wl_listener destroy; + struct wl_listener description; +}; + +struct wlr_xdg_output_manager_v1 { + struct wl_global *global; + struct wlr_output_layout *layout; + + struct wl_list outputs; + + struct { + struct wl_signal destroy; + } events; + + struct wl_listener display_destroy; + struct wl_listener layout_add; + struct wl_listener layout_change; + struct wl_listener layout_destroy; +}; + +struct wlr_xdg_output_manager_v1 *wlr_xdg_output_manager_v1_create( + struct wl_display *display, struct wlr_output_layout *layout); + +#endif diff --git a/include/wlr/types/wlr_xdg_shell.h b/include/wlr/types/wlr_xdg_shell.h new file mode 100644 index 0000000..e5b75c5 --- /dev/null +++ b/include/wlr/types/wlr_xdg_shell.h @@ -0,0 +1,556 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_SHELL_H +#define WLR_TYPES_WLR_XDG_SHELL_H + +#include +#include +#include +#include +#include "xdg-shell-protocol.h" + +struct wlr_xdg_shell { + struct wl_global *global; + uint32_t version; + struct wl_list clients; + struct wl_list popup_grabs; + uint32_t ping_timeout; + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_surface; // struct wlr_xdg_surface + struct wl_signal new_toplevel; // struct wlr_xdg_toplevel + struct wl_signal new_popup; // struct wlr_xdg_popup + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_xdg_client { + struct wlr_xdg_shell *shell; + struct wl_resource *resource; + struct wl_client *client; + struct wl_list surfaces; + + struct wl_list link; // wlr_xdg_shell.clients + + uint32_t ping_serial; + struct wl_event_source *ping_timer; +}; + +struct wlr_xdg_positioner_rules { + struct wlr_box anchor_rect; + enum xdg_positioner_anchor anchor; + enum xdg_positioner_gravity gravity; + enum xdg_positioner_constraint_adjustment constraint_adjustment; + + bool reactive; + + bool has_parent_configure_serial; + uint32_t parent_configure_serial; + + struct { + int32_t width, height; + } size, parent_size; + + struct { + int32_t x, y; + } offset; +}; + +struct wlr_xdg_positioner { + struct wl_resource *resource; + struct wlr_xdg_positioner_rules rules; +}; + +struct wlr_xdg_popup_state { + // Position of the popup relative to the upper left corner of + // the window geometry of the parent surface + struct wlr_box geometry; + + bool reactive; +}; + +enum wlr_xdg_popup_configure_field { + WLR_XDG_POPUP_CONFIGURE_REPOSITION_TOKEN = 1 << 0, +}; + +struct wlr_xdg_popup_configure { + uint32_t fields; // enum wlr_xdg_popup_configure_field + struct wlr_box geometry; + struct wlr_xdg_positioner_rules rules; + uint32_t reposition_token; +}; + +struct wlr_xdg_popup { + struct wlr_xdg_surface *base; + struct wl_list link; + + struct wl_resource *resource; + struct wlr_surface *parent; + struct wlr_seat *seat; + + struct wlr_xdg_popup_configure scheduled; + + struct wlr_xdg_popup_state current, pending; + + struct { + struct wl_signal destroy; + + struct wl_signal reposition; + } events; + + struct wl_list grab_link; // wlr_xdg_popup_grab.popups + + // private state + + struct wlr_surface_synced synced; +}; + +// each seat gets a popup grab +struct wlr_xdg_popup_grab { + struct wl_client *client; + struct wlr_seat_pointer_grab pointer_grab; + struct wlr_seat_keyboard_grab keyboard_grab; + struct wlr_seat_touch_grab touch_grab; + struct wlr_seat *seat; + struct wl_list popups; + struct wl_list link; // wlr_xdg_shell.popup_grabs + struct wl_listener seat_destroy; +}; + +enum wlr_xdg_surface_role { + WLR_XDG_SURFACE_ROLE_NONE, + WLR_XDG_SURFACE_ROLE_TOPLEVEL, + WLR_XDG_SURFACE_ROLE_POPUP, +}; + +struct wlr_xdg_toplevel_state { + bool maximized, fullscreen, resizing, activated, suspended; + uint32_t tiled; // enum wlr_edges + int32_t width, height; + int32_t max_width, max_height; + int32_t min_width, min_height; +}; + +enum wlr_xdg_toplevel_wm_capabilities { + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU = 1 << 0, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE = 1 << 1, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN = 1 << 2, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE = 1 << 3, +}; + +enum wlr_xdg_toplevel_configure_field { + WLR_XDG_TOPLEVEL_CONFIGURE_BOUNDS = 1 << 0, + WLR_XDG_TOPLEVEL_CONFIGURE_WM_CAPABILITIES = 1 << 1, +}; + +struct wlr_xdg_toplevel_configure { + uint32_t fields; // enum wlr_xdg_toplevel_configure_field + bool maximized, fullscreen, resizing, activated, suspended; + uint32_t tiled; // enum wlr_edges + int32_t width, height; + struct { + int32_t width, height; + } bounds; + uint32_t wm_capabilities; // enum wlr_xdg_toplevel_wm_capabilities +}; + +struct wlr_xdg_toplevel_requested { + bool maximized, minimized, fullscreen; + struct wlr_output *fullscreen_output; + struct wl_listener fullscreen_output_destroy; +}; + +struct wlr_xdg_toplevel { + struct wl_resource *resource; + struct wlr_xdg_surface *base; + + struct wlr_xdg_toplevel *parent; + struct wl_listener parent_unmap; + + struct wlr_xdg_toplevel_state current, pending; + + // Properties to be sent to the client in the next configure event. + struct wlr_xdg_toplevel_configure scheduled; + + // Properties that the client has requested. Intended to be checked + // by the compositor on surface map and state change requests (such as + // xdg_toplevel.set_fullscreen) and handled accordingly. + struct wlr_xdg_toplevel_requested requested; + + char *title; + char *app_id; + + struct { + struct wl_signal destroy; + + // Note: as per xdg-shell protocol, the compositor has to + // handle state requests by sending a configure event, + // even if it didn't actually change the state. Therefore, + // every compositor implementing xdg-shell support *must* + // listen to these signals and schedule a configure event + // immediately or at some time in the future; not doing so + // is a protocol violation. + struct wl_signal request_maximize; + struct wl_signal request_fullscreen; + + struct wl_signal request_minimize; + struct wl_signal request_move; + struct wl_signal request_resize; + struct wl_signal request_show_window_menu; + struct wl_signal set_parent; + struct wl_signal set_title; + struct wl_signal set_app_id; + } events; + + // private state + + struct wlr_surface_synced synced; +}; + +struct wlr_xdg_surface_configure { + struct wlr_xdg_surface *surface; + struct wl_list link; // wlr_xdg_surface.configure_list + uint32_t serial; + + union { + struct wlr_xdg_toplevel_configure *toplevel_configure; + struct wlr_xdg_popup_configure *popup_configure; + }; +}; + +struct wlr_xdg_surface_state { + uint32_t configure_serial; + struct wlr_box geometry; +}; + +/** + * An xdg-surface is a user interface element requiring management by the + * compositor. An xdg-surface alone isn't useful, a role should be assigned to + * it in order to map it. + */ +struct wlr_xdg_surface { + struct wlr_xdg_client *client; + struct wl_resource *resource; + struct wlr_surface *surface; + struct wl_list link; // wlr_xdg_client.surfaces + + /** + * The lifetime-bound role of the xdg_surface. WLR_XDG_SURFACE_ROLE_NONE + * if the role was never set. + */ + enum wlr_xdg_surface_role role; + /** + * The role object representing the role. NULL if the object was destroyed. + */ + struct wl_resource *role_resource; + + // NULL if the role resource is inert + union { + struct wlr_xdg_toplevel *toplevel; + struct wlr_xdg_popup *popup; + }; + + struct wl_list popups; // wlr_xdg_popup.link + + bool configured; + struct wl_event_source *configure_idle; + uint32_t scheduled_serial; + struct wl_list configure_list; + + struct wlr_xdg_surface_state current, pending; + + // Whether the surface is ready to receive configure events + bool initialized; + // Whether the latest commit is an initial commit + bool initial_commit; + + struct { + struct wl_signal destroy; + struct wl_signal ping_timeout; + struct wl_signal new_popup; + + // for protocol extensions + struct wl_signal configure; // struct wlr_xdg_surface_configure + struct wl_signal ack_configure; // struct wlr_xdg_surface_configure + } events; + + void *data; + + // private state + + struct wlr_surface_synced synced; + + struct wl_listener role_resource_destroy; +}; + +struct wlr_xdg_toplevel_move_event { + struct wlr_xdg_toplevel *toplevel; + struct wlr_seat_client *seat; + uint32_t serial; +}; + +struct wlr_xdg_toplevel_resize_event { + struct wlr_xdg_toplevel *toplevel; + struct wlr_seat_client *seat; + uint32_t serial; + uint32_t edges; +}; + +struct wlr_xdg_toplevel_show_window_menu_event { + struct wlr_xdg_toplevel *toplevel; + struct wlr_seat_client *seat; + uint32_t serial; + int32_t x, y; +}; + +/** + * Create the xdg_wm_base global with the specified version. + */ +struct wlr_xdg_shell *wlr_xdg_shell_create(struct wl_display *display, + uint32_t version); + +/** Get the corresponding struct wlr_xdg_surface from a resource. + * + * Aborts if the resource doesn't have the correct type. Returns NULL if the + * resource is inert. + */ +struct wlr_xdg_surface *wlr_xdg_surface_from_resource( + struct wl_resource *resource); + +/** Get the corresponding struct wlr_xdg_popup from a resource. + * + * Aborts if the resource doesn't have the correct type. Returns NULL if the + * resource is inert. + */ +struct wlr_xdg_popup *wlr_xdg_popup_from_resource( + struct wl_resource *resource); + +/** Get the corresponding struct wlr_xdg_toplevel from a resource. + * + * Aborts if the resource doesn't have the correct type. Returns NULL if the + * resource is inert. + */ +struct wlr_xdg_toplevel *wlr_xdg_toplevel_from_resource( + struct wl_resource *resource); + +/** Get the corresponding struct wlr_xdg_positioner from a resource. + * + * Aborts if the resource doesn't have the correct type. + */ +struct wlr_xdg_positioner *wlr_xdg_positioner_from_resource( + struct wl_resource *resource); + +/** + * Send a ping to the surface. If the surface does not respond in a reasonable + * amount of time, the ping_timeout event will be emitted. + */ +void wlr_xdg_surface_ping(struct wlr_xdg_surface *surface); + +/** + * Request that this toplevel surface be the given size. Returns the associated + * configure serial. + */ +uint32_t wlr_xdg_toplevel_set_size(struct wlr_xdg_toplevel *toplevel, + int32_t width, int32_t height); + +/** + * Request that this toplevel show itself in an activated or deactivated + * state. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_activated(struct wlr_xdg_toplevel *toplevel, + bool activated); + +/** + * Request that this toplevel consider itself maximized or not + * maximized. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_maximized(struct wlr_xdg_toplevel *toplevel, + bool maximized); + +/** + * Request that this toplevel consider itself fullscreen or not + * fullscreen. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_fullscreen(struct wlr_xdg_toplevel *toplevel, + bool fullscreen); + +/** + * Request that this toplevel consider itself to be resizing or not + * resizing. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_resizing(struct wlr_xdg_toplevel *toplevel, + bool resizing); + +/** + * Request that this toplevel consider itself in a tiled layout and some + * edges are adjacent to another part of the tiling grid. `tiled_edges` is a + * bitfield of enum wlr_edges. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_tiled(struct wlr_xdg_toplevel *toplevel, + uint32_t tiled_edges); + +/** + * Configure the recommended bounds for the client's window geometry size. + * Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_bounds(struct wlr_xdg_toplevel *toplevel, + int32_t width, int32_t height); + +/** + * Configure the window manager capabilities for this toplevel. `caps` is a + * bitfield of `enum wlr_xdg_toplevel_wm_capabilities`. Returns the associated + * configure serial. + */ +uint32_t wlr_xdg_toplevel_set_wm_capabilities(struct wlr_xdg_toplevel *toplevel, + uint32_t caps); + +/** + * Request that this toplevel consider itself suspended or not + * suspended. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_suspended(struct wlr_xdg_toplevel *toplevel, + bool suspended); + +/** + * Request that this toplevel closes. + */ +void wlr_xdg_toplevel_send_close(struct wlr_xdg_toplevel *toplevel); + +/** + * Sets the parent of this toplevel. Parent can be NULL. + * + * Returns true on success, false if setting the parent would create a loop. + */ +bool wlr_xdg_toplevel_set_parent(struct wlr_xdg_toplevel *toplevel, + struct wlr_xdg_toplevel *parent); + +/** + * Notify the client that the popup has been dismissed and destroy the + * struct wlr_xdg_popup, rendering the resource inert. + */ +void wlr_xdg_popup_destroy(struct wlr_xdg_popup *popup); + +/** + * Get the position for this popup in the surface parent's coordinate system. + */ +void wlr_xdg_popup_get_position(struct wlr_xdg_popup *popup, + double *popup_sx, double *popup_sy); + +/** + * Get the geometry based on positioner rules. + */ +void wlr_xdg_positioner_rules_get_geometry( + const struct wlr_xdg_positioner_rules *rules, struct wlr_box *box); + +/** + * Unconstrain the box from the constraint area according to positioner rules. + */ +void wlr_xdg_positioner_rules_unconstrain_box( + const struct wlr_xdg_positioner_rules *rules, + const struct wlr_box *constraint, struct wlr_box *box); + +/** + * Convert the given coordinates in the popup coordinate system to the toplevel + * surface coordinate system. + */ +void wlr_xdg_popup_get_toplevel_coords(struct wlr_xdg_popup *popup, + int popup_sx, int popup_sy, int *toplevel_sx, int *toplevel_sy); + +/** + * Set the geometry of this popup to unconstrain it according to its + * xdg-positioner rules. The box should be in the popup's root toplevel parent + * surface coordinate system. + */ +void wlr_xdg_popup_unconstrain_from_box(struct wlr_xdg_popup *popup, + const struct wlr_box *toplevel_space_box); + +/** + * Find a surface within this xdg-surface tree at the given surface-local + * coordinates. Returns the surface and coordinates in the leaf surface + * coordinate system or NULL if no surface is found at that location. + */ +struct wlr_surface *wlr_xdg_surface_surface_at( + struct wlr_xdg_surface *surface, double sx, double sy, + double *sub_x, double *sub_y); + +/** + * Find a surface within this xdg-surface's popup tree at the given + * surface-local coordinates. Returns the surface and coordinates in the leaf + * surface coordinate system or NULL if no surface is found at that location. + */ +struct wlr_surface *wlr_xdg_surface_popup_surface_at( + struct wlr_xdg_surface *surface, double sx, double sy, + double *sub_x, double *sub_y); + +/** + * Get a struct wlr_xdg_surface from a struct wlr_surface. + * + * Returns NULL if the surface doesn't have the xdg_surface role or + * if the xdg_surface has been destroyed. + */ +struct wlr_xdg_surface *wlr_xdg_surface_try_from_wlr_surface(struct wlr_surface *surface); + +/** + * Get a struct wlr_xdg_toplevel from a struct wlr_surface. + * + * Returns NULL if the surface doesn't have the xdg_surface role, the + * xdg_surface is not a toplevel, or the xdg_surface/xdg_toplevel objects have + * been destroyed. + */ +struct wlr_xdg_toplevel *wlr_xdg_toplevel_try_from_wlr_surface(struct wlr_surface *surface); + +/** + * Get a struct wlr_xdg_popup from a struct wlr_surface. + * + * Returns NULL if the surface doesn't have the xdg_surface role, the + * xdg_surface is not a popup, or the xdg_surface/xdg_popup objects have + * been destroyed. + */ +struct wlr_xdg_popup *wlr_xdg_popup_try_from_wlr_surface(struct wlr_surface *surface); + +/** + * Get the surface geometry. + * + * This is either the geometry as set by the client, or defaulted to the bounds + * of the surface + the subsurfaces (as specified by the protocol). + * + * The x and y value can be < 0. + */ +void wlr_xdg_surface_get_geometry(struct wlr_xdg_surface *surface, + struct wlr_box *box); + +/** + * Call `iterator` on each mapped surface and popup in the xdg-surface tree + * (whether or not this xdg-surface is mapped), with the surface's position + * relative to the root xdg-surface. The function is called from root to leaves + * (in rendering order). + */ +void wlr_xdg_surface_for_each_surface(struct wlr_xdg_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data); + +/** + * Call `iterator` on each mapped popup's surface and popup's subsurface in the + * xdg-surface tree (whether or not this xdg-surface is mapped), with the + * surfaces's position relative to the root xdg-surface. The function is called + * from root to leaves (in rendering order). + */ +void wlr_xdg_surface_for_each_popup_surface(struct wlr_xdg_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data); + +/** + * Schedule a surface configuration. This should only be called by protocols + * extending the shell. + */ +uint32_t wlr_xdg_surface_schedule_configure(struct wlr_xdg_surface *surface); + +#endif diff --git a/include/wlr/util/addon.h b/include/wlr/util/addon.h new file mode 100644 index 0000000..c64200c --- /dev/null +++ b/include/wlr/util/addon.h @@ -0,0 +1,44 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_UTIL_ADDON_H +#define WLR_UTIL_ADDON_H + +#include + +struct wlr_addon_set { + // private state + struct wl_list addons; +}; + +struct wlr_addon; + +struct wlr_addon_interface { + const char *name; + // Has to call wlr_addon_finish() + void (*destroy)(struct wlr_addon *addon); +}; + +struct wlr_addon { + const struct wlr_addon_interface *impl; + // private state + const void *owner; + struct wl_list link; +}; + +void wlr_addon_set_init(struct wlr_addon_set *set); +void wlr_addon_set_finish(struct wlr_addon_set *set); + +void wlr_addon_init(struct wlr_addon *addon, struct wlr_addon_set *set, + const void *owner, const struct wlr_addon_interface *impl); +void wlr_addon_finish(struct wlr_addon *addon); + +struct wlr_addon *wlr_addon_find(struct wlr_addon_set *set, const void *owner, + const struct wlr_addon_interface *impl); + +#endif diff --git a/include/wlr/util/box.h b/include/wlr/util/box.h new file mode 100644 index 0000000..e866b1d --- /dev/null +++ b/include/wlr/util/box.h @@ -0,0 +1,114 @@ +/* +* This is a stable interface of wlroots. Future changes will be limited to: + * + * - New functions + * - New struct members + * - New enum members + * + * Note that wlroots does not make an ABI compatibility promise - in the future, + * the layout and size of structs used by wlroots may change, requiring code + * depending on this header to be recompiled (but not edited). + * + * Breaking changes are announced in the release notes and follow a 1-year + * deprecation schedule. + */ +#ifndef WLR_UTIL_BOX_H +#define WLR_UTIL_BOX_H + +#include +#include + +/** + * A box representing a rectangle region in a 2D space. + * + * The x and y coordinates are inclusive, and the width and height lengths are + * exclusive. In other words, the box starts from the coordinates (x, y), and + * goes up to but not including (x + width, y + height). + */ +struct wlr_box { + int x, y; + int width, height; +}; + +/** + * A floating-point box representing a rectangle region in a 2D space. + * + * struct wlr_fbox has the same semantics as struct wlr_box. + */ +struct wlr_fbox { + double x, y; + double width, height; +}; + +/** + * Functions below accept NULL where a box is expected, which is treated + * the same as an empty box. + */ + +/** + * Finds the closest point within the box bounds. + * + * Returns NAN if the box is empty. + */ +void wlr_box_closest_point(const struct wlr_box *box, double x, double y, + double *dest_x, double *dest_y); + +/** + * Gives the intersecting box between two struct wlr_box. + * + * Returns an empty box if the provided boxes don't intersect. + */ +bool wlr_box_intersection(struct wlr_box *dest, const struct wlr_box *box_a, + const struct wlr_box *box_b); + +/** + * Verifies if a point is contained within the bounds of a given struct wlr_box. + * + * For example: + * + * - A point at (100, 50) is not contained in the box (0, 0, 100, 50). + * - A point at (10, 10) is contained in the box (10, 0, 50, 50). + */ +bool wlr_box_contains_point(const struct wlr_box *box, double x, double y); + +/** + * Checks whether a box is empty or not. + * + * A box is considered empty if its width and/or height is zero or negative. + */ +bool wlr_box_empty(const struct wlr_box *box); + +/** + * Transforms a box inside a (0, 0, width, height) box. + */ +void wlr_box_transform(struct wlr_box *dest, const struct wlr_box *box, + enum wl_output_transform transform, int width, int height); + +/** + * Checks whether a box is empty or not. + * + * A box is considered empty if its width and/or height is zero or negative. + */ +bool wlr_fbox_empty(const struct wlr_fbox *box); + +/** + * Transforms a floating-point box inside a (0, 0, width, height) box. + */ +void wlr_fbox_transform(struct wlr_fbox *dest, const struct wlr_fbox *box, + enum wl_output_transform transform, double width, double height); + +#ifdef WLR_USE_UNSTABLE + +/** + * Returns true if the two boxes are equal, false otherwise. + */ +bool wlr_box_equal(const struct wlr_box *a, const struct wlr_box *b); + +/** + * Returns true if the two boxes are equal, false otherwise. + */ +bool wlr_fbox_equal(const struct wlr_fbox *a, const struct wlr_fbox *b); + +#endif + +#endif diff --git a/include/wlr/util/edges.h b/include/wlr/util/edges.h new file mode 100644 index 0000000..be40556 --- /dev/null +++ b/include/wlr/util/edges.h @@ -0,0 +1,27 @@ +/* + * This is a stable interface of wlroots. Future changes will be limited to: + * + * - New functions + * - New struct members + * - New enum members + * + * Note that wlroots does not make an ABI compatibility promise - in the future, + * the layout and size of structs used by wlroots may change, requiring code + * depending on this header to be recompiled (but not edited). + * + * Breaking changes are announced in the release notes and follow a 1-year + * deprecation schedule. + */ + +#ifndef WLR_UTIL_EDGES_H +#define WLR_UTIL_EDGES_H + +enum wlr_edges { + WLR_EDGE_NONE = 0, + WLR_EDGE_TOP = 1 << 0, + WLR_EDGE_BOTTOM = 1 << 1, + WLR_EDGE_LEFT = 1 << 2, + WLR_EDGE_RIGHT = 1 << 3, +}; + +#endif diff --git a/include/wlr/util/log.h b/include/wlr/util/log.h new file mode 100644 index 0000000..587104c --- /dev/null +++ b/include/wlr/util/log.h @@ -0,0 +1,76 @@ +/* + * This is a stable interface of wlroots. Future changes will be limited to: + * + * - New functions + * - New struct members + * - New enum members + * + * Note that wlroots does not make an ABI compatibility promise - in the future, + * the layout and size of structs used by wlroots may change, requiring code + * depending on this header to be recompiled (but not edited). + * + * Breaking changes are announced in the release notes and follow a 1-year + * deprecation schedule. + */ + +#ifndef WLR_UTIL_LOG_H +#define WLR_UTIL_LOG_H + +#include +#include +#include +#include + +enum wlr_log_importance { + WLR_SILENT = 0, + WLR_ERROR = 1, + WLR_INFO = 2, + WLR_DEBUG = 3, + WLR_LOG_IMPORTANCE_LAST, +}; + +typedef void (*wlr_log_func_t)(enum wlr_log_importance importance, + const char *fmt, va_list args); + +/** + * Set the log verbosity and callback. + * + * Only messages less than or equal to the supplied verbosity will be logged. + * If the callback is NULL, the default logger is used. + * + * This function can be called multiple times to update the verbosity or + * callback function. + */ +void wlr_log_init(enum wlr_log_importance verbosity, wlr_log_func_t callback); + +/** + * Get the current log verbosity configured by wlr_log_init(). + */ +enum wlr_log_importance wlr_log_get_verbosity(void); + +#ifdef __GNUC__ +#define _WLR_ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end))) +#else +#define _WLR_ATTRIB_PRINTF(start, end) +#endif + +void _wlr_log(enum wlr_log_importance verbosity, const char *format, ...) _WLR_ATTRIB_PRINTF(2, 3); +void _wlr_vlog(enum wlr_log_importance verbosity, const char *format, va_list args) _WLR_ATTRIB_PRINTF(2, 0); + +#ifdef _WLR_REL_SRC_DIR +// strip prefix from __FILE__, leaving the path relative to the project root +#define _WLR_FILENAME ((const char *)__FILE__ + sizeof(_WLR_REL_SRC_DIR) - 1) +#else +#define _WLR_FILENAME __FILE__ +#endif + +#define wlr_log(verb, fmt, ...) \ + _wlr_log(verb, "[%s:%d] " fmt, _WLR_FILENAME, __LINE__, ##__VA_ARGS__) + +#define wlr_vlog(verb, fmt, args) \ + _wlr_vlog(verb, "[%s:%d] " fmt, _WLR_FILENAME, __LINE__, args) + +#define wlr_log_errno(verb, fmt, ...) \ + wlr_log(verb, fmt ": %s", ##__VA_ARGS__, strerror(errno)) + +#endif diff --git a/include/wlr/util/region.h b/include/wlr/util/region.h new file mode 100644 index 0000000..ccd926f --- /dev/null +++ b/include/wlr/util/region.h @@ -0,0 +1,77 @@ +/* + * This is a stable interface of wlroots. Future changes will be limited to: + * + * - New functions + * - New struct members + * - New enum members + * + * Note that wlroots does not make an ABI compatibility promise - in the future, + * the layout and size of structs used by wlroots may change, requiring code + * depending on this header to be recompiled (but not edited). + * + * Breaking changes are announced in the release notes and follow a 1-year + * deprecation schedule. + */ + +#ifndef WLR_UTIL_REGION_H +#define WLR_UTIL_REGION_H + +#include +#include +#include + +/** + * Scale a region by the specified factor. + * + * The resulting coordinates are rounded up or down so that the new region is + * at least as big as the original one if the scale factor is greater or equal + * to 1. + * + * Also see wlr_region_scale_xy(). + */ +void wlr_region_scale(pixman_region32_t *dst, const pixman_region32_t *src, + float scale); + +/** + * Scale a region by the specified factors. + * + * The X and Y coordinates are scaled separately by scale_x and scale_y. + * + * The resulting coordinates are rounded up or down so that the new region is + * at least as big as the original one if the scale factor is greater or equal + * to 1. + */ +void wlr_region_scale_xy(pixman_region32_t *dst, const pixman_region32_t *src, + float scale_x, float scale_y); + +/** + * Applies a transform to a region inside a box of size `width` x `height`. + */ +void wlr_region_transform(pixman_region32_t *dst, const pixman_region32_t *src, + enum wl_output_transform transform, int width, int height); + +/** + * Expands the region by distance on both axis. distance must be + * a non-negative number. + */ +void wlr_region_expand(pixman_region32_t *dst, const pixman_region32_t *src, + int distance); + +/* + * Builds the smallest possible region that contains the region rotated about + * the point (ox, oy). + */ +void wlr_region_rotated_bounds(pixman_region32_t *dst, const pixman_region32_t *src, + float rotation, int ox, int oy); + +/** + * Confine a point inside a region. + * + * x1 and y1 are the old position, x2 and y2 are the new tentative position. + * The function returns true with confined coordinates in x2_out and y2_out if + * the old position is within the region, or false otherwise. + */ +bool wlr_region_confine(const pixman_region32_t *region, double x1, double y1, double x2, + double y2, double *x2_out, double *y2_out); + +#endif diff --git a/include/wlr/util/transform.h b/include/wlr/util/transform.h new file mode 100644 index 0000000..6195e6d --- /dev/null +++ b/include/wlr/util/transform.h @@ -0,0 +1,33 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_UTIL_TRANSFORM_H +#define WLR_UTIL_TRANSFORM_H + +#include + +/** + * Returns the transform that, when composed with `tr`, gives + * `WL_OUTPUT_TRANSFORM_NORMAL`. + */ +enum wl_output_transform wlr_output_transform_invert( + enum wl_output_transform tr); + +/** + * Returns a transform that, when applied, has the same effect as applying + * sequentially `tr_a` and `tr_b`. + */ +enum wl_output_transform wlr_output_transform_compose( + enum wl_output_transform tr_a, enum wl_output_transform tr_b); + +/** + * Applies a transform to coordinates. + */ +void wlr_output_transform_coords(enum wl_output_transform tr, int *x, int *y); + +#endif diff --git a/include/wlr/version.h.in b/include/wlr/version.h.in new file mode 100644 index 0000000..dcfcb75 --- /dev/null +++ b/include/wlr/version.h.in @@ -0,0 +1,12 @@ +#ifndef WLR_VERSION_H +#define WLR_VERSION_H + +#mesondefine WLR_VERSION_STR + +#mesondefine WLR_VERSION_MAJOR +#mesondefine WLR_VERSION_MINOR +#mesondefine WLR_VERSION_MICRO + +#define WLR_VERSION_NUM ((WLR_VERSION_MAJOR << 16) | (WLR_VERSION_MINOR << 8) | WLR_VERSION_MICRO) + +#endif diff --git a/include/wlr/xcursor.h b/include/wlr/xcursor.h new file mode 100644 index 0000000..bb75119 --- /dev/null +++ b/include/wlr/xcursor.h @@ -0,0 +1,127 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * This is a stable interface of wlroots. Future changes will be limited to: + * + * - New functions + * - New struct members + * - New enum members + * + * Note that wlroots does not make an ABI compatibility promise - in the future, + * the layout and size of structs used by wlroots may change, requiring code + * depending on this header to be recompiled (but not edited). + * + * Breaking changes are announced in the release notes and follow a 1-year + * deprecation schedule. + */ + +#ifndef WLR_XCURSOR_H +#define WLR_XCURSOR_H + +#include +#include + +/** + * A still cursor image. + * + * The buffer contains pixels layed out in a packed DRM_FORMAT_ARGB8888 format. + */ +struct wlr_xcursor_image { + uint32_t width; /* actual width */ + uint32_t height; /* actual height */ + uint32_t hotspot_x; /* hot-spot x (must be inside image) */ + uint32_t hotspot_y; /* hot-spot y (must be inside image) */ + uint32_t delay; /* animation delay to next frame (ms) */ + uint8_t *buffer; /* pixel data */ +}; + +/** + * A cursor. + * + * If the cursor is animated, it may contain more than a single image. + */ +struct wlr_xcursor { + unsigned int image_count; + struct wlr_xcursor_image **images; + char *name; + uint32_t total_delay; /* total duration of the animation in ms */ +}; + +/** + * Container for an Xcursor theme. + */ +struct wlr_xcursor_theme { + unsigned int cursor_count; + struct wlr_xcursor **cursors; + char *name; + int size; +}; + +/** + * Loads the named Xcursor theme. + * + * This is useful if you need cursor images for your compositor to use when a + * client-side cursor is not available or you wish to override client-side + * cursors for a particular UI interaction (such as using a grab cursor when + * moving a window around). + * + * The size is given in pixels. + * + * If a cursor theme with the given name couldn't be loaded, a fallback theme + * is loaded. + * + * On error, NULL is returned. + */ +struct wlr_xcursor_theme *wlr_xcursor_theme_load(const char *name, int size); + +/** + * Destroy a cursor theme. + * + * This implicitly destroys all child cursors and cursor images. + */ +void wlr_xcursor_theme_destroy(struct wlr_xcursor_theme *theme); + +/** + * Obtain a cursor for the specified name (e.g. "default"). + * + * If the cursor could not be found, NULL is returned. + */ +struct wlr_xcursor *wlr_xcursor_theme_get_cursor( + struct wlr_xcursor_theme *theme, const char *name); + +/** + * Find the frame for a given elapsed time in a cursor animation. + * + * This function converts a timestamp (in ms) to a cursor image index. + */ +int wlr_xcursor_frame(struct wlr_xcursor *cursor, uint32_t time); + +/** + * Get the name of the resize cursor for the given edges. + */ +const char *wlr_xcursor_get_resize_name(enum wlr_edges edges); + +#endif diff --git a/include/wlr/xwayland.h b/include/wlr/xwayland.h new file mode 100644 index 0000000..cf2ef26 --- /dev/null +++ b/include/wlr/xwayland.h @@ -0,0 +1,2 @@ +#include +#include diff --git a/include/wlr/xwayland/server.h b/include/wlr/xwayland/server.h new file mode 100644 index 0000000..fa16a3b --- /dev/null +++ b/include/wlr/xwayland/server.h @@ -0,0 +1,66 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_XWAYLAND_SERVER_H +#define WLR_XWAYLAND_SERVER_H + +#include +#include +#include +#include + +struct wlr_xwayland_server_options { + bool lazy; + bool enable_wm; + bool no_touch_pointer_emulation; + bool force_xrandr_emulation; + int terminate_delay; // in seconds, 0 to terminate immediately +}; + +struct wlr_xwayland_server { + pid_t pid; + struct wl_client *client; + struct wl_event_source *pipe_source; + int wm_fd[2], wl_fd[2]; + bool ready; + + time_t server_start; + + /* Anything above display is reset on Xwayland restart, rest is conserved */ + + int display; + char display_name[16]; + int x_fd[2]; + struct wl_event_source *x_fd_read_event[2]; + struct wlr_xwayland_server_options options; + + struct wl_display *wl_display; + struct wl_event_source *idle_source; + + struct { + struct wl_signal start; + struct wl_signal ready; + struct wl_signal destroy; + } events; + + struct wl_listener client_destroy; + struct wl_listener display_destroy; + + void *data; +}; + +struct wlr_xwayland_server_ready_event { + struct wlr_xwayland_server *server; + int wm_fd; +}; + +struct wlr_xwayland_server *wlr_xwayland_server_create( + struct wl_display *display, struct wlr_xwayland_server_options *options); +void wlr_xwayland_server_destroy(struct wlr_xwayland_server *server); + +#endif diff --git a/include/wlr/xwayland/shell.h b/include/wlr/xwayland/shell.h new file mode 100644 index 0000000..da5cb99 --- /dev/null +++ b/include/wlr/xwayland/shell.h @@ -0,0 +1,80 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_XWAYLAND_SHELL_H +#define WLR_XWAYLAND_SHELL_H + +#include +#include + +/** + * The Xwayland shell. + * + * This is a shell only exposed to Xwayland. + */ +struct wlr_xwayland_shell_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + struct wl_signal new_surface; // struct wlr_xwayland_surface_v1 + } events; + + // private state + + struct wl_client *client; + struct wl_list surfaces; // wlr_xwayland_surface_v1.link + + struct wl_listener display_destroy; + struct wl_listener client_destroy; +}; + +/** + * An Xwayland shell surface. + */ +struct wlr_xwayland_surface_v1 { + struct wlr_surface *surface; + uint64_t serial; + + // private state + + struct wl_resource *resource; + struct wl_list link; + struct wlr_xwayland_shell_v1 *shell; + bool added; +}; + +/** + * Create the xwayland_shell_v1 global. + * + * Compositors should add a global filter (see wl_display_set_global_filter()) + * to only expose this global to Xwayland clients. + */ +struct wlr_xwayland_shell_v1 *wlr_xwayland_shell_v1_create( + struct wl_display *display, uint32_t version); + +/** + * Destroy the xwayland_shell_v1 global. + */ +void wlr_xwayland_shell_v1_destroy(struct wlr_xwayland_shell_v1 *shell); + +/** + * Allow a client to bind to the xwayland_shell_v1 global. + */ +void wlr_xwayland_shell_v1_set_client(struct wlr_xwayland_shell_v1 *shell, + struct wl_client *client); + +/** + * Get a Wayland surface from an xwayland_shell_v1 serial. + * + * Returns NULL if the serial hasn't been associated with any surface. + */ +struct wlr_surface *wlr_xwayland_shell_v1_surface_from_serial( + struct wlr_xwayland_shell_v1 *shell, uint64_t serial); + +#endif diff --git a/include/wlr/xwayland/xwayland.h b/include/wlr/xwayland/xwayland.h new file mode 100644 index 0000000..a2715c6 --- /dev/null +++ b/include/wlr/xwayland/xwayland.h @@ -0,0 +1,312 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_XWAYLAND_XWAYLAND_H +#define WLR_XWAYLAND_XWAYLAND_H + +#include +#include +#include +#include +#include +#include + +struct wlr_box; +struct wlr_xwm; +struct wlr_data_source; +struct wlr_drag; + +struct wlr_xwayland { + struct wlr_xwayland_server *server; + bool own_server; + struct wlr_xwm *xwm; + struct wlr_xwayland_shell_v1 *shell_v1; + struct wlr_xwayland_cursor *cursor; + + const char *display_name; + + struct wl_display *wl_display; + struct wlr_compositor *compositor; + struct wlr_seat *seat; + + struct { + struct wl_signal ready; + struct wl_signal new_surface; // struct wlr_xwayland_surface + struct wl_signal remove_startup_info; // struct wlr_xwayland_remove_startup_info_event + } events; + + /** + * Add a custom event handler to xwayland. Return 1 if the event was + * handled or 0 to use the default wlr-xwayland handler. wlr-xwayland will + * free the event. + */ + int (*user_event_handler)(struct wlr_xwm *xwm, xcb_generic_event_t *event); + + struct wl_listener server_start; + struct wl_listener server_ready; + struct wl_listener server_destroy; + struct wl_listener seat_destroy; + struct wl_listener shell_destroy; + + void *data; +}; + +enum wlr_xwayland_surface_decorations { + WLR_XWAYLAND_SURFACE_DECORATIONS_ALL = 0, + WLR_XWAYLAND_SURFACE_DECORATIONS_NO_BORDER = 1, + WLR_XWAYLAND_SURFACE_DECORATIONS_NO_TITLE = 2, +}; + +/** + * This represents the input focus described as follows: + * + * https://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html#input_focus + */ +enum wlr_xwayland_icccm_input_model { + WLR_ICCCM_INPUT_MODEL_NONE = 0, + WLR_ICCCM_INPUT_MODEL_PASSIVE = 1, + WLR_ICCCM_INPUT_MODEL_LOCAL = 2, + WLR_ICCCM_INPUT_MODEL_GLOBAL = 3, +}; + +/** + * An Xwayland user interface component. It has an absolute position in + * layout-local coordinates. + * + * The inner struct wlr_surface is valid once the associate event is emitted. + * Compositors can set up e.g. map and unmap listeners at this point. The + * struct wlr_surface becomes invalid when the dissociate event is emitted. + */ +struct wlr_xwayland_surface { + xcb_window_t window_id; + struct wlr_xwm *xwm; + uint32_t surface_id; + uint64_t serial; + + struct wl_list link; + struct wl_list stack_link; + struct wl_list unpaired_link; + + struct wlr_surface *surface; + struct wlr_addon surface_addon; + struct wl_listener surface_commit; + struct wl_listener surface_map; + struct wl_listener surface_unmap; + + int16_t x, y; + uint16_t width, height; + uint16_t saved_width, saved_height; + bool override_redirect; + + char *title; + char *class; + char *instance; + char *role; + char *startup_id; + pid_t pid; + bool has_utf8_title; + + struct wl_list children; // wlr_xwayland_surface.parent_link + struct wlr_xwayland_surface *parent; + struct wl_list parent_link; // wlr_xwayland_surface.children + + xcb_atom_t *window_type; + size_t window_type_len; + + xcb_atom_t *protocols; + size_t protocols_len; + + uint32_t decorations; + xcb_icccm_wm_hints_t *hints; + xcb_size_hints_t *size_hints; + /* + * _NET_WM_STRUT_PARTIAL (used by e.g. XWayland panels). + * Note that right/bottom values are offsets from the lower + * right corner of the X11 screen, and the exact relation + * between X11 screen coordinates and the wlr_output_layout + * depends on the XWayland implementation. + */ + xcb_ewmh_wm_strut_partial_t *strut_partial; + + bool pinging; + struct wl_event_source *ping_timer; + + // _NET_WM_STATE + bool modal; + bool fullscreen; + bool maximized_vert, maximized_horz; + bool minimized; + bool withdrawn; + + bool has_alpha; + + struct { + struct wl_signal destroy; + struct wl_signal request_configure; // struct wlr_xwayland_surface_configure_event + struct wl_signal request_move; + struct wl_signal request_resize; // struct wlr_xwayland_resize_event + struct wl_signal request_minimize; // struct wlr_xwayland_minimize_event + struct wl_signal request_maximize; + struct wl_signal request_fullscreen; + struct wl_signal request_activate; + + struct wl_signal associate; + struct wl_signal dissociate; + + struct wl_signal set_title; + struct wl_signal set_class; + struct wl_signal set_role; + struct wl_signal set_parent; + struct wl_signal set_startup_id; + struct wl_signal set_window_type; + struct wl_signal set_hints; + struct wl_signal set_decorations; + struct wl_signal set_strut_partial; + struct wl_signal set_override_redirect; + struct wl_signal set_geometry; + /* can be used to set initial maximized/fullscreen geometry */ + struct wl_signal map_request; + struct wl_signal ping_timeout; + } events; + + void *data; +}; + +struct wlr_xwayland_surface_configure_event { + struct wlr_xwayland_surface *surface; + int16_t x, y; + uint16_t width, height; + uint16_t mask; // xcb_config_window_t +}; + +struct wlr_xwayland_remove_startup_info_event { + const char *id; + xcb_window_t window; +}; + +struct wlr_xwayland_resize_event { + struct wlr_xwayland_surface *surface; + uint32_t edges; +}; + +struct wlr_xwayland_minimize_event { + struct wlr_xwayland_surface *surface; + bool minimize; +}; + +/** Create an Xwayland server and XWM. + * + * The server supports a lazy mode in which Xwayland is only started when a + * client tries to connect. + */ +struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display, + struct wlr_compositor *compositor, bool lazy); + +/** + * Create an XWM from an existing Xwayland server. + */ +struct wlr_xwayland *wlr_xwayland_create_with_server(struct wl_display *display, + struct wlr_compositor *compositor, struct wlr_xwayland_server *server); + +void wlr_xwayland_destroy(struct wlr_xwayland *wlr_xwayland); + +void wlr_xwayland_set_cursor(struct wlr_xwayland *wlr_xwayland, + uint8_t *pixels, uint32_t stride, uint32_t width, uint32_t height, + int32_t hotspot_x, int32_t hotspot_y); + +void wlr_xwayland_surface_activate(struct wlr_xwayland_surface *surface, + bool activated); + +/** + * Restack surface relative to sibling. + * If sibling is NULL, then the surface is moved to the top or the bottom + * of the stack (depending on the mode). + */ +void wlr_xwayland_surface_restack(struct wlr_xwayland_surface *surface, + struct wlr_xwayland_surface *sibling, enum xcb_stack_mode_t mode); + +void wlr_xwayland_surface_configure(struct wlr_xwayland_surface *surface, + int16_t x, int16_t y, uint16_t width, uint16_t height); + +void wlr_xwayland_surface_close(struct wlr_xwayland_surface *surface); + +void wlr_xwayland_surface_set_withdrawn(struct wlr_xwayland_surface *surface, + bool withdrawn); + +void wlr_xwayland_surface_set_minimized(struct wlr_xwayland_surface *surface, + bool minimized); + +void wlr_xwayland_surface_set_maximized(struct wlr_xwayland_surface *surface, + bool maximized); + +void wlr_xwayland_surface_set_fullscreen(struct wlr_xwayland_surface *surface, + bool fullscreen); + +void wlr_xwayland_set_seat(struct wlr_xwayland *xwayland, + struct wlr_seat *seat); + +/** + * Get a struct wlr_xwayland_surface from a struct wlr_surface. + * + * If the surface hasn't been created by Xwayland or has no X11 window + * associated, NULL is returned. + */ +struct wlr_xwayland_surface *wlr_xwayland_surface_try_from_wlr_surface( + struct wlr_surface *surface); + +void wlr_xwayland_surface_ping(struct wlr_xwayland_surface *surface); + +/** Metric to guess if an OR window should "receive" focus + * + * In the pure X setups, window managers usually straight up ignore override + * redirect windows, and never touch them. (we have to handle them for mapping) + * + * When such a window wants to receive keyboard input (e.g. rofi/dzen) it will + * use mechanics we don't support (sniffing/grabbing input). + * [Sadly this is unrelated to xwayland-keyboard-grab] + * + * To still support these windows, while keeping general OR semantics as is, we + * need to hand a subset of windows focus. + * The dirty truth is, we need to hand focus to any Xwayland window, though + * pretending this window has focus makes it easier to handle unmap. + * + * This function provides a handy metric based on the window type to guess if + * the OR window wants focus. + * It's probably not perfect, nor exactly intended but works in practice. + * + * Returns: true if the window should receive focus + * false if it should be ignored + */ +bool wlr_xwayland_or_surface_wants_focus( + const struct wlr_xwayland_surface *xsurface); + +enum wlr_xwayland_icccm_input_model wlr_xwayland_icccm_input_model( + const struct wlr_xwayland_surface *xsurface); + +/** + * Sets the _NET_WORKAREA root window property. The compositor should set + * one workarea per virtual desktop. This indicates the usable geometry + * (relative to the virtual desktop viewport) that is not covered by + * panels, docks, etc. Unfortunately, it is not possible to specify + * per-output workareas. + */ +void wlr_xwayland_set_workareas(struct wlr_xwayland *wlr_xwayland, + const struct wlr_box *workareas, size_t num_workareas); + + +/** + * Get the XCB connection of the XWM. + * + * The connection is only valid after wlr_xwayland.events.ready, and becomes + * invalid on wlr_xwayland_server.events.destroy. In that case, NULL is + * returned. + */ +xcb_connection_t *wlr_xwayland_get_xwm_connection( + struct wlr_xwayland *wlr_xwayland); + +#endif diff --git a/include/xcursor/cursor_data.h b/include/xcursor/cursor_data.h new file mode 100644 index 0000000..8327014 --- /dev/null +++ b/include/xcursor/cursor_data.h @@ -0,0 +1,570 @@ +/* +* Copyright 1999 SuSE, Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice (including the +* next paragraph) shall be included in all copies or substantial +* portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Author: Keith Packard, SuSE, Inc. +*/ + +#include +#include + +static const uint32_t cursor_data[] = { + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0xff000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0x00000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0xff000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, + 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0x00000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xff000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, +}; + +static const struct cursor_metadata { + char *name; + int width, height; + int hotspot_x, hotspot_y; + size_t offset; +} cursor_metadata[] = { + { "bottom_left_corner", 16, 16, 1, 14, 0 }, + { "bottom_right_corner", 16, 16, 14, 14, 256 }, + { "bottom_side", 15, 16, 7, 14, 512 }, + { "grabbing", 16, 16, 8, 8, 752 }, + { "left_ptr", 10, 16, 1, 1, 1008 }, + { "left_side", 16, 15, 1, 7, 1168 }, + { "right_side", 16, 15, 14, 7, 1408 }, + { "top_left_corner", 16, 16, 1, 1, 1648 }, + { "top_right_corner", 16, 16, 14, 1, 1904 }, + { "top_side", 15, 16, 7, 1, 2160 }, + { "xterm", 9, 16, 4, 8, 2400 }, + { "hand1", 13, 16, 12, 0, 2544 }, + { "watch", 16, 16, 15, 9, 2752 }, + + /* https://www.freedesktop.org/wiki/Specifications/cursor-spec/ */ + { "sw-resize", 16, 16, 1, 14, 0 }, + { "se-resize", 16, 16, 14, 14, 256 }, + { "s-resize", 15, 16, 7, 14, 512 }, + { "all-scroll", 16, 16, 8, 8, 752 }, + { "default", 10, 16, 1, 1, 1008 }, + { "w-resize", 16, 15, 1, 7, 1168 }, + { "e-resize", 16, 15, 14, 7, 1408 }, + { "nw-resize", 16, 16, 1, 1, 1648 }, + { "ne-resize", 16, 16, 14, 1, 1904 }, + { "n-resize", 15, 16, 7, 1, 2160 }, + { "text", 9, 16, 4, 8, 2400 }, + { "pointer", 13, 16, 12, 0, 2544 }, + { "wait", 16, 16, 15, 9, 2752 }, +}; diff --git a/include/xcursor/xcursor.h b/include/xcursor/xcursor.h new file mode 100644 index 0000000..459f816 --- /dev/null +++ b/include/xcursor/xcursor.h @@ -0,0 +1,58 @@ +/* + * Copyright © 2002 Keith Packard + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef XCURSOR_H +#define XCURSOR_H + +#include + +struct xcursor_image { + uint32_t version; /* version of the image data */ + uint32_t size; /* nominal size for matching */ + uint32_t width; /* actual width */ + uint32_t height; /* actual height */ + uint32_t xhot; /* hot spot x (must be inside image) */ + uint32_t yhot; /* hot spot y (must be inside image) */ + uint32_t delay; /* animation delay to next frame (ms) */ + uint32_t *pixels; /* pointer to pixels */ +}; + +/* + * Other data structures exposed by the library API + */ +struct xcursor_images { + int nimage; /* number of images */ + struct xcursor_image **images; /* array of XcursorImage pointers */ + char *name; /* name used to load images */ +}; + +void +xcursor_images_destroy(struct xcursor_images *images); + +void +xcursor_load_theme(const char *theme, int size, + void (*load_callback)(struct xcursor_images *, void *), + void *user_data); +#endif diff --git a/include/xwayland/selection.h b/include/xwayland/selection.h new file mode 100644 index 0000000..3083102 --- /dev/null +++ b/include/xwayland/selection.h @@ -0,0 +1,95 @@ +#ifndef XWAYLAND_SELECTION_H +#define XWAYLAND_SELECTION_H + +#include +#include +#include + +#define INCR_CHUNK_SIZE (64 * 1024) + +#define XDND_VERSION 5 + +struct wlr_primary_selection_source; + +struct wlr_xwm_selection; + +struct wlr_drag; +struct wlr_data_source; + +struct wlr_xwm_selection_transfer { + struct wlr_xwm_selection *selection; + + bool incr; + bool flush_property_on_delete; + bool property_set; + struct wl_array source_data; + int wl_client_fd; + struct wl_event_source *event_source; + struct wl_list link; + + // when sending to x11 + xcb_selection_request_event_t request; + + // when receiving from x11 + int property_start; + xcb_get_property_reply_t *property_reply; + xcb_window_t incoming_window; +}; + +struct wlr_xwm_selection { + struct wlr_xwm *xwm; + + xcb_atom_t atom; + xcb_window_t window; + xcb_window_t owner; + xcb_timestamp_t timestamp; + + struct wl_list incoming; + struct wl_list outgoing; +}; + +struct wlr_xwm_selection_transfer * +xwm_selection_find_incoming_transfer_by_window( + struct wlr_xwm_selection *selection, xcb_window_t window); + +void xwm_selection_transfer_remove_event_source( + struct wlr_xwm_selection_transfer *transfer); +void xwm_selection_transfer_close_wl_client_fd( + struct wlr_xwm_selection_transfer *transfer); +void xwm_selection_transfer_destroy_property_reply( + struct wlr_xwm_selection_transfer *transfer); +void xwm_selection_transfer_init(struct wlr_xwm_selection_transfer *transfer, + struct wlr_xwm_selection *selection); +void xwm_selection_transfer_destroy( + struct wlr_xwm_selection_transfer *transfer); + +void xwm_selection_transfer_destroy_outgoing( + struct wlr_xwm_selection_transfer *transfer); + +xcb_atom_t xwm_mime_type_to_atom(struct wlr_xwm *xwm, char *mime_type); +char *xwm_mime_type_from_atom(struct wlr_xwm *xwm, xcb_atom_t atom); +struct wlr_xwm_selection *xwm_get_selection(struct wlr_xwm *xwm, + xcb_atom_t selection_atom); + +void xwm_send_incr_chunk(struct wlr_xwm_selection_transfer *transfer); +void xwm_handle_selection_request(struct wlr_xwm *xwm, + xcb_selection_request_event_t *req); +void xwm_handle_selection_destroy_notify(struct wlr_xwm *xwm, + xcb_destroy_notify_event_t *event); + +void xwm_get_incr_chunk(struct wlr_xwm_selection_transfer *transfer); +void xwm_handle_selection_notify(struct wlr_xwm *xwm, + xcb_selection_notify_event_t *event); +int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm, + xcb_xfixes_selection_notify_event_t *event); +bool data_source_is_xwayland(struct wlr_data_source *wlr_source); +bool primary_selection_source_is_xwayland( + struct wlr_primary_selection_source *wlr_source); + +void xwm_seat_handle_start_drag(struct wlr_xwm *xwm, struct wlr_drag *drag); + +void xwm_selection_init(struct wlr_xwm_selection *selection, + struct wlr_xwm *xwm, xcb_atom_t atom); +void xwm_selection_finish(struct wlr_xwm_selection *selection); + +#endif diff --git a/include/xwayland/xwm.h b/include/xwayland/xwm.h new file mode 100644 index 0000000..66108e2 --- /dev/null +++ b/include/xwayland/xwm.h @@ -0,0 +1,165 @@ +#ifndef XWAYLAND_XWM_H +#define XWAYLAND_XWM_H + +#include +#include +#include +#include +#include "config.h" +#include "xwayland/selection.h" + +#if HAVE_XCB_ERRORS +#include +#endif + +/* This is in xcb/xcb_event.h, but pulling xcb-util just for a constant + * others redefine anyway is meh + */ +#define XCB_EVENT_RESPONSE_TYPE_MASK (0x7f) + +enum atom_name { + WL_SURFACE_ID, + WL_SURFACE_SERIAL, + WM_DELETE_WINDOW, + WM_PROTOCOLS, + WM_HINTS, + WM_NORMAL_HINTS, + WM_SIZE_HINTS, + WM_WINDOW_ROLE, + MOTIF_WM_HINTS, + UTF8_STRING, + WM_S0, + NET_SUPPORTED, + NET_WM_CM_S0, + NET_WM_PID, + NET_WM_NAME, + NET_WM_STATE, + NET_WM_STRUT_PARTIAL, + NET_WM_WINDOW_TYPE, + WM_TAKE_FOCUS, + WINDOW, + NET_ACTIVE_WINDOW, + NET_WM_MOVERESIZE, + NET_SUPPORTING_WM_CHECK, + NET_WM_STATE_FOCUSED, + NET_WM_STATE_MODAL, + NET_WM_STATE_FULLSCREEN, + NET_WM_STATE_MAXIMIZED_VERT, + NET_WM_STATE_MAXIMIZED_HORZ, + NET_WM_STATE_HIDDEN, + NET_WM_PING, + WM_CHANGE_STATE, + WM_STATE, + CLIPBOARD, + PRIMARY, + WL_SELECTION, + TARGETS, + CLIPBOARD_MANAGER, + INCR, + TEXT, + TIMESTAMP, + DELETE, + NET_STARTUP_ID, + NET_STARTUP_INFO, + NET_STARTUP_INFO_BEGIN, + NET_WM_WINDOW_TYPE_NORMAL, + NET_WM_WINDOW_TYPE_UTILITY, + NET_WM_WINDOW_TYPE_TOOLTIP, + NET_WM_WINDOW_TYPE_DND, + NET_WM_WINDOW_TYPE_DROPDOWN_MENU, + NET_WM_WINDOW_TYPE_POPUP_MENU, + NET_WM_WINDOW_TYPE_COMBO, + NET_WM_WINDOW_TYPE_MENU, + NET_WM_WINDOW_TYPE_NOTIFICATION, + NET_WM_WINDOW_TYPE_SPLASH, + NET_WM_WINDOW_TYPE_DESKTOP, + DND_SELECTION, + DND_AWARE, + DND_STATUS, + DND_POSITION, + DND_ENTER, + DND_LEAVE, + DND_DROP, + DND_FINISHED, + DND_PROXY, + DND_TYPE_LIST, + DND_ACTION_MOVE, + DND_ACTION_COPY, + DND_ACTION_ASK, + DND_ACTION_PRIVATE, + NET_CLIENT_LIST, + NET_CLIENT_LIST_STACKING, + NET_WORKAREA, + ATOM_LAST // keep last +}; + +struct wlr_xwm { + struct wlr_xwayland *xwayland; + struct wl_event_source *event_source; + struct wlr_seat *seat; + uint32_t ping_timeout; + + xcb_atom_t atoms[ATOM_LAST]; + xcb_connection_t *xcb_conn; + xcb_screen_t *screen; + xcb_window_t window; + xcb_visualid_t visual_id; + xcb_colormap_t colormap; + xcb_render_pictformat_t render_format_id; + xcb_cursor_t cursor; + + struct wlr_xwm_selection clipboard_selection; + struct wlr_xwm_selection primary_selection; + struct wlr_xwm_selection dnd_selection; + + struct wlr_xwayland_surface *focus_surface; + + // Surfaces in creation order + struct wl_list surfaces; // wlr_xwayland_surface.link + // Surfaces in bottom-to-top stacking order, for _NET_CLIENT_LIST_STACKING + struct wl_list surfaces_in_stack_order; // wlr_xwayland_surface.stack_link + struct wl_list unpaired_surfaces; // wlr_xwayland_surface.unpaired_link + struct wl_list pending_startup_ids; // pending_startup_id + + struct wlr_drag *drag; + struct wlr_xwayland_surface *drag_focus; + + const xcb_query_extension_reply_t *xfixes; + const xcb_query_extension_reply_t *xres; + uint32_t xfixes_major_version; +#if HAVE_XCB_ERRORS + xcb_errors_context_t *errors_context; +#endif + unsigned int last_focus_seq; + + struct wl_listener compositor_new_surface; + struct wl_listener compositor_destroy; + struct wl_listener shell_v1_new_surface; + struct wl_listener seat_set_selection; + struct wl_listener seat_set_primary_selection; + struct wl_listener seat_start_drag; + struct wl_listener seat_drag_focus; + struct wl_listener seat_drag_motion; + struct wl_listener seat_drag_drop; + struct wl_listener seat_drag_destroy; + struct wl_listener seat_drag_source_destroy; +}; + +struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland, int wm_fd); + +void xwm_destroy(struct wlr_xwm *xwm); + +void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride, + uint32_t width, uint32_t height, int32_t hotspot_x, int32_t hotspot_y); + +int xwm_handle_selection_event(struct wlr_xwm *xwm, xcb_generic_event_t *event); +int xwm_handle_selection_client_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev); + +void xwm_set_seat(struct wlr_xwm *xwm, struct wlr_seat *seat); + +char *xwm_get_atom_name(struct wlr_xwm *xwm, xcb_atom_t atom); +bool xwm_atoms_contains(struct wlr_xwm *xwm, xcb_atom_t *atoms, + size_t num_atoms, enum atom_name needle); + +#endif diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..c3ea7fc --- /dev/null +++ b/meson.build @@ -0,0 +1,213 @@ +project( + 'wlroots', + 'c', + version: '0.18.0-dev', + license: 'MIT', + meson_version: '>=0.59.0', + default_options: [ + 'c_std=c11', + 'warning_level=2', + 'werror=true', + ], +) + +version = meson.project_version().split('-')[0] +version_major = version.split('.')[0] +version_minor = version.split('.')[1] +assert(version_major == '0') +soversion = version_minor.to_int() - 5 + +little_endian = target_machine.endian() == 'little' +big_endian = target_machine.endian() == 'big' + +add_project_arguments([ + '-D_POSIX_C_SOURCE=200809L', + '-DWLR_USE_UNSTABLE', + '-DWLR_LITTLE_ENDIAN=@0@'.format(little_endian.to_int()), + '-DWLR_BIG_ENDIAN=@0@'.format(big_endian.to_int()), +], language: 'c') + +cc = meson.get_compiler('c') + +add_project_arguments(cc.get_supported_arguments([ + '-Wundef', + '-Wlogical-op', + '-Wmissing-include-dirs', + '-Wold-style-definition', + '-Wpointer-arith', + '-Winit-self', + '-Wstrict-prototypes', + '-Wimplicit-fallthrough=2', + '-Wendif-labels', + '-Wstrict-aliasing=2', + '-Woverflow', + '-Wmissing-prototypes', + '-Walloca', + + '-Wno-missing-braces', + '-Wno-missing-field-initializers', + '-Wno-unused-parameter', +]), language: 'c') + +# Compute the relative path used by compiler invocations. +source_root = meson.current_source_dir().split('/') +build_root = meson.global_build_root().split('/') +relative_dir_parts = [] +i = 0 +in_prefix = true +foreach p : build_root + if i >= source_root.length() or not in_prefix or p != source_root[i] + in_prefix = false + relative_dir_parts += '..' + endif + i += 1 +endforeach +i = 0 +in_prefix = true +foreach p : source_root + if i >= build_root.length() or not in_prefix or build_root[i] != p + in_prefix = false + relative_dir_parts += p + endif + i += 1 +endforeach +relative_dir = join_paths(relative_dir_parts) + '/' + +# Strip relative path prefixes from the code if possible, otherwise hide them. +if cc.has_argument('-fmacro-prefix-map=/prefix/to/hide=') + add_project_arguments( + '-fmacro-prefix-map=@0@='.format(relative_dir), + language: 'c', + ) +else + add_project_arguments( + '-D_WLR_REL_SRC_DIR="@0@"'.format(relative_dir), + language: 'c', + ) +endif + +features = { + 'drm-backend': false, + 'x11-backend': false, + 'libinput-backend': false, + 'xwayland': false, + 'gles2-renderer': false, + 'vulkan-renderer': false, + 'gbm-allocator': false, + 'session': false, +} +internal_features = { + 'xcb-errors': false, + 'egl': false, +} +internal_config = configuration_data() + +wayland_project_options = ['tests=false', 'documentation=false'] +wayland_server = dependency('wayland-server', + version: '>=1.22', + fallback: 'wayland', + default_options: wayland_project_options, +) + +drm = dependency('libdrm', + version: '>=2.4.120', + fallback: 'libdrm', + default_options: [ + 'intel=disabled', + 'radeon=disabled', + 'amdgpu=disabled', + 'nouveau=disabled', + 'vmwgfx=disabled', + 'omap=disabled', + 'exynos=disabled', + 'freedreno=disabled', + 'tegra=disabled', + 'vc4=disabled', + 'etnaviv=disabled', + 'cairo-tests=disabled', + 'man-pages=disabled', + 'valgrind=disabled', + 'tests=false', + ], +) +xkbcommon = dependency( + 'xkbcommon', + fallback: 'libxkbcommon', + default_options: [ + 'enable-tools=false', + 'enable-x11=false', + 'enable-docs=false', + 'enable-xkbregistry=false', + ], +) +pixman = dependency('pixman-1', + version: '>=0.42.0', + fallback: 'pixman', + default_options: ['werror=false'], +) +math = cc.find_library('m') +rt = cc.find_library('rt') + +wlr_files = [] +wlr_deps = [ + wayland_server, + drm, + xkbcommon, + pixman, + math, + rt, +] + +subdir('protocol') +subdir('render') + +subdir('backend') +subdir('types') +subdir('util') +subdir('xcursor') +subdir('xwayland') + +subdir('include') + +wlr_inc = include_directories('include') + +symbols_file = 'wlroots.syms' +symbols_flag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), symbols_file) +lib_wlr = library( + meson.project_name(), wlr_files, + soversion: soversion.to_string(), + dependencies: wlr_deps, + include_directories: [wlr_inc], + install: true, + link_args: symbols_flag, + link_depends: symbols_file, +) + +wlr_vars = {} +foreach name, have : features + wlr_vars += { 'have_' + name.underscorify(): have.to_string() } +endforeach + +wlroots = declare_dependency( + link_with: lib_wlr, + dependencies: wlr_deps, + include_directories: wlr_inc, + variables: wlr_vars, +) + +meson.override_dependency('wlroots', wlroots) + +summary(features + internal_features, bool_yn: true) + +if get_option('examples') + subdir('examples') + subdir('tinywl') +endif + +pkgconfig = import('pkgconfig') +pkgconfig.generate( + lib_wlr, + description: 'Wayland compositor library', + url: 'https://gitlab.freedesktop.org/wlroots/wlroots', + variables: wlr_vars, +) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..6977643 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,9 @@ +option('xcb-errors', type: 'feature', value: 'auto', description: 'Use xcb-errors util library') +option('xwayland', type: 'feature', value: 'auto', yield: true, description: 'Enable support for X11 applications') +option('examples', type: 'boolean', value: true, description: 'Build example applications') +option('icon_directory', description: 'Location used to look for cursors (default: ${datadir}/icons)', type: 'string', value: '') +option('renderers', type: 'array', choices: ['auto', 'gles2', 'vulkan'], value: ['auto'], description: 'Select built-in renderers') +option('backends', type: 'array', choices: ['auto', 'drm', 'libinput', 'x11'], value: ['auto'], description: 'Select built-in backends') +option('allocators', type: 'array', choices: ['auto', 'gbm'], value: ['auto'], + description: 'Select built-in allocators') +option('session', type: 'feature', value: 'auto', description: 'Enable session support') diff --git a/protocol/drm.xml b/protocol/drm.xml new file mode 100644 index 0000000..eaf2654 --- /dev/null +++ b/protocol/drm.xml @@ -0,0 +1,189 @@ + + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2011 Intel Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that\n the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bitmask of capabilities. + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/protocol/input-method-unstable-v2.xml b/protocol/input-method-unstable-v2.xml new file mode 100644 index 0000000..51bccf2 --- /dev/null +++ b/protocol/input-method-unstable-v2.xml @@ -0,0 +1,494 @@ + + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2011 Intel Corporation + Copyright © 2012-2013 Collabora, Ltd. + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + Copyright © 2017, 2018 Red Hat, Inc. + Copyright © 2018 Purism SPC + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows applications to act as input methods for compositors. + + An input method context is used to manage the state of the input method. + + Text strings are UTF-8 encoded, their indices and lengths are in bytes. + + This document adheres to the RFC 2119 when using words like "must", + "should", "may", etc. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + An input method object allows for clients to compose text. + + The objects connects the client to a text input in an application, and + lets the client to serve as an input method for a seat. + + The zwp_input_method_v2 object can occupy two distinct states: active and + inactive. In the active state, the object is associated to and + communicates with a text input. In the inactive state, there is no + associated text input, and the only communication is with the compositor. + Initially, the input method is in the inactive state. + + Requests issued in the inactive state must be accepted by the compositor. + Because of the serial mechanism, and the state reset on activate event, + they will not have any effect on the state of the next text input. + + There must be no more than one input method object per seat. + + + + + + + + + Notification that a text input focused on this seat requested the input + method to be activated. + + This event serves the purpose of providing the compositor with an + active input method. + + This event resets all state associated with previous enable, disable, + surrounding_text, text_change_cause, and content_type events, as well + as the state associated with set_preedit_string, commit_string, and + delete_surrounding_text requests. In addition, it marks the + zwp_input_method_v2 object as active, and makes any existing + zwp_input_popup_surface_v2 objects visible. + + The surrounding_text, and content_type events must follow before the + next done event if the text input supports the respective + functionality. + + State set with this event is double-buffered. It will get applied on + the next zwp_input_method_v2.done event, and stay valid until changed. + + + + + + Notification that no focused text input currently needs an active + input method on this seat. + + This event marks the zwp_input_method_v2 object as inactive. The + compositor must make all existing zwp_input_popup_surface_v2 objects + invisible until the next activate event. + + State set with this event is double-buffered. It will get applied on + the next zwp_input_method_v2.done event, and stay valid until changed. + + + + + + Updates the surrounding plain text around the cursor, excluding the + preedit text. + + If any preedit text is present, it is replaced with the cursor for the + purpose of this event. + + The argument text is a buffer containing the preedit string, and must + include the cursor position, and the complete selection. It should + contain additional characters before and after these. There is a + maximum length of wayland messages, so text can not be longer than 4000 + bytes. + + cursor is the byte offset of the cursor within the text buffer. + + anchor is the byte offset of the selection anchor within the text + buffer. If there is no selected text, anchor must be the same as + cursor. + + If this event does not arrive before the first done event, the input + method may assume that the text input does not support this + functionality and ignore following surrounding_text events. + + Values set with this event are double-buffered. They will get applied + and set to initial values on the next zwp_input_method_v2.done + event. + + The initial state for affected fields is empty, meaning that the text + input does not support sending surrounding text. If the empty values + get applied, subsequent attempts to change them may have no effect. + + + + + + + + + Tells the input method why the text surrounding the cursor changed. + + Whenever the client detects an external change in text, cursor, or + anchor position, it must issue this request to the compositor. This + request is intended to give the input method a chance to update the + preedit text in an appropriate way, e.g. by removing it when the user + starts typing with a keyboard. + + cause describes the source of the change. + + The value set with this event is double-buffered. It will get applied + and set to its initial value on the next zwp_input_method_v2.done + event. + + The initial value of cause is input_method. + + + + + + + Indicates the content type and hint for the current + zwp_input_method_v2 instance. + + Values set with this event are double-buffered. They will get applied + on the next zwp_input_method_v2.done event. + + The initial value for hint is none, and the initial value for purpose + is normal. + + + + + + + + Atomically applies state changes recently sent to the client. + + The done event establishes and updates the state of the client, and + must be issued after any changes to apply them. + + Text input state (content purpose, content hint, surrounding text, and + change cause) is conceptually double-buffered within an input method + context. + + Events modify the pending state, as opposed to the current state in use + by the input method. A done event atomically applies all pending state, + replacing the current state. After done, the new pending state is as + documented for each related request. + + Events must be applied in the order of arrival. + + Neither current nor pending state are modified unless noted otherwise. + + + + + + Send the commit string text for insertion to the application. + + Inserts a string at current cursor position (see commit event + sequence). The string to commit could be either just a single character + after a key press or the result of some composing. + + The argument text is a buffer containing the string to insert. There is + a maximum length of wayland messages, so text can not be longer than + 4000 bytes. + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.commit request. + + The initial value of text is an empty string. + + + + + + + Send the pre-edit string text to the application text input. + + Place a new composing text (pre-edit) at the current cursor position. + Any previously set composing text must be removed. Any previously + existing selected text must be removed. The cursor is moved to a new + position within the preedit string. + + The argument text is a buffer containing the preedit string. There is + a maximum length of wayland messages, so text can not be longer than + 4000 bytes. + + The arguments cursor_begin and cursor_end are counted in bytes relative + to the beginning of the submitted string buffer. Cursor should be + hidden by the text input when both are equal to -1. + + cursor_begin indicates the beginning of the cursor. cursor_end + indicates the end of the cursor. It may be equal or different than + cursor_begin. + + Values set with this event are double-buffered. They must be applied on + the next zwp_input_method_v2.commit event. + + The initial value of text is an empty string. The initial value of + cursor_begin, and cursor_end are both 0. + + + + + + + + + Remove the surrounding text. + + before_length and after_length are the number of bytes before and after + the current cursor index (excluding the preedit text) to delete. + + If any preedit text is present, it is replaced with the cursor for the + purpose of this event. In effect before_length is counted from the + beginning of preedit text, and after_length from its end (see commit + event sequence). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_input_method_v2.commit request. + + The initial values of both before_length and after_length are 0. + + + + + + + + Apply state changes from commit_string, set_preedit_string and + delete_surrounding_text requests. + + The state relating to these events is double-buffered, and each one + modifies the pending state. This request replaces the current state + with the pending state. + + The connected text input is expected to proceed by evaluating the + changes in the following order: + + 1. Replace existing preedit string with the cursor. + 2. Delete requested surrounding text. + 3. Insert commit string with the cursor at its end. + 4. Calculate surrounding text to send. + 5. Insert new preedit text in cursor position. + 6. Place cursor inside preedit text. + + The serial number reflects the last state of the zwp_input_method_v2 + object known to the client. The value of the serial argument must be + equal to the number of done events already issued by that object. When + the compositor receives a commit request with a serial different than + the number of past done events, it must proceed as normal, except it + should not change the current state of the zwp_input_method_v2 object. + + + + + + + Creates a new zwp_input_popup_surface_v2 object wrapping a given + surface. + + The surface gets assigned the "input_popup" role. If the surface + already has an assigned role, the compositor must issue a protocol + error. + + + + + + + + Allow an input method to receive hardware keyboard input and process + key events to generate text events (with pre-edit) over the wire. This + allows input methods which compose multiple key events for inputting + text like it is done for CJK languages. + + The compositor should send all keyboard events on the seat to the grab + holder via the returned wl_keyboard object. Nevertheless, the + compositor may decide not to forward any particular event. The + compositor must not further process any event after it has been + forwarded to the grab holder. + + Releasing the resulting wl_keyboard object releases the grab. + + + + + + + The input method ceased to be available. + + The compositor must issue this event as the only event on the object if + there was another input_method object associated with the same seat at + the time of its creation. + + The compositor must issue this request when the object is no longer + usable, e.g. due to seat removal. + + The input method context becomes inert and should be destroyed after + deactivation is handled. Any further requests and events except for the + destroy request must be ignored. + + + + + + Destroys the zwp_text_input_v2 object and any associated child + objects, i.e. zwp_input_popup_surface_v2 and + zwp_input_method_keyboard_grab_v2. + + + + + + + This interface marks a surface as a popup for interacting with an input + method. + + The compositor should place it near the active text input area. It must + be visible if and only if the input method is in the active state. + + The client must not destroy the underlying wl_surface while the + zwp_input_popup_surface_v2 object exists. + + + + + Notify about the position of the area of the text input expressed as a + rectangle in surface local coordinates. + + This is a hint to the input method telling it the relative position of + the text being entered. + + + + + + + + + + + + + + The zwp_input_method_keyboard_grab_v2 interface represents an exclusive + grab of the wl_keyboard interface associated with the seat. + + + + + This event provides a file descriptor to the client which can be + memory-mapped to provide a keyboard mapping description. + + + + + + + + + A key was pressed or released. + The time argument is a timestamp with millisecond granularity, with an + undefined base. + + + + + + + + + + Notifies clients that the modifier and/or group state has changed, and + it should update its local state. + + + + + + + + + + + + + + + Informs the client about the keyboard's repeat rate and delay. + + This event is sent as soon as the zwp_input_method_keyboard_grab_v2 + object has been created, and is guaranteed to be received by the + client before any key press event. + + Negative values for either rate or delay are illegal. A rate of zero + will disable any repeating (regardless of the value of delay). + + This event can be sent later on as well with a new value if necessary, + so clients should continue listening for the event past the creation + of zwp_input_method_keyboard_grab_v2. + + + + + + + + + The input method manager allows the client to become the input method on + a chosen seat. + + No more than one input method must be associated with any seat at any + given time. + + + + + Request a new input zwp_input_method_v2 object associated with a given + seat. + + + + + + + + Destroys the zwp_input_method_manager_v2 object. + + The zwp_input_method_v2 objects originating from it remain valid. + + + + diff --git a/protocol/meson.build b/protocol/meson.build new file mode 100644 index 0000000..6a8d82b --- /dev/null +++ b/protocol/meson.build @@ -0,0 +1,98 @@ +wayland_protos = dependency('wayland-protocols', + version: '>=1.33', + fallback: 'wayland-protocols', + default_options: ['tests=false'], +) +wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') + +wayland_scanner_dep = dependency('wayland-scanner', native: true) +wayland_scanner = find_program( + wayland_scanner_dep.get_variable('wayland_scanner'), + native: true, +) + +protocols = { + # Stable upstream protocols + 'linux-dmabuf-v1': wl_protocol_dir / 'stable/linux-dmabuf/linux-dmabuf-v1.xml', + 'presentation-time': wl_protocol_dir / 'stable/presentation-time/presentation-time.xml', + 'viewporter': wl_protocol_dir / 'stable/viewporter/viewporter.xml', + 'xdg-shell': wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', + + # Staging upstream protocols + 'content-type-v1': wl_protocol_dir / 'staging/content-type/content-type-v1.xml', + 'cursor-shape-v1': wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', + 'drm-lease-v1': wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', + 'ext-foreign-toplevel-list-v1': wl_protocol_dir / 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml', + 'ext-idle-notify-v1': wl_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', + 'ext-session-lock-v1': wl_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', + 'fractional-scale-v1': wl_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', + 'security-context-v1': wl_protocol_dir / 'staging/security-context/security-context-v1.xml', + 'single-pixel-buffer-v1': wl_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', + 'xdg-activation-v1': wl_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml', + 'xwayland-shell-v1': wl_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml', + 'tearing-control-v1': wl_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', + + # Unstable upstream protocols + 'fullscreen-shell-unstable-v1': wl_protocol_dir / 'unstable/fullscreen-shell/fullscreen-shell-unstable-v1.xml', + 'idle-inhibit-unstable-v1': wl_protocol_dir / 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml', + 'keyboard-shortcuts-inhibit-unstable-v1': wl_protocol_dir / 'unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml', + 'pointer-constraints-unstable-v1': wl_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', + 'pointer-gestures-unstable-v1': wl_protocol_dir / 'unstable/pointer-gestures/pointer-gestures-unstable-v1.xml', + 'primary-selection-unstable-v1': wl_protocol_dir / 'unstable/primary-selection/primary-selection-unstable-v1.xml', + 'relative-pointer-unstable-v1': wl_protocol_dir / 'unstable/relative-pointer/relative-pointer-unstable-v1.xml', + 'tablet-unstable-v2': wl_protocol_dir / 'unstable/tablet/tablet-unstable-v2.xml', + 'text-input-unstable-v3': wl_protocol_dir / 'unstable/text-input/text-input-unstable-v3.xml', + 'xdg-decoration-unstable-v1': wl_protocol_dir / 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml', + 'xdg-foreign-unstable-v1': wl_protocol_dir / 'unstable/xdg-foreign/xdg-foreign-unstable-v1.xml', + 'xdg-foreign-unstable-v2': wl_protocol_dir / 'unstable/xdg-foreign/xdg-foreign-unstable-v2.xml', + 'xdg-output-unstable-v1': wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', + 'ext-transient-seat-v1': wl_protocol_dir / 'staging/ext-transient-seat/ext-transient-seat-v1.xml', + + # Other protocols + 'drm': 'drm.xml', + 'input-method-unstable-v2': 'input-method-unstable-v2.xml', + 'kde-server-decoration': 'server-decoration.xml', + 'virtual-keyboard-unstable-v1': 'virtual-keyboard-unstable-v1.xml', + 'wlr-data-control-unstable-v1': 'wlr-data-control-unstable-v1.xml', + 'wlr-export-dmabuf-unstable-v1': 'wlr-export-dmabuf-unstable-v1.xml', + 'wlr-foreign-toplevel-management-unstable-v1': 'wlr-foreign-toplevel-management-unstable-v1.xml', + 'wlr-gamma-control-unstable-v1': 'wlr-gamma-control-unstable-v1.xml', + 'wlr-layer-shell-unstable-v1': 'wlr-layer-shell-unstable-v1.xml', + 'wlr-output-management-unstable-v1': 'wlr-output-management-unstable-v1.xml', + 'wlr-output-power-management-unstable-v1': 'wlr-output-power-management-unstable-v1.xml', + 'wlr-screencopy-unstable-v1': 'wlr-screencopy-unstable-v1.xml', + 'wlr-virtual-pointer-unstable-v1': 'wlr-virtual-pointer-unstable-v1.xml', +} + +protocols_code = {} +protocols_server_header = {} +protocols_client_header = {} +foreach name, path : protocols + code = custom_target( + name.underscorify() + '_c', + input: path, + output: '@BASENAME@-protocol.c', + command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], + ) + wlr_files += code + + server_header = custom_target( + name.underscorify() + '_server_h', + input: path, + output: '@BASENAME@-protocol.h', + command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'], + ) + wlr_files += server_header + + client_header = custom_target( + name.underscorify() + '_client_h', + input: path, + output: '@BASENAME@-client-protocol.h', + command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], + build_by_default: false, + ) + + protocols_code += { name: code } + protocols_server_header += { name: server_header } + protocols_client_header += { name: client_header } +endforeach diff --git a/protocol/server-decoration.xml b/protocol/server-decoration.xml new file mode 100644 index 0000000..45f1128 --- /dev/null +++ b/protocol/server-decoration.xml @@ -0,0 +1,94 @@ + + + . + ]]> + + + This interface allows to coordinate whether the server should create + a server-side window decoration around a wl_surface representing a + shell surface (wl_shell_surface or similar). By announcing support + for this interface the server indicates that it supports server + side decorations. + + + + When a client creates a server-side decoration object it indicates + that it supports the protocol. The client is supposed to tell the + server whether it wants server-side decorations or will provide + client-side decorations. + + If the client does not create a server-side decoration object for + a surface the server interprets this as lack of support for this + protocol and considers it as client-side decorated. Nevertheless a + client-side decorated surface should use this protocol to indicate + to the server that it does not want a server-side deco. + + + + + + + + + + + + + This event is emitted directly after binding the interface. It contains + the default mode for the decoration. When a new server decoration object + is created this new object will be in the default mode until the first + request_mode is requested. + + The server may change the default mode at any time. + + + + + + + + + + + + + + + + + + + + + This event is emitted directly after the decoration is created and + represents the base decoration policy by the server. E.g. a server + which wants all surfaces to be client-side decorated will send Client, + a server which wants server-side decoration will send Server. + + The client can request a different mode through the decoration request. + The server will acknowledge this by another event with the same mode. So + even if a server prefers server-side decoration it's possible to force a + client-side decoration. + + The server may emit this event at any time. In this case the client can + again request a different mode. It's the responsibility of the server to + prevent a feedback loop. + + + + + diff --git a/protocol/virtual-keyboard-unstable-v1.xml b/protocol/virtual-keyboard-unstable-v1.xml new file mode 100644 index 0000000..5095c91 --- /dev/null +++ b/protocol/virtual-keyboard-unstable-v1.xml @@ -0,0 +1,113 @@ + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2013 Intel Corporation + Copyright © 2012-2013 Collabora, Ltd. + Copyright © 2018 Purism SPC + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The virtual keyboard provides an application with requests which emulate + the behaviour of a physical keyboard. + + This interface can be used by clients on its own to provide raw input + events, or it can accompany the input method protocol. + + + + + Provide a file descriptor to the compositor which can be + memory-mapped to provide a keyboard mapping description. + + Format carries a value from the keymap_format enumeration. + + + + + + + + + + + + + A key was pressed or released. + The time argument is a timestamp with millisecond granularity, with an + undefined base. All requests regarding a single object must share the + same clock. + + Keymap must be set before issuing this request. + + State carries a value from the key_state enumeration. + + + + + + + + + Notifies the compositor that the modifier and/or group state has + changed, and it should update state. + + The client should use wl_keyboard.modifiers event to synchronize its + internal state with seat state. + + Keymap must be set before issuing this request. + + + + + + + + + + + + + + + A virtual keyboard manager allows an application to provide keyboard + input events as if they came from a physical keyboard. + + + + + + + + + Creates a new virtual keyboard associated to a seat. + + If the compositor enables a keyboard to perform arbitrary actions, it + should present an error when an untrusted client requests a new + keyboard. + + + + + + diff --git a/protocol/wlr-data-control-unstable-v1.xml b/protocol/wlr-data-control-unstable-v1.xml new file mode 100644 index 0000000..75e8671 --- /dev/null +++ b/protocol/wlr-data-control-unstable-v1.xml @@ -0,0 +1,278 @@ + + + + Copyright © 2018 Simon Ser + Copyright © 2019 Ivan Molodetskikh + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol allows a privileged client to control data devices. In + particular, the client will be able to manage the current selection and take + the role of a clipboard manager. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-seat data device + controls. + + + + + Create a new data source. + + + + + + + Create a data device that can be used to manage a seat's selection. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This interface allows a client to manage a seat's selection. + + When the seat is destroyed, this object becomes inert. + + + + + This request asks the compositor to set the selection to the data from + the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the selection, set the source to NULL. + + + + + + + Destroys the data device object. + + + + + + The data_offer event introduces a new wlr_data_control_offer object, + which will subsequently be used in either the + wlr_data_control_device.selection event (for the regular clipboard + selections) or the wlr_data_control_device.primary_selection event (for + the primary clipboard selections). Immediately following the + wlr_data_control_device.data_offer event, the new data_offer object + will send out wlr_data_control_offer.offer events to describe the MIME + types it offers. + + + + + + + The selection event is sent out to notify the client of a new + wlr_data_control_offer for the selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The selection event is sent to a client when a new + selection is set. The wlr_data_control_offer is valid until a new + wlr_data_control_offer or NULL is received. The client must destroy the + previous selection wlr_data_control_offer, if any, upon receiving this + event. + + The first selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This data control object is no longer valid and should be destroyed by + the client. + + + + + + + + The primary_selection event is sent out to notify the client of a new + wlr_data_control_offer for the primary selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The primary_selection event is sent to a client when a + new primary selection is set. The wlr_data_control_offer is valid until + a new wlr_data_control_offer or NULL is received. The client must + destroy the previous primary selection wlr_data_control_offer, if any, + upon receiving this event. + + If the compositor supports primary selection, the first + primary_selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This request asks the compositor to set the primary selection to the + data from the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the primary selection, set the source to NULL. + + The compositor will ignore this request if it does not support primary + selection. + + + + + + + + + + + + The wlr_data_control_source object is the source side of a + wlr_data_control_offer. It is created by the source client in a data + transfer and provides a way to describe the offered data and a way to + respond to requests to transfer the data. + + + + + + + + + This request adds a MIME type to the set of MIME types advertised to + targets. Can be called several times to offer multiple types. + + Calling this after wlr_data_control_device.set_selection is a protocol + error. + + + + + + + Destroys the data source object. + + + + + + Request for data from the client. Send the data as the specified MIME + type over the passed file descriptor, then close it. + + + + + + + + This data source is no longer valid. The data source has been replaced + by another data source. + + The client should clean up and destroy this data source. + + + + + + + A wlr_data_control_offer represents a piece of data offered for transfer + by another client (the source client). The offer describes the different + MIME types that the data can be converted to and provides the mechanism + for transferring the data directly from the source client. + + + + + To transfer the offered data, the client issues this request and + indicates the MIME type it wants to receive. The transfer happens + through the passed file descriptor (typically created with the pipe + system call). The source client writes the data in the MIME type + representation requested and then closes the file descriptor. + + The receiving client reads from the read end of the pipe until EOF and + then closes its end, at which point the transfer is complete. + + This request may happen multiple times for different MIME types. + + + + + + + + Destroys the data offer object. + + + + + + Sent immediately after creating the wlr_data_control_offer object. + One event per offered MIME type. + + + + + diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml new file mode 100644 index 0000000..751f7ef --- /dev/null +++ b/protocol/wlr-export-dmabuf-unstable-v1.xml @@ -0,0 +1,203 @@ + + + + Copyright © 2018 Rostislav Pehlivanov + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + An interface to capture surfaces in an efficient way by exporting DMA-BUFs. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This object is a manager with which to start capturing from sources. + + + + + Capture the next frame of a an entire output. + + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object represents a single DMA-BUF frame. + + If the capture is successful, the compositor will first send a "frame" + event, followed by one or several "object". When the frame is available + for readout, the "ready" event is sent. + + If the capture failed, the "cancel" event is sent. This can happen anytime + before the "ready" event. + + Once either a "ready" or a "cancel" event is received, the client should + destroy the frame. Once an "object" event is received, the client is + responsible for closing the associated file descriptor. + + All frames are read-only and may not be written into or altered. + + + + + Special flags that should be respected by the client. + + + + + + + Main event supplying the client with information about the frame. If the + capture didn't fail, this event is always emitted first before any other + events. + + This event is followed by a number of "object" as specified by the + "num_objects" argument. + + + + + + + + + + + + + + + + Event which serves to supply the client with the file descriptors + containing the data for each object. + + After receiving this event, the client must always close the file + descriptor as soon as they're done with it and even if the frame fails. + + + + + + + + + + + + This event is sent as soon as the frame is presented, indicating it is + available for reading. This event includes the time at which + presentation happened at. + + The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, + each component being an unsigned 32-bit value. Whole seconds are in + tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, + and the additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part + may have an arbitrary offset at start. + + After receiving this event, the client should destroy this object. + + + + + + + + + Indicates reason for cancelling the frame. + + + + + + + + + If the capture failed or if the frame is no longer valid after the + "frame" event has been emitted, this event will be used to inform the + client to scrap the frame. + + If the failure is temporary, the client may capture again the same + source. If the failure is permanent, any further attempts to capture the + same source will fail again. + + After receiving this event, the client should destroy this object. + + + + + + + Unreferences the frame. This request must be called as soon as its no + longer used. + + It can be called at any time by the client. The client will still have + to close any FDs it has been given. + + + + diff --git a/protocol/wlr-foreign-toplevel-management-unstable-v1.xml b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml new file mode 100644 index 0000000..1081337 --- /dev/null +++ b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,270 @@ + + + + Copyright © 2018 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + + + + + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + + + + + + + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + + + + + + + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + + + + + This event is emitted whenever the title of the toplevel changes. + + + + + + + This event is emitted whenever the app-id of the toplevel changes. + + + + + + + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + + + + + + + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + + + + + + + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + + + + + + + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + + + + + + + + + + + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + + + + + + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + + + + + + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + + + + + + + + + + + + + + + + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + + + + + + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + + + + + + + + Requests that the toplevel be fullscreened on the given output. If the + fullscreen state and/or the outputs the toplevel is visible on actually + change, this will be indicated by the state and output_enter/leave + events. + + The output parameter is only a hint to the compositor. Also, if output + is NULL, the compositor should decide which output the toplevel will be + fullscreened on, if at all. + + + + + + + Requests that the toplevel be unfullscreened. If the fullscreen state + actually changes, this will be indicated by the state event. + + + + + + + + This event is emitted whenever the parent of the toplevel changes. + + No event is emitted when the parent handle is destroyed by the client. + + + + + diff --git a/protocol/wlr-gamma-control-unstable-v1.xml b/protocol/wlr-gamma-control-unstable-v1.xml new file mode 100644 index 0000000..a9db762 --- /dev/null +++ b/protocol/wlr-gamma-control-unstable-v1.xml @@ -0,0 +1,126 @@ + + + + Copyright © 2015 Giulio camuffo + Copyright © 2018 Simon Ser + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol allows a privileged client to set the gamma tables for + outputs. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-output gamma + controls. + + + + + Create a gamma control that can be used to adjust gamma tables for the + provided output. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This interface allows a client to adjust gamma tables for a particular + output. + + The client will receive the gamma size, and will then be able to set gamma + tables. At any time the compositor can send a failed event indicating that + this object is no longer valid. + + There must always be at most one gamma control object per output, which + has exclusive access to this particular output. When the gamma control + object is destroyed, the gamma table is restored to its original value. + + + + + Advertise the size of each gamma ramp. + + This event is sent immediately when the gamma control object is created. + + + + + + + + + + + Set the gamma table. The file descriptor can be memory-mapped to provide + the raw gamma table, which contains successive gamma ramps for the red, + green and blue channels. Each gamma ramp is an array of 16-byte unsigned + integers which has the same length as the gamma size. + + The file descriptor data must have the same length as three times the + gamma size. + + + + + + + This event indicates that the gamma control is no longer valid. This + can happen for a number of reasons, including: + - The output doesn't support gamma tables + - Setting the gamma tables failed + - Another client already has exclusive gamma control for this output + - The compositor has transferred gamma control to another client + + Upon receiving this event, the client should destroy this object. + + + + + + Destroys the gamma control object. If the object is still valid, this + restores the original gamma tables. + + + + diff --git a/protocol/wlr-layer-shell-unstable-v1.xml b/protocol/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..d62fd51 --- /dev/null +++ b/protocol/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,390 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + + + + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + diff --git a/protocol/wlr-output-management-unstable-v1.xml b/protocol/wlr-output-management-unstable-v1.xml new file mode 100644 index 0000000..411e2f0 --- /dev/null +++ b/protocol/wlr-output-management-unstable-v1.xml @@ -0,0 +1,601 @@ + + + + Copyright © 2019 Purism SPC + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol exposes interfaces to obtain and modify output device + configuration. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows reading and writing the current + output device configuration. + + Output devices that display pixels (e.g. a physical monitor or a virtual + output in a window) are represented as heads. Heads cannot be created nor + destroyed by the client, but they can be enabled or disabled and their + properties can be changed. Each head may have one or more available modes. + + Whenever a head appears (e.g. a monitor is plugged in), it will be + advertised via the head event. Immediately after the output manager is + bound, all current heads are advertised. + + Whenever a head's properties change, the relevant wlr_output_head events + will be sent. Not all head properties will be sent: only properties that + have changed need to. + + Whenever a head disappears (e.g. a monitor is unplugged), a + wlr_output_head.finished event will be sent. + + After one or more heads appear, change or disappear, the done event will + be sent. It carries a serial which can be used in a create_configuration + request to update heads properties. + + The information obtained from this protocol should only be used for output + configuration purposes. This protocol is not designed to be a generic + output property advertisement protocol for regular clients. Instead, + protocols such as xdg-output should be used. + + + + + This event introduces a new head. This happens whenever a new head + appears (e.g. a monitor is plugged in) or after the output manager is + bound. + + + + + + + This event is sent after all information has been sent after binding to + the output manager object and after any subsequent changes. This applies + to child head and mode objects as well. In other words, this event is + sent whenever a head or mode is created or destroyed and whenever one of + their properties has been changed. Not all state is re-sent each time + the current configuration changes: only the actual changes are sent. + + This allows changes to the output configuration to be seen as atomic, + even if they happen via multiple events. + + A serial is sent to be used in a future create_configuration request. + + + + + + + Create a new output configuration object. This allows to update head + properties. + + + + + + + + Indicates the client no longer wishes to receive events for output + configuration changes. However the compositor may emit further events, + until the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + This event indicates that the compositor is done sending manager events. + The compositor will destroy the object immediately after sending this + event, so it will become invalid and the client should release any + resources associated with it. + + + + + + + A head is an output device. The difference between a wl_output object and + a head is that heads are advertised even if they are turned off. A head + object only advertises properties and cannot be used directly to change + them. + + A head has some read-only properties: modes, name, description and + physical_size. These cannot be changed by clients. + + Other properties can be updated via a wlr_output_configuration object. + + Properties sent via this interface are applied atomically via the + wlr_output_manager.done event. No guarantees are made regarding the order + in which properties are sent. + + + + + This event describes the head name. + + The naming convention is compositor defined, but limited to alphanumeric + characters and dashes (-). Each name is unique among all wlr_output_head + objects, but if a wlr_output_head object is destroyed the same name may + be reused later. The names will also remain consistent across sessions + with the same hardware and software configuration. + + Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do + not assume that the name is a reflection of an underlying DRM + connector, X11 connection, etc. + + If the compositor implements the xdg-output protocol and this head is + enabled, the xdg_output.name event must report the same name. + + The name event is sent after a wlr_output_head object is created. This + event is only sent once per object, and the name does not change over + the lifetime of the wlr_output_head object. + + + + + + + This event describes a human-readable description of the head. + + The description is a UTF-8 string with no convention defined for its + contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 + output via :1'. However, do not assume that the name is a reflection of + the make, model, serial of the underlying DRM connector or the display + name of the underlying X11 connection, etc. + + If the compositor implements xdg-output and this head is enabled, + the xdg_output.description must report the same description. + + The description event is sent after a wlr_output_head object is created. + This event is only sent once per object, and the description does not + change over the lifetime of the wlr_output_head object. + + + + + + + This event describes the physical size of the head. This event is only + sent if the head has a physical size (e.g. is not a projector or a + virtual device). + + + + + + + + This event introduces a mode for this head. It is sent once per + supported mode. + + + + + + + This event describes whether the head is enabled. A disabled head is not + mapped to a region of the global compositor space. + + When a head is disabled, some properties (current_mode, position, + transform and scale) are irrelevant. + + + + + + + This event describes the mode currently in use for this head. It is only + sent if the output is enabled. + + + + + + + This events describes the position of the head in the global compositor + space. It is only sent if the output is enabled. + + + + + + + + This event describes the transformation currently applied to the head. + It is only sent if the output is enabled. + + + + + + + This events describes the scale of the head in the global compositor + space. It is only sent if the output is enabled. + + + + + + + This event indicates that the head is no longer available. The head + object becomes inert. Clients should send a destroy request and release + any resources associated with it. + + + + + + + + This event describes the manufacturer of the head. + + This must report the same make as the wl_output interface does in its + geometry event. + + Together with the model and serial_number events the purpose is to + allow clients to recognize heads from previous sessions and for example + load head-specific configurations back. + + It is not guaranteed this event will be ever sent. A reason for that + can be that the compositor does not have information about the make of + the head or the definition of a make is not sensible in the current + setup, for example in a virtual session. Clients can still try to + identify the head by available information from other events but should + be aware that there is an increased risk of false positives. + + It is not recommended to display the make string in UI to users. For + that the string provided by the description event should be preferred. + + + + + + + This event describes the model of the head. + + This must report the same model as the wl_output interface does in its + geometry event. + + Together with the make and serial_number events the purpose is to + allow clients to recognize heads from previous sessions and for example + load head-specific configurations back. + + It is not guaranteed this event will be ever sent. A reason for that + can be that the compositor does not have information about the model of + the head or the definition of a model is not sensible in the current + setup, for example in a virtual session. Clients can still try to + identify the head by available information from other events but should + be aware that there is an increased risk of false positives. + + It is not recommended to display the model string in UI to users. For + that the string provided by the description event should be preferred. + + + + + + + This event describes the serial number of the head. + + Together with the make and model events the purpose is to allow clients + to recognize heads from previous sessions and for example load head- + specific configurations back. + + It is not guaranteed this event will be ever sent. A reason for that + can be that the compositor does not have information about the serial + number of the head or the definition of a serial number is not sensible + in the current setup. Clients can still try to identify the head by + available information from other events but should be aware that there + is an increased risk of false positives. + + It is not recommended to display the serial_number string in UI to + users. For that the string provided by the description event should be + preferred. + + + + + + + + + This request indicates that the client will no longer use this head + object. + + + + + + + + + + + + + This event describes whether adaptive sync is currently enabled for + the head or not. Adaptive sync is also known as Variable Refresh + Rate or VRR. + + + + + + + + This object describes an output mode. + + Some heads don't support output modes, in which case modes won't be + advertised. + + Properties sent via this interface are applied atomically via the + wlr_output_manager.done event. No guarantees are made regarding the order + in which properties are sent. + + + + + This event describes the mode size. The size is given in physical + hardware units of the output device. This is not necessarily the same as + the output size in the global compositor space. For instance, the output + may be scaled or transformed. + + + + + + + + This event describes the mode's fixed vertical refresh rate. It is only + sent if the mode has a fixed refresh rate. + + + + + + + This event advertises this mode as preferred. + + + + + + This event indicates that the mode is no longer available. The mode + object becomes inert. Clients should send a destroy request and release + any resources associated with it. + + + + + + + + This request indicates that the client will no longer use this mode + object. + + + + + + + This object is used by the client to describe a full output configuration. + + First, the client needs to setup the output configuration. Each head can + be either enabled (and configured) or disabled. It is a protocol error to + send two enable_head or disable_head requests with the same head. It is a + protocol error to omit a head in a configuration. + + Then, the client can apply or test the configuration. The compositor will + then reply with a succeeded, failed or cancelled event. Finally the client + should destroy the configuration object. + + + + + + + + + + + Enable a head. This request creates a head configuration object that can + be used to change the head's properties. + + + + + + + + Disable a head. + + + + + + + Apply the new output configuration. + + In case the configuration is successfully applied, there is no guarantee + that the new output state matches completely the requested + configuration. For instance, a compositor might round the scale if it + doesn't support fractional scaling. + + After this request has been sent, the compositor must respond with an + succeeded, failed or cancelled event. Sending a request that isn't the + destructor is a protocol error. + + + + + + Test the new output configuration. The configuration won't be applied, + but will only be validated. + + Even if the compositor succeeds to test a configuration, applying it may + fail. + + After this request has been sent, the compositor must respond with an + succeeded, failed or cancelled event. Sending a request that isn't the + destructor is a protocol error. + + + + + + Sent after the compositor has successfully applied the changes or + tested them. + + Upon receiving this event, the client should destroy this object. + + If the current configuration has changed, events to describe the changes + will be sent followed by a wlr_output_manager.done event. + + + + + + Sent if the compositor rejects the changes or failed to apply them. The + compositor should revert any changes made by the apply request that + triggered this event. + + Upon receiving this event, the client should destroy this object. + + + + + + Sent if the compositor cancels the configuration because the state of an + output changed and the client has outdated information (e.g. after an + output has been hotplugged). + + The client can create a new configuration with a newer serial and try + again. + + Upon receiving this event, the client should destroy this object. + + + + + + Using this request a client can tell the compositor that it is not going + to use the configuration object anymore. Any changes to the outputs + that have not been applied will be discarded. + + This request also destroys wlr_output_configuration_head objects created + via this object. + + + + + + + This object is used by the client to update a single head's configuration. + + It is a protocol error to set the same property twice. + + + + + + + + + + + + + + This request sets the head's mode. + + + + + + + This request assigns a custom mode to the head. The size is given in + physical hardware units of the output device. If set to zero, the + refresh rate is unspecified. + + It is a protocol error to set both a mode and a custom mode. + + + + + + + + + This request sets the head's position in the global compositor space. + + + + + + + + This request sets the head's transform. + + + + + + + This request sets the head's scale. + + + + + + + + + This request enables/disables adaptive sync. Adaptive sync is also + known as Variable Refresh Rate or VRR. + + + + + diff --git a/protocol/wlr-output-power-management-unstable-v1.xml b/protocol/wlr-output-power-management-unstable-v1.xml new file mode 100644 index 0000000..a977839 --- /dev/null +++ b/protocol/wlr-output-power-management-unstable-v1.xml @@ -0,0 +1,128 @@ + + + + Copyright © 2019 Purism SPC + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows clients to control power management modes + of outputs that are currently part of the compositor space. The + intent is to allow special clients like desktop shells to power + down outputs when the system is idle. + + To modify outputs not currently part of the compositor space see + wlr-output-management. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-output power + management mode controls. + + + + + Create a output power management mode control that can be used to + adjust the power management mode for a given output. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object offers requests to set the power management mode of + an output. + + + + + + + + + + + + + + Set an output's power save mode to the given mode. The mode change + is effective immediately. If the output does not support the given + mode a failed event is sent. + + + + + + + Report the power management mode change of an output. + + The mode event is sent after an output changed its power + management mode. The reason can be a client using set_mode or the + compositor deciding to change an output's mode. + This event is also sent immediately when the object is created + so the client is informed about the current power management mode. + + + + + + + This event indicates that the output power management mode control + is no longer valid. This can happen for a number of reasons, + including: + - The output doesn't support power management + - Another client already has exclusive power management mode control + for this output + - The output disappeared + + Upon receiving this event, the client should destroy this object. + + + + + + Destroys the output power management mode control object. + + + + diff --git a/protocol/wlr-screencopy-unstable-v1.xml b/protocol/wlr-screencopy-unstable-v1.xml new file mode 100644 index 0000000..50b1b7d --- /dev/null +++ b/protocol/wlr-screencopy-unstable-v1.xml @@ -0,0 +1,232 @@ + + + + Copyright © 2018 Simon Ser + Copyright © 2019 Andri Yngvason + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows clients to ask the compositor to copy part of the + screen content to a client buffer. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This object is a manager which offers requests to start capturing from a + source. + + + + + Capture the next frame of an entire output. + + + + + + + + + Capture the next frame of an output's region. + + The region is given in output logical coordinates, see + xdg_output.logical_size. The region will be clipped to the output's + extents. + + + + + + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object represents a single frame. + + When created, a series of buffer events will be sent, each representing a + supported buffer type. The "buffer_done" event is sent afterwards to + indicate that all supported buffer types have been enumerated. The client + will then be able to send a "copy" request. If the capture is successful, + the compositor will send a "flags" followed by a "ready" event. + + For objects version 2 or lower, wl_shm buffers are always supported, ie. + the "buffer" event is guaranteed to be sent. + + If the capture failed, the "failed" event is sent. This can happen anytime + before the "ready" event. + + Once either a "ready" or a "failed" event is received, the client should + destroy the frame. + + + + + Provides information about wl_shm buffer parameters that need to be + used for this frame. This event is sent once after the frame is created + if wl_shm buffers are supported. + + + + + + + + + + Copy the frame to the supplied buffer. The buffer must have a the + correct size, see zwlr_screencopy_frame_v1.buffer and + zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a + supported format. + + If the frame is successfully copied, a "flags" and a "ready" events are + sent. Otherwise, a "failed" event is sent. + + + + + + + + + + + + + + + + Provides flags about the frame. This event is sent once before the + "ready" event. + + + + + + + Called as soon as the frame is copied, indicating it is available + for reading. This event includes the time at which presentation happened + at. + + The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, + each component being an unsigned 32-bit value. Whole seconds are in + tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, + and the additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part + may have an arbitrary offset at start. + + After receiving this event, the client should destroy the object. + + + + + + + + + This event indicates that the attempted frame copy has failed. + + After receiving this event, the client should destroy the object. + + + + + + Destroys the frame. This request can be sent at any time by the client. + + + + + + + Same as copy, except it waits until there is damage to copy. + + + + + + + This event is sent right before the ready event when copy_with_damage is + requested. It may be generated multiple times for each copy_with_damage + request. + + The arguments describe a box around an area that has changed since the + last copy request that was derived from the current screencopy manager + instance. + + The union of all regions received between the call to copy_with_damage + and a ready event is the total damage since the prior ready event. + + + + + + + + + + + Provides information about linux-dmabuf buffer parameters that need to + be used for this frame. This event is sent once after the frame is + created if linux-dmabuf buffers are supported. + + + + + + + + + This event is sent once after all buffer events have been sent. + + The client should proceed to create a buffer of one of the supported + types, and send a "copy" request. + + + + diff --git a/protocol/wlr-virtual-pointer-unstable-v1.xml b/protocol/wlr-virtual-pointer-unstable-v1.xml new file mode 100644 index 0000000..ea243e7 --- /dev/null +++ b/protocol/wlr-virtual-pointer-unstable-v1.xml @@ -0,0 +1,152 @@ + + + + Copyright © 2019 Josef Gajdusek + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This protocol allows clients to emulate a physical pointer device. The + requests are mostly mirror opposites of those specified in wl_pointer. + + + + + + + + + + The pointer has moved by a relative amount to the previous request. + + Values are in the global compositor space. + + + + + + + + + The pointer has moved in an absolute coordinate frame. + + Value of x can range from 0 to x_extent, value of y can range from 0 + to y_extent. + + + + + + + + + + + A button was pressed or released. + + + + + + + + + Scroll and other axis requests. + + + + + + + + + Indicates the set of events that logically belong together. + + + + + + Source information for scroll and other axis. + + + + + + + Stop notification for scroll and other axes. + + + + + + + + Discrete step information for scroll and other axes. + + This event allows the client to extend data normally sent using the axis + event with discrete value. + + + + + + + + + + + + + + + This object allows clients to create individual virtual pointer objects. + + + + + Creates a new virtual pointer. The optional seat is a suggestion to the + compositor. + + + + + + + + + + + + + Creates a new virtual pointer. The seat and the output arguments are + optional. If the seat argument is set, the compositor should assign the + input device to the requested seat. If the output argument is set, the + compositor should map the input device to the requested output. + + + + + + + diff --git a/render/allocator/allocator.c b/render/allocator/allocator.c new file mode 100644 index 0000000..639b52b --- /dev/null +++ b/render/allocator/allocator.c @@ -0,0 +1,186 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/backend.h" +#include "render/allocator/allocator.h" +#include "render/allocator/drm_dumb.h" +#include "render/allocator/shm.h" +#include "render/wlr_renderer.h" + +#if WLR_HAS_GBM_ALLOCATOR +#include "render/allocator/gbm.h" +#endif + +void wlr_allocator_init(struct wlr_allocator *alloc, + const struct wlr_allocator_interface *impl, uint32_t buffer_caps) { + assert(impl && impl->destroy && impl->create_buffer); + *alloc = (struct wlr_allocator){ + .impl = impl, + .buffer_caps = buffer_caps, + }; + wl_signal_init(&alloc->events.destroy); +} + +/* Re-open the DRM node to avoid GEM handle ref'counting issues. See: + * https://gitlab.freedesktop.org/mesa/drm/-/merge_requests/110 + */ +static int reopen_drm_node(int drm_fd, bool allow_render_node) { + if (drmIsMaster(drm_fd)) { + // Only recent kernels support empty leases + uint32_t lessee_id; + int lease_fd = drmModeCreateLease(drm_fd, NULL, 0, O_CLOEXEC, &lessee_id); + if (lease_fd >= 0) { + return lease_fd; + } else if (lease_fd != -EINVAL && lease_fd != -EOPNOTSUPP) { + wlr_log_errno(WLR_ERROR, "drmModeCreateLease failed"); + return -1; + } + wlr_log(WLR_DEBUG, "drmModeCreateLease failed, " + "falling back to plain open"); + } + + char *name = NULL; + if (allow_render_node) { + name = drmGetRenderDeviceNameFromFd(drm_fd); + } + if (name == NULL) { + // Either the DRM device has no render node, either the caller wants + // a primary node + name = drmGetDeviceNameFromFd2(drm_fd); + if (name == NULL) { + wlr_log(WLR_ERROR, "drmGetDeviceNameFromFd2 failed"); + return -1; + } + } + + int new_fd = open(name, O_RDWR | O_CLOEXEC); + if (new_fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open DRM node '%s'", name); + free(name); + return -1; + } + + free(name); + + // If we're using a DRM primary node and we are DRM master (e.g. because + // we're running under the DRM backend), we need to use the legacy DRM + // authentication mechanism to have the permission to manipulate DRM dumb + // buffers. + if (drmIsMaster(drm_fd) && drmGetNodeTypeFromFd(new_fd) == DRM_NODE_PRIMARY) { + drm_magic_t magic; + if (drmGetMagic(new_fd, &magic) < 0) { + wlr_log_errno(WLR_ERROR, "drmGetMagic failed"); + close(new_fd); + return -1; + } + + if (drmAuthMagic(drm_fd, magic) < 0) { + wlr_log_errno(WLR_ERROR, "drmAuthMagic failed"); + close(new_fd); + return -1; + } + } + + return new_fd; +} + +struct wlr_allocator *allocator_autocreate_with_drm_fd( + uint32_t backend_caps, struct wlr_renderer *renderer, + int drm_fd) { + uint32_t renderer_caps = renderer_get_render_buffer_caps(renderer); + + struct wlr_allocator *alloc = NULL; + + uint32_t gbm_caps = WLR_BUFFER_CAP_DMABUF; + if ((backend_caps & gbm_caps) && (renderer_caps & gbm_caps) + && drm_fd >= 0) { +#if WLR_HAS_GBM_ALLOCATOR + wlr_log(WLR_DEBUG, "Trying to create gbm allocator"); + int gbm_fd = reopen_drm_node(drm_fd, true); + if (gbm_fd < 0) { + return NULL; + } + if ((alloc = wlr_gbm_allocator_create(gbm_fd)) != NULL) { + return alloc; + } + close(gbm_fd); + wlr_log(WLR_DEBUG, "Failed to create gbm allocator"); +#else + wlr_log(WLR_DEBUG, "Skipping gbm allocator: disabled at compile-time"); +#endif + } + + uint32_t shm_caps = WLR_BUFFER_CAP_SHM | WLR_BUFFER_CAP_DATA_PTR; + if ((backend_caps & shm_caps) && (renderer_caps & shm_caps)) { + wlr_log(WLR_DEBUG, "Trying to create shm allocator"); + if ((alloc = wlr_shm_allocator_create()) != NULL) { + return alloc; + } + wlr_log(WLR_DEBUG, "Failed to create shm allocator"); + } + + uint32_t drm_caps = WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_DATA_PTR; + if ((backend_caps & drm_caps) && (renderer_caps & drm_caps) + && drm_fd >= 0 && drmIsMaster(drm_fd)) { + wlr_log(WLR_DEBUG, "Trying to create drm dumb allocator"); + int dumb_fd = reopen_drm_node(drm_fd, false); + if (dumb_fd < 0) { + return NULL; + } + if ((alloc = wlr_drm_dumb_allocator_create(dumb_fd)) != NULL) { + return alloc; + } + close(dumb_fd); + wlr_log(WLR_DEBUG, "Failed to create drm dumb allocator"); + } + + wlr_log(WLR_ERROR, "Failed to create allocator"); + return NULL; +} + +struct wlr_allocator *wlr_allocator_autocreate(struct wlr_backend *backend, + struct wlr_renderer *renderer) { + uint32_t backend_caps = backend_get_buffer_caps(backend); + // Note, drm_fd may be negative if unavailable + int drm_fd = wlr_backend_get_drm_fd(backend); + if (drm_fd < 0) { + drm_fd = wlr_renderer_get_drm_fd(renderer); + } + + return allocator_autocreate_with_drm_fd(backend_caps, renderer, drm_fd); +} + +void wlr_allocator_destroy(struct wlr_allocator *alloc) { + if (alloc == NULL) { + return; + } + wl_signal_emit_mutable(&alloc->events.destroy, NULL); + alloc->impl->destroy(alloc); +} + +struct wlr_buffer *wlr_allocator_create_buffer(struct wlr_allocator *alloc, + int width, int height, const struct wlr_drm_format *format) { + struct wlr_buffer *buffer = + alloc->impl->create_buffer(alloc, width, height, format); + if (buffer == NULL) { + return NULL; + } + if (alloc->buffer_caps & WLR_BUFFER_CAP_DATA_PTR) { + assert(buffer->impl->begin_data_ptr_access && + buffer->impl->end_data_ptr_access); + } + if (alloc->buffer_caps & WLR_BUFFER_CAP_DMABUF) { + assert(buffer->impl->get_dmabuf); + } + if (alloc->buffer_caps & WLR_BUFFER_CAP_SHM) { + assert(buffer->impl->get_shm); + } + return buffer; +} diff --git a/render/allocator/drm_dumb.c b/render/allocator/drm_dumb.c new file mode 100644 index 0000000..eb4ce99 --- /dev/null +++ b/render/allocator/drm_dumb.c @@ -0,0 +1,230 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "render/allocator/drm_dumb.h" +#include "render/drm_format_set.h" +#include "render/pixel_format.h" + +static const struct wlr_buffer_impl buffer_impl; + +static struct wlr_drm_dumb_buffer *drm_dumb_buffer_from_buffer( + struct wlr_buffer *wlr_buf) { + assert(wlr_buf->impl == &buffer_impl); + struct wlr_drm_dumb_buffer *buf = wl_container_of(wlr_buf, buf, base); + return buf; +} + +static struct wlr_drm_dumb_buffer *create_buffer( + struct wlr_drm_dumb_allocator *alloc, int width, int height, + const struct wlr_drm_format *format) { + if (!wlr_drm_format_has(format, DRM_FORMAT_MOD_INVALID) && + !wlr_drm_format_has(format, DRM_FORMAT_MOD_LINEAR)) { + wlr_log(WLR_ERROR, "DRM dumb allocator only supports INVALID and " + "LINEAR modifiers"); + return NULL; + } + + const struct wlr_pixel_format_info *info = + drm_get_pixel_format_info(format->format); + if (info == NULL) { + wlr_log(WLR_ERROR, "DRM format 0x%"PRIX32" not supported", + format->format); + return NULL; + } else if (pixel_format_info_pixels_per_block(info) != 1) { + wlr_log(WLR_ERROR, "Block formats are not supported"); + return NULL; + } + + struct wlr_drm_dumb_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + return NULL; + } + wlr_buffer_init(&buffer->base, &buffer_impl, width, height); + wl_list_insert(&alloc->buffers, &buffer->link); + + buffer->drm_fd = alloc->drm_fd; + + uint32_t bpp = 8 * info->bytes_per_block; + if (drmModeCreateDumbBuffer(alloc->drm_fd, width, height, bpp, 0, + &buffer->handle, &buffer->stride, &buffer->size) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to create DRM dumb buffer"); + goto create_destroy; + } + + buffer->width = width; + buffer->height = height; + buffer->format = format->format; + + uint64_t offset; + if (drmModeMapDumbBuffer(alloc->drm_fd, buffer->handle, &offset) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to map DRM dumb buffer"); + goto create_destroy; + } + + buffer->data = mmap(NULL, buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, + alloc->drm_fd, offset); + if (buffer->data == MAP_FAILED) { + wlr_log_errno(WLR_ERROR, "Failed to mmap DRM dumb buffer"); + goto create_destroy; + } + + memset(buffer->data, 0, buffer->size); + + int prime_fd; + if (drmPrimeHandleToFD(alloc->drm_fd, buffer->handle, DRM_CLOEXEC, + &prime_fd) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to get PRIME handle from GEM handle"); + goto create_destroy; + } + + buffer->dmabuf = (struct wlr_dmabuf_attributes){ + .width = buffer->width, + .height = buffer->height, + .format = format->format, + .modifier = DRM_FORMAT_MOD_LINEAR, + .n_planes = 1, + .offset[0] = 0, + .stride[0] = buffer->stride, + .fd[0] = prime_fd, + }; + + wlr_log(WLR_DEBUG, "Allocated %"PRIu32"x%"PRIu32" DRM dumb buffer", + buffer->width, buffer->height); + + return buffer; + +create_destroy: + wlr_buffer_drop(&buffer->base); + return NULL; +} + +static bool drm_dumb_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, + uint32_t flags, void **data, uint32_t *format, size_t *stride) { + struct wlr_drm_dumb_buffer *buf = drm_dumb_buffer_from_buffer(wlr_buffer); + *data = buf->data; + *stride = buf->stride; + *format = buf->format; + return true; +} + +static void drm_dumb_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { + // This space is intentionally left blank +} + +static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buffer, + struct wlr_dmabuf_attributes *attribs) { + struct wlr_drm_dumb_buffer *buf = drm_dumb_buffer_from_buffer(wlr_buffer); + *attribs = buf->dmabuf; + return true; +} + +static void buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct wlr_drm_dumb_buffer *buf = drm_dumb_buffer_from_buffer(wlr_buffer); + + if (buf->data) { + munmap(buf->data, buf->size); + } + + wlr_dmabuf_attributes_finish(&buf->dmabuf); + + if (buf->drm_fd >= 0) { + if (drmModeDestroyDumbBuffer(buf->drm_fd, buf->handle) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to destroy DRM dumb buffer"); + } + } + + wl_list_remove(&buf->link); + free(buf); +} + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = buffer_destroy, + .get_dmabuf = buffer_get_dmabuf, + .begin_data_ptr_access = drm_dumb_buffer_begin_data_ptr_access, + .end_data_ptr_access = drm_dumb_buffer_end_data_ptr_access, +}; + +static const struct wlr_allocator_interface allocator_impl; + +static struct wlr_drm_dumb_allocator *drm_dumb_alloc_from_alloc( + struct wlr_allocator *wlr_alloc) { + assert(wlr_alloc->impl == &allocator_impl); + struct wlr_drm_dumb_allocator *alloc = wl_container_of(wlr_alloc, alloc, base); + return alloc; +} + +static struct wlr_buffer *allocator_create_buffer( + struct wlr_allocator *wlr_alloc, int width, int height, + const struct wlr_drm_format *drm_format) { + struct wlr_drm_dumb_allocator *alloc = drm_dumb_alloc_from_alloc(wlr_alloc); + struct wlr_drm_dumb_buffer *buffer = create_buffer(alloc, width, height, + drm_format); + if (buffer == NULL) { + return NULL; + } + return &buffer->base; +} + +static void allocator_destroy(struct wlr_allocator *wlr_alloc) { + struct wlr_drm_dumb_allocator *alloc = drm_dumb_alloc_from_alloc(wlr_alloc); + + struct wlr_drm_dumb_buffer *buf, *buf_tmp; + wl_list_for_each_safe(buf, buf_tmp, &alloc->buffers, link) { + buf->drm_fd = -1; + wl_list_remove(&buf->link); + wl_list_init(&buf->link); + } + + close(alloc->drm_fd); + free(alloc); +} + +static const struct wlr_allocator_interface allocator_impl = { + .create_buffer = allocator_create_buffer, + .destroy = allocator_destroy, +}; + +struct wlr_allocator *wlr_drm_dumb_allocator_create(int drm_fd) { + if (drmGetNodeTypeFromFd(drm_fd) != DRM_NODE_PRIMARY) { + wlr_log(WLR_ERROR, "Cannot use DRM dumb buffers with non-primary DRM FD"); + return NULL; + } + + uint64_t has_dumb = 0; + if (drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0) { + wlr_log(WLR_ERROR, "Failed to get DRM capabilities"); + return NULL; + } + + if (has_dumb == 0) { + wlr_log(WLR_ERROR, "DRM dumb buffers not supported"); + return NULL; + } + + struct wlr_drm_dumb_allocator *alloc = calloc(1, sizeof(*alloc)); + if (alloc == NULL) { + return NULL; + } + wlr_allocator_init(&alloc->base, &allocator_impl, + WLR_BUFFER_CAP_DATA_PTR | WLR_BUFFER_CAP_DMABUF); + + alloc->drm_fd = drm_fd; + wl_list_init(&alloc->buffers); + + wlr_log(WLR_DEBUG, "Created DRM dumb allocator"); + return &alloc->base; +} diff --git a/render/allocator/gbm.c b/render/allocator/gbm.c new file mode 100644 index 0000000..baa0fb6 --- /dev/null +++ b/render/allocator/gbm.c @@ -0,0 +1,252 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "render/allocator/gbm.h" +#include "render/drm_format_set.h" + +static const struct wlr_buffer_impl buffer_impl; + +static struct wlr_gbm_buffer *get_gbm_buffer_from_buffer( + struct wlr_buffer *wlr_buffer) { + assert(wlr_buffer->impl == &buffer_impl); + struct wlr_gbm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + return buffer; +} + +static bool export_gbm_bo(struct gbm_bo *bo, + struct wlr_dmabuf_attributes *out) { + struct wlr_dmabuf_attributes attribs = {0}; + + attribs.n_planes = gbm_bo_get_plane_count(bo); + if (attribs.n_planes > WLR_DMABUF_MAX_PLANES) { + wlr_log(WLR_ERROR, "GBM BO contains too many planes (%d)", + attribs.n_planes); + return false; + } + + attribs.width = gbm_bo_get_width(bo); + attribs.height = gbm_bo_get_height(bo); + attribs.format = gbm_bo_get_format(bo); + attribs.modifier = gbm_bo_get_modifier(bo); + + int i; + int32_t handle = -1; + for (i = 0; i < attribs.n_planes; ++i) { +#if HAVE_GBM_BO_GET_FD_FOR_PLANE + (void)handle; + + attribs.fd[i] = gbm_bo_get_fd_for_plane(bo, i); + if (attribs.fd[i] < 0) { + wlr_log(WLR_ERROR, "gbm_bo_get_fd_for_plane failed"); + goto error_fd; + } +#else + // GBM is lacking a function to get a FD for a given plane. Instead, + // check all planes have the same handle. We can't use + // drmPrimeHandleToFD because that messes up handle ref'counting in + // the user-space driver. + union gbm_bo_handle plane_handle = gbm_bo_get_handle_for_plane(bo, i); + if (plane_handle.s32 < 0) { + wlr_log(WLR_ERROR, "gbm_bo_get_handle_for_plane failed"); + goto error_fd; + } + if (i == 0) { + handle = plane_handle.s32; + } else if (plane_handle.s32 != handle) { + wlr_log(WLR_ERROR, "Failed to export GBM BO: " + "all planes don't have the same GEM handle"); + goto error_fd; + } + + attribs.fd[i] = gbm_bo_get_fd(bo); + if (attribs.fd[i] < 0) { + wlr_log(WLR_ERROR, "gbm_bo_get_fd failed"); + goto error_fd; + } +#endif + + attribs.offset[i] = gbm_bo_get_offset(bo, i); + attribs.stride[i] = gbm_bo_get_stride_for_plane(bo, i); + } + + *out = attribs; + return true; + +error_fd: + for (int j = 0; j < i; ++j) { + close(attribs.fd[j]); + } + return false; +} + +static struct wlr_gbm_buffer *create_buffer(struct wlr_gbm_allocator *alloc, + int width, int height, const struct wlr_drm_format *format) { + struct gbm_device *gbm_device = alloc->gbm_device; + + assert(format->len > 0); + + bool has_modifier = true; + uint64_t fallback_modifier = DRM_FORMAT_MOD_INVALID; + struct gbm_bo *bo = gbm_bo_create_with_modifiers(gbm_device, width, height, + format->format, format->modifiers, format->len); + if (bo == NULL) { + uint32_t usage = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING; + if (format->len == 1 && + format->modifiers[0] == DRM_FORMAT_MOD_LINEAR) { + usage |= GBM_BO_USE_LINEAR; + fallback_modifier = DRM_FORMAT_MOD_LINEAR; + } else if (!wlr_drm_format_has(format, DRM_FORMAT_MOD_INVALID)) { + // If the format doesn't accept an implicit modifier, bail out. + wlr_log(WLR_ERROR, "gbm_bo_create_with_modifiers failed"); + return NULL; + } + bo = gbm_bo_create(gbm_device, width, height, format->format, usage); + has_modifier = false; + } + if (bo == NULL) { + wlr_log(WLR_ERROR, "gbm_bo_create failed"); + return NULL; + } + + struct wlr_gbm_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + gbm_bo_destroy(bo); + return NULL; + } + wlr_buffer_init(&buffer->base, &buffer_impl, width, height); + buffer->gbm_bo = bo; + wl_list_insert(&alloc->buffers, &buffer->link); + + if (!export_gbm_bo(bo, &buffer->dmabuf)) { + free(buffer); + gbm_bo_destroy(bo); + return NULL; + } + + // If the buffer has been allocated with an implicit modifier, make sure we + // don't populate the modifier field: other parts of the stack may not + // understand modifiers, and they can't strip the modifier. + if (!has_modifier) { + buffer->dmabuf.modifier = fallback_modifier; + } + + char *format_name = drmGetFormatName(buffer->dmabuf.format); + char *modifier_name = drmGetFormatModifierName(buffer->dmabuf.modifier); + wlr_log(WLR_DEBUG, "Allocated %dx%d GBM buffer " + "with format %s (0x%08"PRIX32"), modifier %s (0x%016"PRIX64")", + buffer->base.width, buffer->base.height, + format_name ? format_name : "", buffer->dmabuf.format, + modifier_name ? modifier_name : "", buffer->dmabuf.modifier); + free(format_name); + free(modifier_name); + + return buffer; +} + +static void buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct wlr_gbm_buffer *buffer = + get_gbm_buffer_from_buffer(wlr_buffer); + wlr_dmabuf_attributes_finish(&buffer->dmabuf); + if (buffer->gbm_bo != NULL) { + gbm_bo_destroy(buffer->gbm_bo); + } + wl_list_remove(&buffer->link); + free(buffer); +} + +static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buffer, + struct wlr_dmabuf_attributes *attribs) { + struct wlr_gbm_buffer *buffer = + get_gbm_buffer_from_buffer(wlr_buffer); + *attribs = buffer->dmabuf; + return true; +} + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = buffer_destroy, + .get_dmabuf = buffer_get_dmabuf, +}; + +static const struct wlr_allocator_interface allocator_impl; + +static struct wlr_gbm_allocator *get_gbm_alloc_from_alloc( + struct wlr_allocator *wlr_alloc) { + assert(wlr_alloc->impl == &allocator_impl); + struct wlr_gbm_allocator *alloc = wl_container_of(wlr_alloc, alloc, base); + return alloc; +} + +struct wlr_allocator *wlr_gbm_allocator_create(int fd) { + uint64_t cap; + if (drmGetCap(fd, DRM_CAP_PRIME, &cap) || + !(cap & DRM_PRIME_CAP_EXPORT)) { + wlr_log(WLR_ERROR, "PRIME export not supported"); + return NULL; + } + + struct wlr_gbm_allocator *alloc = calloc(1, sizeof(*alloc)); + if (alloc == NULL) { + return NULL; + } + wlr_allocator_init(&alloc->base, &allocator_impl, WLR_BUFFER_CAP_DMABUF); + + alloc->fd = fd; + wl_list_init(&alloc->buffers); + + alloc->gbm_device = gbm_create_device(fd); + if (alloc->gbm_device == NULL) { + wlr_log(WLR_ERROR, "gbm_create_device failed"); + free(alloc); + return NULL; + } + + wlr_log(WLR_DEBUG, "Created GBM allocator with backend %s", + gbm_device_get_backend_name(alloc->gbm_device)); + char *drm_name = drmGetDeviceNameFromFd2(fd); + wlr_log(WLR_DEBUG, "Using DRM node %s", drm_name); + free(drm_name); + + return &alloc->base; +} + +static void allocator_destroy(struct wlr_allocator *wlr_alloc) { + struct wlr_gbm_allocator *alloc = get_gbm_alloc_from_alloc(wlr_alloc); + + // The gbm_bo objects need to be destroyed before the gbm_device + struct wlr_gbm_buffer *buf, *buf_tmp; + wl_list_for_each_safe(buf, buf_tmp, &alloc->buffers, link) { + gbm_bo_destroy(buf->gbm_bo); + buf->gbm_bo = NULL; + wl_list_remove(&buf->link); + wl_list_init(&buf->link); + } + + gbm_device_destroy(alloc->gbm_device); + close(alloc->fd); + free(alloc); +} + +static struct wlr_buffer *allocator_create_buffer( + struct wlr_allocator *wlr_alloc, int width, int height, + const struct wlr_drm_format *format) { + struct wlr_gbm_allocator *alloc = get_gbm_alloc_from_alloc(wlr_alloc); + struct wlr_gbm_buffer *buffer = create_buffer(alloc, width, height, format); + if (buffer == NULL) { + return NULL; + } + return &buffer->base; +} + +static const struct wlr_allocator_interface allocator_impl = { + .destroy = allocator_destroy, + .create_buffer = allocator_create_buffer, +}; diff --git a/render/allocator/meson.build b/render/allocator/meson.build new file mode 100644 index 0000000..730a2a4 --- /dev/null +++ b/render/allocator/meson.build @@ -0,0 +1,25 @@ +allocators = get_option('allocators') +if 'auto' in allocators and get_option('auto_features').enabled() + allocators = ['gbm'] +elif 'auto' in allocators and get_option('auto_features').disabled() + allocators = [] +endif + +wlr_files += files( + 'allocator.c', + 'shm.c', + 'drm_dumb.c', +) + +gbm = disabler() +if 'gbm' in allocators or 'auto' in allocators + gbm = dependency('gbm', version: '>=17.1.0', required: 'gbm' in allocators) +endif +if gbm.found() + wlr_files += files('gbm.c') + wlr_deps += gbm + features += { 'gbm-allocator': true } + + has = cc.has_function('gbm_bo_get_fd_for_plane', dependencies: [gbm]) + internal_config.set10('HAVE_GBM_BO_GET_FD_FOR_PLANE', has) +endif diff --git a/render/allocator/shm.c b/render/allocator/shm.c new file mode 100644 index 0000000..2622f99 --- /dev/null +++ b/render/allocator/shm.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "render/pixel_format.h" +#include "render/allocator/shm.h" +#include "util/shm.h" + +static const struct wlr_buffer_impl buffer_impl; + +static struct wlr_shm_buffer *shm_buffer_from_buffer( + struct wlr_buffer *wlr_buffer) { + assert(wlr_buffer->impl == &buffer_impl); + struct wlr_shm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + return buffer; +} + +static void buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct wlr_shm_buffer *buffer = shm_buffer_from_buffer(wlr_buffer); + munmap(buffer->data, buffer->size); + close(buffer->shm.fd); + free(buffer); +} + +static bool buffer_get_shm(struct wlr_buffer *wlr_buffer, + struct wlr_shm_attributes *shm) { + struct wlr_shm_buffer *buffer = shm_buffer_from_buffer(wlr_buffer); + *shm = buffer->shm; + return true; +} + +static bool shm_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, + uint32_t flags, void **data, uint32_t *format, size_t *stride) { + struct wlr_shm_buffer *buffer = shm_buffer_from_buffer(wlr_buffer); + *data = buffer->data; + *format = buffer->shm.format; + *stride = buffer->shm.stride; + return true; +} + +static void shm_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { + // This space is intentionally left blank +} + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = buffer_destroy, + .get_shm = buffer_get_shm, + .begin_data_ptr_access = shm_buffer_begin_data_ptr_access, + .end_data_ptr_access = shm_buffer_end_data_ptr_access, +}; + +static struct wlr_buffer *allocator_create_buffer( + struct wlr_allocator *wlr_allocator, int width, int height, + const struct wlr_drm_format *format) { + const struct wlr_pixel_format_info *info = + drm_get_pixel_format_info(format->format); + if (info == NULL) { + wlr_log(WLR_ERROR, "Unsupported pixel format 0x%"PRIX32, format->format); + return NULL; + } + + struct wlr_shm_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + return NULL; + } + wlr_buffer_init(&buffer->base, &buffer_impl, width, height); + + // TODO: consider using a single file for multiple buffers + int stride = pixel_format_info_min_stride(info, width); // TODO: align? + buffer->size = stride * height; + buffer->shm.fd = allocate_shm_file(buffer->size); + if (buffer->shm.fd < 0) { + free(buffer); + return NULL; + } + + buffer->shm.format = format->format; + buffer->shm.width = width; + buffer->shm.height = height; + buffer->shm.stride = stride; + buffer->shm.offset = 0; + + buffer->data = mmap(NULL, buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, + buffer->shm.fd, 0); + if (buffer->data == MAP_FAILED) { + wlr_log_errno(WLR_ERROR, "mmap failed"); + close(buffer->shm.fd); + free(buffer); + return NULL; + } + + return &buffer->base; +} + +static void allocator_destroy(struct wlr_allocator *wlr_allocator) { + free(wlr_allocator); +} + +static const struct wlr_allocator_interface allocator_impl = { + .destroy = allocator_destroy, + .create_buffer = allocator_create_buffer, +}; + +struct wlr_allocator *wlr_shm_allocator_create(void) { + struct wlr_shm_allocator *allocator = calloc(1, sizeof(*allocator)); + if (allocator == NULL) { + return NULL; + } + wlr_allocator_init(&allocator->base, &allocator_impl, + WLR_BUFFER_CAP_DATA_PTR | WLR_BUFFER_CAP_SHM); + + wlr_log(WLR_DEBUG, "Created shm allocator"); + return &allocator->base; +} diff --git a/render/dmabuf.c b/render/dmabuf.c new file mode 100644 index 0000000..7096dbc --- /dev/null +++ b/render/dmabuf.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include "render/dmabuf.h" + +void wlr_dmabuf_attributes_finish(struct wlr_dmabuf_attributes *attribs) { + for (int i = 0; i < attribs->n_planes; ++i) { + close(attribs->fd[i]); + attribs->fd[i] = -1; + } + attribs->n_planes = 0; +} + +bool wlr_dmabuf_attributes_copy(struct wlr_dmabuf_attributes *dst, + const struct wlr_dmabuf_attributes *src) { + *dst = *src; + + int i; + for (i = 0; i < src->n_planes; ++i) { + dst->fd[i] = fcntl(src->fd[i], F_DUPFD_CLOEXEC, 0); + if (dst->fd[i] < 0) { + wlr_log_errno(WLR_ERROR, "fcntl(F_DUPFD_CLOEXEC) failed"); + goto error; + } + } + + return true; + +error: + for (int j = 0; j < i; j++) { + close(dst->fd[j]); + dst->fd[j] = -1; + } + dst->n_planes = 0; + return false; +} diff --git a/render/dmabuf_fallback.c b/render/dmabuf_fallback.c new file mode 100644 index 0000000..820d46d --- /dev/null +++ b/render/dmabuf_fallback.c @@ -0,0 +1,17 @@ +#include + +#include "render/dmabuf.h" + +bool dmabuf_check_sync_file_import_export(void) { + return false; +} + +bool dmabuf_import_sync_file(int dmabuf_fd, uint32_t flags, int sync_file_fd) { + wlr_log(WLR_ERROR, "DMA-BUF sync_file import IOCTL not available on this system"); + return false; +} + +int dmabuf_export_sync_file(int dmabuf_fd, uint32_t flags) { + wlr_log(WLR_ERROR, "DMA-BUF sync_file export IOCTL not available on this system"); + return false; +} diff --git a/render/dmabuf_linux.c b/render/dmabuf_linux.c new file mode 100644 index 0000000..cf06229 --- /dev/null +++ b/render/dmabuf_linux.c @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include +#include + +#include "render/dmabuf.h" + +bool dmabuf_check_sync_file_import_export(void) { + /* Unfortunately there's no better way to check the availability of the + * IOCTL than to check the kernel version. See the discussion at: + * https://lore.kernel.org/dri-devel/20220601161303.64797-1-contact@emersion.fr/ + */ + + struct utsname utsname = {0}; + if (uname(&utsname) != 0) { + wlr_log_errno(WLR_ERROR, "uname failed"); + return false; + } + + if (strcmp(utsname.sysname, "Linux") != 0) { + return false; + } + + // Trim release suffix if any, e.g. "-arch1-1" + for (size_t i = 0; utsname.release[i] != '\0'; i++) { + char ch = utsname.release[i]; + if ((ch < '0' || ch > '9') && ch != '.') { + utsname.release[i] = '\0'; + break; + } + } + + char *rel = strtok(utsname.release, "."); + int major = atoi(rel); + + int minor = 0; + rel = strtok(NULL, "."); + if (rel != NULL) { + minor = atoi(rel); + } + + int patch = 0; + rel = strtok(NULL, "."); + if (rel != NULL) { + patch = atoi(rel); + } + + return KERNEL_VERSION(major, minor, patch) >= KERNEL_VERSION(5, 20, 0); +} + +// TODO: drop these definitions once widespread + +#if !defined(DMA_BUF_IOCTL_IMPORT_SYNC_FILE) + +struct dma_buf_import_sync_file { + __u32 flags; + __s32 fd; +}; + +#define DMA_BUF_IOCTL_IMPORT_SYNC_FILE _IOW(DMA_BUF_BASE, 3, struct dma_buf_import_sync_file) + +#endif + +#if !defined(DMA_BUF_IOCTL_EXPORT_SYNC_FILE) + +struct dma_buf_export_sync_file { + __u32 flags; + __s32 fd; +}; + +#define DMA_BUF_IOCTL_EXPORT_SYNC_FILE _IOWR(DMA_BUF_BASE, 2, struct dma_buf_export_sync_file) + +#endif + +bool dmabuf_import_sync_file(int dmabuf_fd, uint32_t flags, int sync_file_fd) { + struct dma_buf_import_sync_file data = { + .flags = flags, + .fd = sync_file_fd, + }; + if (drmIoctl(dmabuf_fd, DMA_BUF_IOCTL_IMPORT_SYNC_FILE, &data) != 0) { + wlr_log_errno(WLR_ERROR, "drmIoctl(IMPORT_SYNC_FILE) failed"); + return false; + } + return true; +} + +int dmabuf_export_sync_file(int dmabuf_fd, uint32_t flags) { + struct dma_buf_export_sync_file data = { + .flags = flags, + .fd = -1, + }; + if (drmIoctl(dmabuf_fd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &data) != 0) { + wlr_log_errno(WLR_ERROR, "drmIoctl(EXPORT_SYNC_FILE) failed"); + return -1; + } + return data.fd; +} diff --git a/render/drm_format_set.c b/render/drm_format_set.c new file mode 100644 index 0000000..dc57ad9 --- /dev/null +++ b/render/drm_format_set.c @@ -0,0 +1,284 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "render/drm_format_set.h" + +void wlr_drm_format_finish(struct wlr_drm_format *format) { + if (!format) { + return; + } + + free(format->modifiers); +} + +void wlr_drm_format_set_finish(struct wlr_drm_format_set *set) { + for (size_t i = 0; i < set->len; ++i) { + wlr_drm_format_finish(&set->formats[i]); + } + free(set->formats); + + set->len = 0; + set->capacity = 0; + set->formats = NULL; +} + +static struct wlr_drm_format *format_set_get(const struct wlr_drm_format_set *set, + uint32_t format) { + for (size_t i = 0; i < set->len; ++i) { + if (set->formats[i].format == format) { + return &set->formats[i]; + } + } + + return NULL; +} + +const struct wlr_drm_format *wlr_drm_format_set_get( + const struct wlr_drm_format_set *set, uint32_t format) { + return format_set_get(set, format); +} + +bool wlr_drm_format_set_has(const struct wlr_drm_format_set *set, + uint32_t format, uint64_t modifier) { + const struct wlr_drm_format *fmt = wlr_drm_format_set_get(set, format); + if (!fmt) { + return false; + } + return wlr_drm_format_has(fmt, modifier); +} + +bool wlr_drm_format_set_add(struct wlr_drm_format_set *set, uint32_t format, + uint64_t modifier) { + assert(format != DRM_FORMAT_INVALID); + + struct wlr_drm_format *existing = format_set_get(set, format); + if (existing) { + return wlr_drm_format_add(existing, modifier); + } + + struct wlr_drm_format fmt; + wlr_drm_format_init(&fmt, format); + if (!wlr_drm_format_add(&fmt, modifier)) { + wlr_drm_format_finish(&fmt); + return false; + } + + if (set->len == set->capacity) { + size_t capacity = set->capacity ? set->capacity * 2 : 4; + + struct wlr_drm_format *fmts = realloc(set->formats, sizeof(*fmts) * capacity); + if (!fmts) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + wlr_drm_format_finish(&fmt); + return false; + } + + set->capacity = capacity; + set->formats = fmts; + } + + set->formats[set->len++] = fmt; + return true; +} + +void wlr_drm_format_init(struct wlr_drm_format *fmt, uint32_t format) { + *fmt = (struct wlr_drm_format){ + .format = format, + }; +} + +bool wlr_drm_format_has(const struct wlr_drm_format *fmt, uint64_t modifier) { + for (size_t i = 0; i < fmt->len; ++i) { + if (fmt->modifiers[i] == modifier) { + return true; + } + } + return false; +} + +bool wlr_drm_format_add(struct wlr_drm_format *fmt, uint64_t modifier) { + if (wlr_drm_format_has(fmt, modifier)) { + return true; + } + + if (fmt->len == fmt->capacity) { + size_t capacity = fmt->capacity ? fmt->capacity * 2 : 4; + + uint64_t *new_modifiers = realloc(fmt->modifiers, sizeof(*fmt->modifiers) * capacity); + if (!new_modifiers) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return false; + } + + fmt->capacity = capacity; + fmt->modifiers = new_modifiers; + } + + fmt->modifiers[fmt->len++] = modifier; + return true; +} + +bool wlr_drm_format_copy(struct wlr_drm_format *dst, const struct wlr_drm_format *src) { + assert(src->len <= src->capacity); + + uint64_t *modifiers = malloc(sizeof(*modifiers) * src->len); + if (!modifiers) { + return false; + } + + memcpy(modifiers, src->modifiers, sizeof(*modifiers) * src->len); + + wlr_drm_format_finish(dst); + dst->capacity = src->len; + dst->len = src->len; + dst->format = src->format; + dst->modifiers = modifiers; + return true; +} + +bool wlr_drm_format_set_copy(struct wlr_drm_format_set *dst, const struct wlr_drm_format_set *src) { + struct wlr_drm_format *formats = malloc(src->len * sizeof(formats[0])); + if (formats == NULL) { + return false; + } + + struct wlr_drm_format_set out = { + .len = 0, + .capacity = src->len, + .formats = formats, + }; + + size_t i; + for (i = 0; i < src->len; i++) { + out.formats[out.len] = (struct wlr_drm_format){0}; + if (!wlr_drm_format_copy(&out.formats[out.len], &src->formats[i])) { + wlr_drm_format_set_finish(&out); + return false; + } + + out.len++; + } + + *dst = out; + + return true; +} + +bool wlr_drm_format_intersect(struct wlr_drm_format *dst, + const struct wlr_drm_format *a, const struct wlr_drm_format *b) { + assert(a->format == b->format); + + size_t capacity = a->len < b->len ? a->len : b->len; + uint64_t *modifiers = malloc(sizeof(*modifiers) * capacity); + if (!modifiers) { + return false; + } + + struct wlr_drm_format fmt = { + .capacity = capacity, + .len = 0, + .modifiers = modifiers, + .format = a->format, + }; + + for (size_t i = 0; i < a->len; i++) { + for (size_t j = 0; j < b->len; j++) { + if (a->modifiers[i] == b->modifiers[j]) { + assert(fmt.len < fmt.capacity); + fmt.modifiers[fmt.len++] = a->modifiers[i]; + break; + } + } + } + + wlr_drm_format_finish(dst); + *dst = fmt; + return true; +} + +bool wlr_drm_format_set_intersect(struct wlr_drm_format_set *dst, + const struct wlr_drm_format_set *a, const struct wlr_drm_format_set *b) { + struct wlr_drm_format_set out = {0}; + out.capacity = a->len < b->len ? a->len : b->len; + out.formats = malloc(sizeof(*out.formats) * out.capacity); + if (out.formats == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return false; + } + + for (size_t i = 0; i < a->len; i++) { + for (size_t j = 0; j < b->len; j++) { + if (a->formats[i].format == b->formats[j].format) { + // When the two formats have no common modifier, keep + // intersecting the rest of the formats: they may be compatible + // with each other + out.formats[out.len] = (struct wlr_drm_format){0}; + if (!wlr_drm_format_intersect(&out.formats[out.len], + &a->formats[i], &b->formats[j])) { + wlr_drm_format_set_finish(&out); + return false; + } + + if (out.formats[out.len].len == 0) { + wlr_drm_format_finish(&out.formats[out.len]); + } else { + out.len++; + } + + break; + } + } + } + + if (out.len == 0) { + wlr_drm_format_set_finish(&out); + return false; + } + + wlr_drm_format_set_finish(dst); + *dst = out; + return true; +} + +static bool drm_format_set_extend(struct wlr_drm_format_set *dst, + const struct wlr_drm_format_set *src) { + for (size_t i = 0; i < src->len; i++) { + struct wlr_drm_format *format = &src->formats[i]; + for (size_t j = 0; j < format->len; j++) { + if (!wlr_drm_format_set_add(dst, format->format, format->modifiers[j])) { + wlr_log_errno(WLR_ERROR, "Adding format/modifier to set failed"); + return false; + } + } + } + + return true; +} + +bool wlr_drm_format_set_union(struct wlr_drm_format_set *dst, + const struct wlr_drm_format_set *a, const struct wlr_drm_format_set *b) { + struct wlr_drm_format_set out = {0}; + out.capacity = a->len + b->len; + out.formats = malloc(sizeof(*out.formats) * out.capacity); + if (out.formats == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return false; + } + + // Add both a and b sets into out + if (!drm_format_set_extend(&out, a) || + !drm_format_set_extend(&out, b)) { + wlr_drm_format_set_finish(&out); + return false; + } + + wlr_drm_format_set_finish(dst); + *dst = out; + + return true; +} diff --git a/render/egl.c b/render/egl.c new file mode 100644 index 0000000..19868ca --- /dev/null +++ b/render/egl.c @@ -0,0 +1,1016 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "render/egl.h" +#include "util/env.h" + +static enum wlr_log_importance egl_log_importance_to_wlr(EGLint type) { + switch (type) { + case EGL_DEBUG_MSG_CRITICAL_KHR: return WLR_ERROR; + case EGL_DEBUG_MSG_ERROR_KHR: return WLR_ERROR; + case EGL_DEBUG_MSG_WARN_KHR: return WLR_ERROR; + case EGL_DEBUG_MSG_INFO_KHR: return WLR_INFO; + default: return WLR_INFO; + } +} + +static const char *egl_error_str(EGLint error) { + switch (error) { + case EGL_SUCCESS: + return "EGL_SUCCESS"; + case EGL_NOT_INITIALIZED: + return "EGL_NOT_INITIALIZED"; + case EGL_BAD_ACCESS: + return "EGL_BAD_ACCESS"; + case EGL_BAD_ALLOC: + return "EGL_BAD_ALLOC"; + case EGL_BAD_ATTRIBUTE: + return "EGL_BAD_ATTRIBUTE"; + case EGL_BAD_CONTEXT: + return "EGL_BAD_CONTEXT"; + case EGL_BAD_CONFIG: + return "EGL_BAD_CONFIG"; + case EGL_BAD_CURRENT_SURFACE: + return "EGL_BAD_CURRENT_SURFACE"; + case EGL_BAD_DISPLAY: + return "EGL_BAD_DISPLAY"; + case EGL_BAD_DEVICE_EXT: + return "EGL_BAD_DEVICE_EXT"; + case EGL_BAD_SURFACE: + return "EGL_BAD_SURFACE"; + case EGL_BAD_MATCH: + return "EGL_BAD_MATCH"; + case EGL_BAD_PARAMETER: + return "EGL_BAD_PARAMETER"; + case EGL_BAD_NATIVE_PIXMAP: + return "EGL_BAD_NATIVE_PIXMAP"; + case EGL_BAD_NATIVE_WINDOW: + return "EGL_BAD_NATIVE_WINDOW"; + case EGL_CONTEXT_LOST: + return "EGL_CONTEXT_LOST"; + } + return "unknown error"; +} + +static void egl_log(EGLenum error, const char *command, EGLint msg_type, + EGLLabelKHR thread, EGLLabelKHR obj, const char *msg) { + _wlr_log(egl_log_importance_to_wlr(msg_type), + "[EGL] command: %s, error: %s (0x%x), message: \"%s\"", + command, egl_error_str(error), error, msg); +} + +static bool check_egl_ext(const char *exts, const char *ext) { + size_t extlen = strlen(ext); + const char *end = exts + strlen(exts); + + while (exts < end) { + if (*exts == ' ') { + exts++; + continue; + } + size_t n = strcspn(exts, " "); + if (n == extlen && strncmp(ext, exts, n) == 0) { + return true; + } + exts += n; + } + return false; +} + +static void load_egl_proc(void *proc_ptr, const char *name) { + void *proc = (void *)eglGetProcAddress(name); + if (proc == NULL) { + wlr_log(WLR_ERROR, "eglGetProcAddress(%s) failed", name); + abort(); + } + *(void **)proc_ptr = proc; +} + +static int get_egl_dmabuf_formats(struct wlr_egl *egl, EGLint **formats); +static int get_egl_dmabuf_modifiers(struct wlr_egl *egl, EGLint format, + uint64_t **modifiers, EGLBoolean **external_only); + +static void log_modifier(uint64_t modifier, bool external_only) { + char *mod_name = drmGetFormatModifierName(modifier); + wlr_log(WLR_DEBUG, " %s (0x%016"PRIX64"): ✓ texture %s render", + mod_name ? mod_name : "", modifier, external_only ? "✗" : "✓"); + free(mod_name); +} + +static void init_dmabuf_formats(struct wlr_egl *egl) { + bool no_modifiers = env_parse_bool("WLR_EGL_NO_MODIFIERS"); + if (no_modifiers) { + wlr_log(WLR_INFO, "WLR_EGL_NO_MODIFIERS set, disabling modifiers for EGL"); + } + + EGLint *formats; + int formats_len = get_egl_dmabuf_formats(egl, &formats); + if (formats_len < 0) { + return; + } + + wlr_log(WLR_DEBUG, "Supported DMA-BUF formats:"); + + bool has_modifiers = false; + for (int i = 0; i < formats_len; i++) { + EGLint fmt = formats[i]; + + uint64_t *modifiers = NULL; + EGLBoolean *external_only = NULL; + int modifiers_len = 0; + if (!no_modifiers) { + modifiers_len = get_egl_dmabuf_modifiers(egl, fmt, &modifiers, &external_only); + } + if (modifiers_len < 0) { + continue; + } + + has_modifiers = has_modifiers || modifiers_len > 0; + + bool all_external_only = true; + for (int j = 0; j < modifiers_len; j++) { + wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt, + modifiers[j]); + if (!external_only[j]) { + wlr_drm_format_set_add(&egl->dmabuf_render_formats, fmt, + modifiers[j]); + all_external_only = false; + } + } + + // EGL always supports implicit modifiers. If at least one modifier supports rendering, + // assume the implicit modifier supports rendering too. + wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt, + DRM_FORMAT_MOD_INVALID); + if (modifiers_len == 0 || !all_external_only) { + wlr_drm_format_set_add(&egl->dmabuf_render_formats, fmt, + DRM_FORMAT_MOD_INVALID); + } + + if (modifiers_len == 0) { + // Asume the linear layout is supported if the driver doesn't + // explicitly say otherwise + wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt, + DRM_FORMAT_MOD_LINEAR); + wlr_drm_format_set_add(&egl->dmabuf_render_formats, fmt, + DRM_FORMAT_MOD_LINEAR); + } + + if (wlr_log_get_verbosity() >= WLR_DEBUG) { + char *fmt_name = drmGetFormatName(fmt); + wlr_log(WLR_DEBUG, " %s (0x%08"PRIX32")", + fmt_name ? fmt_name : "", fmt); + free(fmt_name); + + log_modifier(DRM_FORMAT_MOD_INVALID, false); + if (modifiers_len == 0) { + log_modifier(DRM_FORMAT_MOD_LINEAR, false); + } + for (int j = 0; j < modifiers_len; j++) { + log_modifier(modifiers[j], external_only[j]); + } + } + + free(modifiers); + free(external_only); + } + free(formats); + + egl->has_modifiers = has_modifiers; + if (!no_modifiers) { + wlr_log(WLR_DEBUG, "EGL DMA-BUF format modifiers %s", + has_modifiers ? "supported" : "unsupported"); + } +} + +static struct wlr_egl *egl_create(void) { + const char *client_exts_str = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (client_exts_str == NULL) { + if (eglGetError() == EGL_BAD_DISPLAY) { + wlr_log(WLR_ERROR, "EGL_EXT_client_extensions not supported"); + } else { + wlr_log(WLR_ERROR, "Failed to query EGL client extensions"); + } + return NULL; + } + + wlr_log(WLR_INFO, "Supported EGL client extensions: %s", client_exts_str); + + if (!check_egl_ext(client_exts_str, "EGL_EXT_platform_base")) { + wlr_log(WLR_ERROR, "EGL_EXT_platform_base not supported"); + return NULL; + } + + struct wlr_egl *egl = calloc(1, sizeof(*egl)); + if (egl == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + load_egl_proc(&egl->procs.eglGetPlatformDisplayEXT, + "eglGetPlatformDisplayEXT"); + + egl->exts.KHR_platform_gbm = check_egl_ext(client_exts_str, + "EGL_KHR_platform_gbm"); + egl->exts.EXT_platform_device = check_egl_ext(client_exts_str, + "EGL_EXT_platform_device"); + egl->exts.KHR_display_reference = check_egl_ext(client_exts_str, + "EGL_KHR_display_reference"); + + if (check_egl_ext(client_exts_str, "EGL_EXT_device_base") || check_egl_ext(client_exts_str, "EGL_EXT_device_enumeration")) { + load_egl_proc(&egl->procs.eglQueryDevicesEXT, "eglQueryDevicesEXT"); + } + + if (check_egl_ext(client_exts_str, "EGL_EXT_device_base") || check_egl_ext(client_exts_str, "EGL_EXT_device_query")) { + egl->exts.EXT_device_query = true; + load_egl_proc(&egl->procs.eglQueryDeviceStringEXT, + "eglQueryDeviceStringEXT"); + load_egl_proc(&egl->procs.eglQueryDisplayAttribEXT, + "eglQueryDisplayAttribEXT"); + } + + if (check_egl_ext(client_exts_str, "EGL_KHR_debug")) { + load_egl_proc(&egl->procs.eglDebugMessageControlKHR, + "eglDebugMessageControlKHR"); + + static const EGLAttrib debug_attribs[] = { + EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, + EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, + EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, + EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, + EGL_NONE, + }; + egl->procs.eglDebugMessageControlKHR(egl_log, debug_attribs); + } + + if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { + wlr_log(WLR_ERROR, "Failed to bind to the OpenGL ES API"); + free(egl); + return NULL; + } + + return egl; +} + +static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { + egl->display = display; + + EGLint major, minor; + if (eglInitialize(egl->display, &major, &minor) == EGL_FALSE) { + wlr_log(WLR_ERROR, "Failed to initialize EGL"); + return false; + } + + const char *display_exts_str = eglQueryString(egl->display, EGL_EXTENSIONS); + if (display_exts_str == NULL) { + wlr_log(WLR_ERROR, "Failed to query EGL display extensions"); + return false; + } + + if (check_egl_ext(display_exts_str, "EGL_KHR_image_base")) { + egl->exts.KHR_image_base = true; + load_egl_proc(&egl->procs.eglCreateImageKHR, "eglCreateImageKHR"); + load_egl_proc(&egl->procs.eglDestroyImageKHR, "eglDestroyImageKHR"); + } + + egl->exts.EXT_image_dma_buf_import = + check_egl_ext(display_exts_str, "EGL_EXT_image_dma_buf_import"); + if (check_egl_ext(display_exts_str, + "EGL_EXT_image_dma_buf_import_modifiers")) { + egl->exts.EXT_image_dma_buf_import_modifiers = true; + load_egl_proc(&egl->procs.eglQueryDmaBufFormatsEXT, + "eglQueryDmaBufFormatsEXT"); + load_egl_proc(&egl->procs.eglQueryDmaBufModifiersEXT, + "eglQueryDmaBufModifiersEXT"); + } + + egl->exts.EXT_create_context_robustness = + check_egl_ext(display_exts_str, "EGL_EXT_create_context_robustness"); + + const char *device_exts_str = NULL, *driver_name = NULL; + if (egl->exts.EXT_device_query) { + EGLAttrib device_attrib; + if (!egl->procs.eglQueryDisplayAttribEXT(egl->display, + EGL_DEVICE_EXT, &device_attrib)) { + wlr_log(WLR_ERROR, "eglQueryDisplayAttribEXT(EGL_DEVICE_EXT) failed"); + return false; + } + egl->device = (EGLDeviceEXT)device_attrib; + + device_exts_str = + egl->procs.eglQueryDeviceStringEXT(egl->device, EGL_EXTENSIONS); + if (device_exts_str == NULL) { + wlr_log(WLR_ERROR, "eglQueryDeviceStringEXT(EGL_EXTENSIONS) failed"); + return false; + } + + if (check_egl_ext(device_exts_str, "EGL_MESA_device_software")) { + if (env_parse_bool("WLR_RENDERER_ALLOW_SOFTWARE")) { + wlr_log(WLR_INFO, "Using software rendering"); + } else { + wlr_log(WLR_ERROR, "Software rendering detected, please use " + "the WLR_RENDERER_ALLOW_SOFTWARE environment variable " + "to proceed"); + return false; + } + } + +#ifdef EGL_DRIVER_NAME_EXT + if (check_egl_ext(device_exts_str, "EGL_EXT_device_persistent_id")) { + driver_name = egl->procs.eglQueryDeviceStringEXT(egl->device, + EGL_DRIVER_NAME_EXT); + } +#endif + + egl->exts.EXT_device_drm = + check_egl_ext(device_exts_str, "EGL_EXT_device_drm"); + egl->exts.EXT_device_drm_render_node = + check_egl_ext(device_exts_str, "EGL_EXT_device_drm_render_node"); + } + + if (!check_egl_ext(display_exts_str, "EGL_KHR_no_config_context") && + !check_egl_ext(display_exts_str, "EGL_MESA_configless_context")) { + wlr_log(WLR_ERROR, "EGL_KHR_no_config_context or " + "EGL_MESA_configless_context not supported"); + return false; + } + + if (!check_egl_ext(display_exts_str, "EGL_KHR_surfaceless_context")) { + wlr_log(WLR_ERROR, "EGL_KHR_surfaceless_context not supported"); + return false; + } + + egl->exts.IMG_context_priority = + check_egl_ext(display_exts_str, "EGL_IMG_context_priority"); + + wlr_log(WLR_INFO, "Using EGL %d.%d", (int)major, (int)minor); + wlr_log(WLR_INFO, "Supported EGL display extensions: %s", display_exts_str); + if (device_exts_str != NULL) { + wlr_log(WLR_INFO, "Supported EGL device extensions: %s", device_exts_str); + } + wlr_log(WLR_INFO, "EGL vendor: %s", eglQueryString(egl->display, EGL_VENDOR)); + if (driver_name != NULL) { + wlr_log(WLR_INFO, "EGL driver name: %s", driver_name); + } + + init_dmabuf_formats(egl); + + return true; +} + +static bool egl_init(struct wlr_egl *egl, EGLenum platform, + void *remote_display) { + EGLint display_attribs[3] = {0}; + size_t display_attribs_len = 0; + + if (egl->exts.KHR_display_reference) { + display_attribs[display_attribs_len++] = EGL_TRACK_REFERENCES_KHR; + display_attribs[display_attribs_len++] = EGL_TRUE; + } + + display_attribs[display_attribs_len++] = EGL_NONE; + assert(display_attribs_len < sizeof(display_attribs) / sizeof(display_attribs[0])); + + EGLDisplay display = egl->procs.eglGetPlatformDisplayEXT(platform, + remote_display, display_attribs); + if (display == EGL_NO_DISPLAY) { + wlr_log(WLR_ERROR, "Failed to create EGL display"); + return false; + } + + if (!egl_init_display(egl, display)) { + if (egl->exts.KHR_display_reference) { + eglTerminate(display); + } + return false; + } + + size_t atti = 0; + EGLint attribs[7]; + attribs[atti++] = EGL_CONTEXT_CLIENT_VERSION; + attribs[atti++] = 2; + + // Request a high priority context if possible + // TODO: only do this if we're running as the DRM master + bool request_high_priority = egl->exts.IMG_context_priority; + + // Try to reschedule all of our rendering to be completed first. If it + // fails, it will fallback to the default priority (MEDIUM). + if (request_high_priority) { + attribs[atti++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG; + attribs[atti++] = EGL_CONTEXT_PRIORITY_HIGH_IMG; + } + + if (egl->exts.EXT_create_context_robustness) { + attribs[atti++] = EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT; + attribs[atti++] = EGL_LOSE_CONTEXT_ON_RESET_EXT; + } + + attribs[atti++] = EGL_NONE; + assert(atti <= sizeof(attribs)/sizeof(attribs[0])); + + egl->context = eglCreateContext(egl->display, EGL_NO_CONFIG_KHR, + EGL_NO_CONTEXT, attribs); + if (egl->context == EGL_NO_CONTEXT) { + wlr_log(WLR_ERROR, "Failed to create EGL context"); + return false; + } + + if (request_high_priority) { + EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; + eglQueryContext(egl->display, egl->context, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority); + if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG) { + wlr_log(WLR_INFO, "Failed to obtain a high priority context"); + } else { + wlr_log(WLR_DEBUG, "Obtained high priority context"); + } + } + + return true; +} + +static bool device_has_name(const drmDevice *device, const char *name); + +static EGLDeviceEXT get_egl_device_from_drm_fd(struct wlr_egl *egl, + int drm_fd) { + if (egl->procs.eglQueryDevicesEXT == NULL) { + wlr_log(WLR_DEBUG, "EGL_EXT_device_enumeration not supported"); + return EGL_NO_DEVICE_EXT; + } else if (!egl->exts.EXT_device_query) { + wlr_log(WLR_DEBUG, "EGL_EXT_device_query not supported"); + return EGL_NO_DEVICE_EXT; + } + + EGLint nb_devices = 0; + if (!egl->procs.eglQueryDevicesEXT(0, NULL, &nb_devices)) { + wlr_log(WLR_ERROR, "Failed to query EGL devices"); + return EGL_NO_DEVICE_EXT; + } + + EGLDeviceEXT *devices = calloc(nb_devices, sizeof(*devices)); + if (devices == NULL) { + wlr_log_errno(WLR_ERROR, "Failed to allocate EGL device list"); + return EGL_NO_DEVICE_EXT; + } + + if (!egl->procs.eglQueryDevicesEXT(nb_devices, devices, &nb_devices)) { + wlr_log(WLR_ERROR, "Failed to query EGL devices"); + return EGL_NO_DEVICE_EXT; + } + + drmDevice *device = NULL; + int ret = drmGetDevice(drm_fd, &device); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get DRM device: %s", strerror(-ret)); + return EGL_NO_DEVICE_EXT; + } + + EGLDeviceEXT egl_device = NULL; + for (int i = 0; i < nb_devices; i++) { + const char *egl_device_name = egl->procs.eglQueryDeviceStringEXT( + devices[i], EGL_DRM_DEVICE_FILE_EXT); + if (egl_device_name == NULL) { + continue; + } + + if (device_has_name(device, egl_device_name)) { + wlr_log(WLR_DEBUG, "Using EGL device %s", egl_device_name); + egl_device = devices[i]; + break; + } + } + + drmFreeDevice(&device); + free(devices); + + return egl_device; +} + +static int open_render_node(int drm_fd) { + char *render_name = drmGetRenderDeviceNameFromFd(drm_fd); + if (render_name == NULL) { + // This can happen on split render/display platforms, fallback to + // primary node + render_name = drmGetPrimaryDeviceNameFromFd(drm_fd); + if (render_name == NULL) { + wlr_log_errno(WLR_ERROR, "drmGetPrimaryDeviceNameFromFd failed"); + return -1; + } + wlr_log(WLR_DEBUG, "DRM device '%s' has no render node, " + "falling back to primary node", render_name); + } + + int render_fd = open(render_name, O_RDWR | O_CLOEXEC); + if (render_fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open DRM node '%s'", render_name); + } + free(render_name); + return render_fd; +} + +struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd) { + struct wlr_egl *egl = egl_create(); + if (egl == NULL) { + wlr_log(WLR_ERROR, "Failed to create EGL context"); + return NULL; + } + + if (egl->exts.EXT_platform_device) { + /* + * Search for the EGL device matching the DRM fd using the + * EXT_device_enumeration extension. + */ + EGLDeviceEXT egl_device = get_egl_device_from_drm_fd(egl, drm_fd); + if (egl_device != EGL_NO_DEVICE_EXT) { + if (egl_init(egl, EGL_PLATFORM_DEVICE_EXT, egl_device)) { + wlr_log(WLR_DEBUG, "Using EGL_PLATFORM_DEVICE_EXT"); + return egl; + } + goto error; + } + /* Falls back on GBM in case the device was not found */ + } else { + wlr_log(WLR_DEBUG, "EXT_platform_device not supported"); + } + + if (egl->exts.KHR_platform_gbm) { + int gbm_fd = open_render_node(drm_fd); + if (gbm_fd < 0) { + wlr_log(WLR_ERROR, "Failed to open DRM render node"); + goto error; + } + + egl->gbm_device = gbm_create_device(gbm_fd); + if (!egl->gbm_device) { + close(gbm_fd); + wlr_log(WLR_ERROR, "Failed to create GBM device"); + goto error; + } + + if (egl_init(egl, EGL_PLATFORM_GBM_KHR, egl->gbm_device)) { + wlr_log(WLR_DEBUG, "Using EGL_PLATFORM_GBM_KHR"); + return egl; + } + + gbm_device_destroy(egl->gbm_device); + close(gbm_fd); + } else { + wlr_log(WLR_DEBUG, "KHR_platform_gbm not supported"); + } + +error: + wlr_log(WLR_ERROR, "Failed to initialize EGL context"); + free(egl); + eglReleaseThread(); + return NULL; +} + +struct wlr_egl *wlr_egl_create_with_context(EGLDisplay display, + EGLContext context) { + EGLint client_type; + if (!eglQueryContext(display, context, EGL_CONTEXT_CLIENT_TYPE, &client_type) || + client_type != EGL_OPENGL_ES_API) { + wlr_log(WLR_ERROR, "Unsupported EGL context client type (need OpenGL ES)"); + return NULL; + } + + EGLint client_version; + if (!eglQueryContext(display, context, EGL_CONTEXT_CLIENT_VERSION, &client_version) || + client_version < 2) { + wlr_log(WLR_ERROR, "Unsupported EGL context client version (need OpenGL ES >= 2)"); + return NULL; + } + + struct wlr_egl *egl = egl_create(); + if (egl == NULL) { + return NULL; + } + + if (!egl_init_display(egl, display)) { + free(egl); + return NULL; + } + + egl->context = context; + + return egl; +} + +void wlr_egl_destroy(struct wlr_egl *egl) { + if (egl == NULL) { + return; + } + + wlr_drm_format_set_finish(&egl->dmabuf_render_formats); + wlr_drm_format_set_finish(&egl->dmabuf_texture_formats); + + eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(egl->display, egl->context); + + if (egl->exts.KHR_display_reference) { + eglTerminate(egl->display); + } + + eglReleaseThread(); + + if (egl->gbm_device) { + int gbm_fd = gbm_device_get_fd(egl->gbm_device); + gbm_device_destroy(egl->gbm_device); + close(gbm_fd); + } + + free(egl); +} + +EGLDisplay wlr_egl_get_display(struct wlr_egl *egl) { + return egl->display; +} + +EGLContext wlr_egl_get_context(struct wlr_egl *egl) { + return egl->context; +} + +bool wlr_egl_destroy_image(struct wlr_egl *egl, EGLImage image) { + if (!egl->exts.KHR_image_base) { + return false; + } + if (!image) { + return true; + } + return egl->procs.eglDestroyImageKHR(egl->display, image); +} + +bool wlr_egl_make_current(struct wlr_egl *egl, + struct wlr_egl_context *save_context) { + if (save_context != NULL) { + save_context->display = eglGetCurrentDisplay(); + save_context->context = eglGetCurrentContext(); + save_context->draw_surface = eglGetCurrentSurface(EGL_DRAW); + save_context->read_surface = eglGetCurrentSurface(EGL_READ); + } + if (!eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, + egl->context)) { + wlr_log(WLR_ERROR, "eglMakeCurrent failed"); + return false; + } + return true; +} + +bool wlr_egl_unset_current(struct wlr_egl *egl) { + if (!eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)) { + wlr_log(WLR_ERROR, "eglMakeCurrent failed"); + return false; + } + return true; +} + +bool wlr_egl_restore_context(struct wlr_egl_context *context) { + // If the saved context is a null-context, we must use the current + // display instead of the saved display because eglMakeCurrent() can't + // handle EGL_NO_DISPLAY. + EGLDisplay display = context->display == EGL_NO_DISPLAY ? + eglGetCurrentDisplay() : context->display; + + // If the current display is also EGL_NO_DISPLAY, we assume that there + // is currently no context set and no action needs to be taken to unset + // the context. + if (display == EGL_NO_DISPLAY) { + return true; + } + + return eglMakeCurrent(display, context->draw_surface, + context->read_surface, context->context); +} + +EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl, + struct wlr_dmabuf_attributes *attributes, bool *external_only) { + if (!egl->exts.KHR_image_base || !egl->exts.EXT_image_dma_buf_import) { + wlr_log(WLR_ERROR, "dmabuf import extension not present"); + return NULL; + } + + if (attributes->modifier != DRM_FORMAT_MOD_INVALID && + attributes->modifier != DRM_FORMAT_MOD_LINEAR && + !egl->has_modifiers) { + wlr_log(WLR_ERROR, "EGL implementation doesn't support modifiers"); + return NULL; + } + + unsigned int atti = 0; + EGLint attribs[50]; + attribs[atti++] = EGL_WIDTH; + attribs[atti++] = attributes->width; + attribs[atti++] = EGL_HEIGHT; + attribs[atti++] = attributes->height; + attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; + attribs[atti++] = attributes->format; + + struct { + EGLint fd; + EGLint offset; + EGLint pitch; + EGLint mod_lo; + EGLint mod_hi; + } attr_names[WLR_DMABUF_MAX_PLANES] = { + { + EGL_DMA_BUF_PLANE0_FD_EXT, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, + EGL_DMA_BUF_PLANE0_PITCH_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT + }, { + EGL_DMA_BUF_PLANE1_FD_EXT, + EGL_DMA_BUF_PLANE1_OFFSET_EXT, + EGL_DMA_BUF_PLANE1_PITCH_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT + }, { + EGL_DMA_BUF_PLANE2_FD_EXT, + EGL_DMA_BUF_PLANE2_OFFSET_EXT, + EGL_DMA_BUF_PLANE2_PITCH_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT + }, { + EGL_DMA_BUF_PLANE3_FD_EXT, + EGL_DMA_BUF_PLANE3_OFFSET_EXT, + EGL_DMA_BUF_PLANE3_PITCH_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT + } + }; + + for (int i = 0; i < attributes->n_planes; i++) { + attribs[atti++] = attr_names[i].fd; + attribs[atti++] = attributes->fd[i]; + attribs[atti++] = attr_names[i].offset; + attribs[atti++] = attributes->offset[i]; + attribs[atti++] = attr_names[i].pitch; + attribs[atti++] = attributes->stride[i]; + if (egl->has_modifiers && + attributes->modifier != DRM_FORMAT_MOD_INVALID) { + attribs[atti++] = attr_names[i].mod_lo; + attribs[atti++] = attributes->modifier & 0xFFFFFFFF; + attribs[atti++] = attr_names[i].mod_hi; + attribs[atti++] = attributes->modifier >> 32; + } + } + + // Our clients don't expect our usage to trash the buffer contents + attribs[atti++] = EGL_IMAGE_PRESERVED_KHR; + attribs[atti++] = EGL_TRUE; + + attribs[atti++] = EGL_NONE; + assert(atti < sizeof(attribs)/sizeof(attribs[0])); + + EGLImageKHR image = egl->procs.eglCreateImageKHR(egl->display, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, NULL, attribs); + if (image == EGL_NO_IMAGE_KHR) { + wlr_log(WLR_ERROR, "eglCreateImageKHR failed"); + return EGL_NO_IMAGE_KHR; + } + + *external_only = !wlr_drm_format_set_has(&egl->dmabuf_render_formats, + attributes->format, attributes->modifier); + return image; +} + +static int get_egl_dmabuf_formats(struct wlr_egl *egl, EGLint **formats) { + if (!egl->exts.EXT_image_dma_buf_import) { + wlr_log(WLR_DEBUG, "DMA-BUF import extension not present"); + return -1; + } + + // when we only have the image_dmabuf_import extension we can't query + // which formats are supported. These two are on almost always + // supported; it's the intended way to just try to create buffers. + // Just a guess but better than not supporting dmabufs at all, + // given that the modifiers extension isn't supported everywhere. + if (!egl->exts.EXT_image_dma_buf_import_modifiers) { + static const EGLint fallback_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + }; + int num = sizeof(fallback_formats) / sizeof(fallback_formats[0]); + + *formats = calloc(num, sizeof(**formats)); + if (!*formats) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return -1; + } + + memcpy(*formats, fallback_formats, num * sizeof(**formats)); + return num; + } + + EGLint num; + if (!egl->procs.eglQueryDmaBufFormatsEXT(egl->display, 0, NULL, &num)) { + wlr_log(WLR_ERROR, "Failed to query number of dmabuf formats"); + return -1; + } + + *formats = calloc(num, sizeof(**formats)); + if (*formats == NULL) { + wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); + return -1; + } + + if (!egl->procs.eglQueryDmaBufFormatsEXT(egl->display, num, *formats, &num)) { + wlr_log(WLR_ERROR, "Failed to query dmabuf format"); + free(*formats); + return -1; + } + return num; +} + +static int get_egl_dmabuf_modifiers(struct wlr_egl *egl, EGLint format, + uint64_t **modifiers, EGLBoolean **external_only) { + *modifiers = NULL; + *external_only = NULL; + + if (!egl->exts.EXT_image_dma_buf_import) { + wlr_log(WLR_DEBUG, "DMA-BUF extension not present"); + return -1; + } + if (!egl->exts.EXT_image_dma_buf_import_modifiers) { + return 0; + } + + EGLint num; + if (!egl->procs.eglQueryDmaBufModifiersEXT(egl->display, format, 0, + NULL, NULL, &num)) { + wlr_log(WLR_ERROR, "Failed to query dmabuf number of modifiers"); + return -1; + } + if (num == 0) { + return 0; + } + + *modifiers = calloc(num, sizeof(**modifiers)); + if (*modifiers == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return -1; + } + *external_only = calloc(num, sizeof(**external_only)); + if (*external_only == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + free(*modifiers); + *modifiers = NULL; + return -1; + } + + if (!egl->procs.eglQueryDmaBufModifiersEXT(egl->display, format, num, + *modifiers, *external_only, &num)) { + wlr_log(WLR_ERROR, "Failed to query dmabuf modifiers"); + free(*modifiers); + free(*external_only); + return -1; + } + return num; +} + +const struct wlr_drm_format_set *wlr_egl_get_dmabuf_texture_formats( + struct wlr_egl *egl) { + return &egl->dmabuf_texture_formats; +} + +const struct wlr_drm_format_set *wlr_egl_get_dmabuf_render_formats( + struct wlr_egl *egl) { + return &egl->dmabuf_render_formats; +} + +static bool device_has_name(const drmDevice *device, const char *name) { + for (size_t i = 0; i < DRM_NODE_MAX; i++) { + if (!(device->available_nodes & (1 << i))) { + continue; + } + if (strcmp(device->nodes[i], name) == 0) { + return true; + } + } + return false; +} + +static char *get_render_name(const char *name) { + uint32_t flags = 0; + int devices_len = drmGetDevices2(flags, NULL, 0); + if (devices_len < 0) { + wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len)); + return NULL; + } + drmDevice **devices = calloc(devices_len, sizeof(*devices)); + if (devices == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + devices_len = drmGetDevices2(flags, devices, devices_len); + if (devices_len < 0) { + free(devices); + wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len)); + return NULL; + } + + const drmDevice *match = NULL; + for (int i = 0; i < devices_len; i++) { + if (device_has_name(devices[i], name)) { + match = devices[i]; + break; + } + } + + char *render_name = NULL; + if (match == NULL) { + wlr_log(WLR_ERROR, "Cannot find DRM device %s", name); + } else if (!(match->available_nodes & (1 << DRM_NODE_RENDER))) { + // Likely a split display/render setup. Pick the primary node and hope + // Mesa will open the right render node under-the-hood. + wlr_log(WLR_DEBUG, "DRM device %s has no render node, " + "falling back to primary node", name); + assert(match->available_nodes & (1 << DRM_NODE_PRIMARY)); + render_name = strdup(match->nodes[DRM_NODE_PRIMARY]); + } else { + render_name = strdup(match->nodes[DRM_NODE_RENDER]); + } + + for (int i = 0; i < devices_len; i++) { + drmFreeDevice(&devices[i]); + } + free(devices); + + return render_name; +} + +static int dup_egl_device_drm_fd(struct wlr_egl *egl) { + if (egl->device == EGL_NO_DEVICE_EXT || (!egl->exts.EXT_device_drm && + !egl->exts.EXT_device_drm_render_node)) { + return -1; + } + + char *render_name = NULL; +#ifdef EGL_EXT_device_drm_render_node + if (egl->exts.EXT_device_drm_render_node) { + const char *name = egl->procs.eglQueryDeviceStringEXT(egl->device, + EGL_DRM_RENDER_NODE_FILE_EXT); + if (name == NULL) { + wlr_log(WLR_DEBUG, "EGL device has no render node"); + return -1; + } + render_name = strdup(name); + } +#endif + + if (render_name == NULL) { + const char *primary_name = egl->procs.eglQueryDeviceStringEXT(egl->device, + EGL_DRM_DEVICE_FILE_EXT); + if (primary_name == NULL) { + wlr_log(WLR_ERROR, + "eglQueryDeviceStringEXT(EGL_DRM_DEVICE_FILE_EXT) failed"); + return -1; + } + + render_name = get_render_name(primary_name); + if (render_name == NULL) { + wlr_log(WLR_ERROR, "Can't find render node name for device %s", + primary_name); + return -1; + } + } + + int render_fd = open(render_name, O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (render_fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open DRM render node %s", + render_name); + free(render_name); + return -1; + } + free(render_name); + + return render_fd; +} + +int wlr_egl_dup_drm_fd(struct wlr_egl *egl) { + int fd = dup_egl_device_drm_fd(egl); + if (fd >= 0) { + return fd; + } + + // Fallback to GBM's FD if we can't use EGLDevice + if (egl->gbm_device == NULL) { + return -1; + } + + fd = fcntl(gbm_device_get_fd(egl->gbm_device), F_DUPFD_CLOEXEC, 0); + if (fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to dup GBM FD"); + } + return fd; +} diff --git a/render/gles2/meson.build b/render/gles2/meson.build new file mode 100644 index 0000000..2a6db96 --- /dev/null +++ b/render/gles2/meson.build @@ -0,0 +1,17 @@ +glesv2 = dependency('glesv2', required: 'gles2' in renderers) + +if not (glesv2.found() and internal_features['egl']) + subdir_done() +endif + +features += { 'gles2-renderer': true } +wlr_deps += glesv2 + +wlr_files += files( + 'pass.c', + 'pixel_format.c', + 'renderer.c', + 'texture.c', +) + +subdir('shaders') diff --git a/render/gles2/pass.c b/render/gles2/pass.c new file mode 100644 index 0000000..9177b0a --- /dev/null +++ b/render/gles2/pass.c @@ -0,0 +1,291 @@ +#include +#include +#include +#include +#include +#include +#include "render/gles2.h" +#include "types/wlr_matrix.h" + +#define MAX_QUADS 86 // 4kb + +static const struct wlr_render_pass_impl render_pass_impl; + +static struct wlr_gles2_render_pass *get_render_pass(struct wlr_render_pass *wlr_pass) { + assert(wlr_pass->impl == &render_pass_impl); + struct wlr_gles2_render_pass *pass = wl_container_of(wlr_pass, pass, base); + return pass; +} + +static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { + struct wlr_gles2_render_pass *pass = get_render_pass(wlr_pass); + struct wlr_gles2_renderer *renderer = pass->buffer->renderer; + struct wlr_gles2_render_timer *timer = pass->timer; + + push_gles2_debug(renderer); + + if (timer) { + // clear disjoint flag + GLint64 disjoint; + renderer->procs.glGetInteger64vEXT(GL_GPU_DISJOINT_EXT, &disjoint); + // set up the query + renderer->procs.glQueryCounterEXT(timer->id, GL_TIMESTAMP_EXT); + // get end-of-CPU-work time in GL time domain + renderer->procs.glGetInteger64vEXT(GL_TIMESTAMP_EXT, &timer->gl_cpu_end); + // get end-of-CPU-work time in CPU time domain + clock_gettime(CLOCK_MONOTONIC, &timer->cpu_end); + } + + glFlush(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + pop_gles2_debug(renderer); + wlr_egl_restore_context(&pass->prev_ctx); + + wlr_buffer_unlock(pass->buffer->buffer); + free(pass); + + return true; +} + +static void render(const struct wlr_box *box, const pixman_region32_t *clip, GLint attrib) { + pixman_region32_t region; + pixman_region32_init_rect(®ion, box->x, box->y, box->width, box->height); + + if (clip) { + pixman_region32_intersect(®ion, ®ion, clip); + } + + int rects_len; + const pixman_box32_t *rects = pixman_region32_rectangles(®ion, &rects_len); + if (rects_len == 0) { + pixman_region32_fini(®ion); + return; + } + + glEnableVertexAttribArray(attrib); + + for (int i = 0; i < rects_len;) { + int batch = rects_len - i < MAX_QUADS ? rects_len - i : MAX_QUADS; + int batch_end = batch + i; + + size_t vert_index = 0; + GLfloat verts[MAX_QUADS * 6 * 2]; + for (; i < batch_end; i++) { + const pixman_box32_t *rect = &rects[i]; + + verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; + verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; + verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; + verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; + verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; + verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; + verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; + verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; + verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; + verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; + verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; + verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; + } + + glVertexAttribPointer(attrib, 2, GL_FLOAT, GL_FALSE, 0, verts); + glDrawArrays(GL_TRIANGLES, 0, batch * 6); + } + + glDisableVertexAttribArray(attrib); + + pixman_region32_fini(®ion); +} + +static void set_proj_matrix(GLint loc, float proj[9], const struct wlr_box *box) { + float gl_matrix[9]; + wlr_matrix_identity(gl_matrix); + wlr_matrix_translate(gl_matrix, box->x, box->y); + wlr_matrix_scale(gl_matrix, box->width, box->height); + wlr_matrix_multiply(gl_matrix, proj, gl_matrix); + glUniformMatrix3fv(loc, 1, GL_FALSE, gl_matrix); +} + +static void set_tex_matrix(GLint loc, enum wl_output_transform trans, + const struct wlr_fbox *box) { + float tex_matrix[9]; + wlr_matrix_identity(tex_matrix); + wlr_matrix_translate(tex_matrix, box->x, box->y); + wlr_matrix_scale(tex_matrix, box->width, box->height); + wlr_matrix_translate(tex_matrix, .5, .5); + + // since textures have a different origin point we have to transform + // differently if we are rotating + if (trans & WL_OUTPUT_TRANSFORM_90) { + wlr_matrix_transform(tex_matrix, wlr_output_transform_invert(trans)); + } else { + wlr_matrix_transform(tex_matrix, trans); + } + wlr_matrix_translate(tex_matrix, -.5, -.5); + + glUniformMatrix3fv(loc, 1, GL_FALSE, tex_matrix); +} + +static void setup_blending(enum wlr_render_blend_mode mode) { + switch (mode) { + case WLR_RENDER_BLEND_MODE_PREMULTIPLIED: + glEnable(GL_BLEND); + break; + case WLR_RENDER_BLEND_MODE_NONE: + glDisable(GL_BLEND); + break; + } +} + +static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, + const struct wlr_render_texture_options *options) { + struct wlr_gles2_render_pass *pass = get_render_pass(wlr_pass); + struct wlr_gles2_renderer *renderer = pass->buffer->renderer; + struct wlr_gles2_texture *texture = gles2_get_texture(options->texture); + + struct wlr_gles2_tex_shader *shader = NULL; + + switch (texture->target) { + case GL_TEXTURE_2D: + if (texture->has_alpha) { + shader = &renderer->shaders.tex_rgba; + } else { + shader = &renderer->shaders.tex_rgbx; + } + break; + case GL_TEXTURE_EXTERNAL_OES: + // EGL_EXT_image_dma_buf_import_modifiers requires + // GL_OES_EGL_image_external + assert(renderer->exts.OES_egl_image_external); + shader = &renderer->shaders.tex_ext; + break; + default: + abort(); + } + + struct wlr_box dst_box; + struct wlr_fbox src_fbox; + wlr_render_texture_options_get_src_box(options, &src_fbox); + wlr_render_texture_options_get_dst_box(options, &dst_box); + float alpha = wlr_render_texture_options_get_alpha(options); + + src_fbox.x /= options->texture->width; + src_fbox.y /= options->texture->height; + src_fbox.width /= options->texture->width; + src_fbox.height /= options->texture->height; + + push_gles2_debug(renderer); + setup_blending(!texture->has_alpha && alpha == 1.0 ? + WLR_RENDER_BLEND_MODE_NONE : options->blend_mode); + + glUseProgram(shader->program); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(texture->target, texture->tex); + + switch (options->filter_mode) { + case WLR_SCALE_FILTER_BILINEAR: + glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + break; + case WLR_SCALE_FILTER_NEAREST: + glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + break; + } + + glUniform1i(shader->tex, 0); + glUniform1f(shader->alpha, alpha); + set_proj_matrix(shader->proj, pass->projection_matrix, &dst_box); + set_tex_matrix(shader->tex_proj, options->transform, &src_fbox); + + render(&dst_box, options->clip, shader->pos_attrib); + + glBindTexture(texture->target, 0); + pop_gles2_debug(renderer); +} + +static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, + const struct wlr_render_rect_options *options) { + struct wlr_gles2_render_pass *pass = get_render_pass(wlr_pass); + struct wlr_gles2_renderer *renderer = pass->buffer->renderer; + + const struct wlr_render_color *color = &options->color; + struct wlr_box box; + wlr_render_rect_options_get_box(options, pass->buffer->buffer, &box); + + push_gles2_debug(renderer); + setup_blending(color->a == 1.0 ? WLR_RENDER_BLEND_MODE_NONE : options->blend_mode); + + glUseProgram(renderer->shaders.quad.program); + + set_proj_matrix(renderer->shaders.quad.proj, pass->projection_matrix, &box); + glUniform4f(renderer->shaders.quad.color, color->r, color->g, color->b, color->a); + + render(&box, options->clip, renderer->shaders.quad.pos_attrib); + + pop_gles2_debug(renderer); +} + +static const struct wlr_render_pass_impl render_pass_impl = { + .submit = render_pass_submit, + .add_texture = render_pass_add_texture, + .add_rect = render_pass_add_rect, +}; + +static const char *reset_status_str(GLenum status) { + switch (status) { + case GL_GUILTY_CONTEXT_RESET_KHR: + return "guilty"; + case GL_INNOCENT_CONTEXT_RESET_KHR: + return "innocent"; + case GL_UNKNOWN_CONTEXT_RESET_KHR: + return "unknown"; + default: + return ""; + } +} + +struct wlr_gles2_render_pass *begin_gles2_buffer_pass(struct wlr_gles2_buffer *buffer, + struct wlr_egl_context *prev_ctx, struct wlr_gles2_render_timer *timer) { + struct wlr_gles2_renderer *renderer = buffer->renderer; + struct wlr_buffer *wlr_buffer = buffer->buffer; + + if (renderer->procs.glGetGraphicsResetStatusKHR) { + GLenum status = renderer->procs.glGetGraphicsResetStatusKHR(); + if (status != GL_NO_ERROR) { + wlr_log(WLR_ERROR, "GPU reset (%s)", reset_status_str(status)); + wl_signal_emit_mutable(&renderer->wlr_renderer.events.lost, NULL); + return NULL; + } + } + + GLint fbo = gles2_buffer_get_fbo(buffer); + if (!fbo) { + return NULL; + } + + struct wlr_gles2_render_pass *pass = calloc(1, sizeof(*pass)); + if (pass == NULL) { + return NULL; + } + + wlr_render_pass_init(&pass->base, &render_pass_impl); + wlr_buffer_lock(wlr_buffer); + pass->buffer = buffer; + pass->timer = timer; + pass->prev_ctx = *prev_ctx; + + matrix_projection(pass->projection_matrix, wlr_buffer->width, wlr_buffer->height, + WL_OUTPUT_TRANSFORM_FLIPPED_180); + + push_gles2_debug(renderer); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + glViewport(0, 0, wlr_buffer->width, wlr_buffer->height); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_SCISSOR_TEST); + pop_gles2_debug(renderer); + + return pass; +} diff --git a/render/gles2/pixel_format.c b/render/gles2/pixel_format.c new file mode 100644 index 0000000..5cdf8c9 --- /dev/null +++ b/render/gles2/pixel_format.c @@ -0,0 +1,165 @@ +#include +#include +#include +#include "render/gles2.h" +#include "render/pixel_format.h" + +/* + * The DRM formats are little endian while the GL formats are big endian, + * so DRM_FORMAT_ARGB8888 is actually compatible with GL_BGRA_EXT. + */ +static const struct wlr_gles2_pixel_format formats[] = { + { + .drm_format = DRM_FORMAT_ARGB8888, + .gl_format = GL_BGRA_EXT, + .gl_type = GL_UNSIGNED_BYTE, + }, + { + .drm_format = DRM_FORMAT_XRGB8888, + .gl_format = GL_BGRA_EXT, + .gl_type = GL_UNSIGNED_BYTE, + }, + { + .drm_format = DRM_FORMAT_XBGR8888, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_BYTE, + }, + { + .drm_format = DRM_FORMAT_ABGR8888, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_BYTE, + }, + { + .drm_format = DRM_FORMAT_BGR888, + .gl_format = GL_RGB, + .gl_type = GL_UNSIGNED_BYTE, + }, +#if WLR_LITTLE_ENDIAN + { + .drm_format = DRM_FORMAT_RGBX4444, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT_4_4_4_4, + }, + { + .drm_format = DRM_FORMAT_RGBA4444, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT_4_4_4_4, + }, + { + .drm_format = DRM_FORMAT_RGBX5551, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT_5_5_5_1, + }, + { + .drm_format = DRM_FORMAT_RGBA5551, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT_5_5_5_1, + }, + { + .drm_format = DRM_FORMAT_RGB565, + .gl_format = GL_RGB, + .gl_type = GL_UNSIGNED_SHORT_5_6_5, + }, + { + .drm_format = DRM_FORMAT_XBGR2101010, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, + }, + { + .drm_format = DRM_FORMAT_ABGR2101010, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, + }, + { + .drm_format = DRM_FORMAT_XBGR16161616F, + .gl_format = GL_RGBA, + .gl_type = GL_HALF_FLOAT_OES, + }, + { + .drm_format = DRM_FORMAT_ABGR16161616F, + .gl_format = GL_RGBA, + .gl_type = GL_HALF_FLOAT_OES, + }, + { + .drm_format = DRM_FORMAT_XBGR16161616, + .gl_internalformat = GL_RGBA16_EXT, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT, + }, + { + .drm_format = DRM_FORMAT_ABGR16161616, + .gl_internalformat = GL_RGBA16_EXT, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT, + }, +#endif +}; + +// TODO: more pixel formats + +/* + * Return true if supported for texturing, even if other operations like + * reading aren't supported. + */ +bool is_gles2_pixel_format_supported(const struct wlr_gles2_renderer *renderer, + const struct wlr_gles2_pixel_format *format) { + if (format->gl_type == GL_UNSIGNED_INT_2_10_10_10_REV_EXT + && !renderer->exts.EXT_texture_type_2_10_10_10_REV) { + return false; + } + if (format->gl_type == GL_HALF_FLOAT_OES + && !renderer->exts.OES_texture_half_float_linear) { + return false; + } + if (format->gl_type == GL_UNSIGNED_SHORT + && !renderer->exts.EXT_texture_norm16) { + return false; + } + /* + * Note that we don't need to check for GL_EXT_texture_format_BGRA8888 + * here, since we've already checked if we have it at renderer creation + * time and bailed out if not. We do the check there because Wayland + * requires all compositors to support SHM buffers in that format. + */ + return true; +} + +const struct wlr_gles2_pixel_format *get_gles2_format_from_drm(uint32_t fmt) { + for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) { + if (formats[i].drm_format == fmt) { + return &formats[i]; + } + } + return NULL; +} + +const struct wlr_gles2_pixel_format *get_gles2_format_from_gl( + GLint gl_format, GLint gl_type, bool alpha) { + for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) { + if (formats[i].gl_format != gl_format || + formats[i].gl_type != gl_type) { + continue; + } + + if (pixel_format_has_alpha(formats[i].drm_format) != alpha) { + continue; + } + + return &formats[i]; + } + return NULL; +} + +const uint32_t *get_gles2_shm_formats(const struct wlr_gles2_renderer *renderer, + size_t *len) { + static uint32_t shm_formats[sizeof(formats) / sizeof(formats[0])]; + size_t j = 0; + for (size_t i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) { + if (!is_gles2_pixel_format_supported(renderer, &formats[i])) { + continue; + } + shm_formats[j++] = formats[i].drm_format; + } + *len = j; + return shm_formats; +} diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c new file mode 100644 index 0000000..3aed64e --- /dev/null +++ b/render/gles2/renderer.c @@ -0,0 +1,708 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "render/egl.h" +#include "render/gles2.h" +#include "render/pixel_format.h" +#include "types/wlr_matrix.h" +#include "util/time.h" + +#include "common_vert_src.h" +#include "quad_frag_src.h" +#include "tex_rgba_frag_src.h" +#include "tex_rgbx_frag_src.h" +#include "tex_external_frag_src.h" + +static const struct wlr_renderer_impl renderer_impl; +static const struct wlr_render_timer_impl render_timer_impl; + +bool wlr_renderer_is_gles2(struct wlr_renderer *wlr_renderer) { + return wlr_renderer->impl == &renderer_impl; +} + +struct wlr_gles2_renderer *gles2_get_renderer( + struct wlr_renderer *wlr_renderer) { + assert(wlr_renderer_is_gles2(wlr_renderer)); + struct wlr_gles2_renderer *renderer = wl_container_of(wlr_renderer, renderer, wlr_renderer); + return renderer; +} + +bool wlr_render_timer_is_gles2(struct wlr_render_timer *timer) { + return timer->impl == &render_timer_impl; +} + +struct wlr_gles2_render_timer *gles2_get_render_timer(struct wlr_render_timer *wlr_timer) { + assert(wlr_render_timer_is_gles2(wlr_timer)); + struct wlr_gles2_render_timer *timer = wl_container_of(wlr_timer, timer, base); + return timer; +} + +static void destroy_buffer(struct wlr_gles2_buffer *buffer) { + wl_list_remove(&buffer->link); + wlr_addon_finish(&buffer->addon); + + struct wlr_egl_context prev_ctx; + wlr_egl_make_current(buffer->renderer->egl, &prev_ctx); + + push_gles2_debug(buffer->renderer); + + glDeleteFramebuffers(1, &buffer->fbo); + glDeleteRenderbuffers(1, &buffer->rbo); + glDeleteTextures(1, &buffer->tex); + + pop_gles2_debug(buffer->renderer); + + wlr_egl_destroy_image(buffer->renderer->egl, buffer->image); + + wlr_egl_restore_context(&prev_ctx); + + free(buffer); +} + +static void handle_buffer_destroy(struct wlr_addon *addon) { + struct wlr_gles2_buffer *buffer = + wl_container_of(addon, buffer, addon); + destroy_buffer(buffer); +} + +static const struct wlr_addon_interface buffer_addon_impl = { + .name = "wlr_gles2_buffer", + .destroy = handle_buffer_destroy, +}; + +GLuint gles2_buffer_get_fbo(struct wlr_gles2_buffer *buffer) { + if (buffer->external_only) { + wlr_log(WLR_ERROR, "DMA-BUF format is external-only"); + return 0; + } + + if (buffer->fbo) { + return buffer->fbo; + } + + push_gles2_debug(buffer->renderer); + + if (!buffer->rbo) { + glGenRenderbuffers(1, &buffer->rbo); + glBindRenderbuffer(GL_RENDERBUFFER, buffer->rbo); + buffer->renderer->procs.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, + buffer->image); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + } + + glGenFramebuffers(1, &buffer->fbo); + glBindFramebuffer(GL_FRAMEBUFFER, buffer->fbo); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, buffer->rbo); + GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if (fb_status != GL_FRAMEBUFFER_COMPLETE) { + wlr_log(WLR_ERROR, "Failed to create FBO"); + glDeleteFramebuffers(1, &buffer->fbo); + buffer->fbo = 0; + } + + pop_gles2_debug(buffer->renderer); + + return buffer->fbo; +} + +struct wlr_gles2_buffer *gles2_buffer_get_or_create(struct wlr_gles2_renderer *renderer, + struct wlr_buffer *wlr_buffer) { + struct wlr_addon *addon = + wlr_addon_find(&wlr_buffer->addons, renderer, &buffer_addon_impl); + if (addon) { + struct wlr_gles2_buffer *buffer = wl_container_of(addon, buffer, addon); + return buffer; + } + + struct wlr_gles2_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + buffer->buffer = wlr_buffer; + buffer->renderer = renderer; + + struct wlr_dmabuf_attributes dmabuf = {0}; + if (!wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf)) { + goto error_buffer; + } + + buffer->image = wlr_egl_create_image_from_dmabuf(renderer->egl, + &dmabuf, &buffer->external_only); + if (buffer->image == EGL_NO_IMAGE_KHR) { + goto error_buffer; + } + + wlr_addon_init(&buffer->addon, &wlr_buffer->addons, renderer, + &buffer_addon_impl); + + wl_list_insert(&renderer->buffers, &buffer->link); + + wlr_log(WLR_DEBUG, "Created GL FBO for buffer %dx%d", + wlr_buffer->width, wlr_buffer->height); + + return buffer; + +error_buffer: + free(buffer); + return NULL; +} + +static const uint32_t *gles2_get_shm_texture_formats( + struct wlr_renderer *wlr_renderer, size_t *len) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + return get_gles2_shm_formats(renderer, len); +} + +static const struct wlr_drm_format_set *gles2_get_dmabuf_texture_formats( + struct wlr_renderer *wlr_renderer) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + return wlr_egl_get_dmabuf_texture_formats(renderer->egl); +} + +static const struct wlr_drm_format_set *gles2_get_render_formats( + struct wlr_renderer *wlr_renderer) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + return wlr_egl_get_dmabuf_render_formats(renderer->egl); +} + +static int gles2_get_drm_fd(struct wlr_renderer *wlr_renderer) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer(wlr_renderer); + + if (renderer->drm_fd < 0) { + renderer->drm_fd = wlr_egl_dup_drm_fd(renderer->egl); + } + + return renderer->drm_fd; +} + +static uint32_t gles2_get_render_buffer_caps(struct wlr_renderer *wlr_renderer) { + return WLR_BUFFER_CAP_DMABUF; +} + +struct wlr_egl *wlr_gles2_renderer_get_egl(struct wlr_renderer *wlr_renderer) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer(wlr_renderer); + return renderer->egl; +} + +static void gles2_destroy(struct wlr_renderer *wlr_renderer) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + + wlr_egl_make_current(renderer->egl, NULL); + + struct wlr_gles2_texture *tex, *tex_tmp; + wl_list_for_each_safe(tex, tex_tmp, &renderer->textures, link) { + gles2_texture_destroy(tex); + } + + struct wlr_gles2_buffer *buffer, *buffer_tmp; + wl_list_for_each_safe(buffer, buffer_tmp, &renderer->buffers, link) { + destroy_buffer(buffer); + } + + push_gles2_debug(renderer); + glDeleteProgram(renderer->shaders.quad.program); + glDeleteProgram(renderer->shaders.tex_rgba.program); + glDeleteProgram(renderer->shaders.tex_rgbx.program); + glDeleteProgram(renderer->shaders.tex_ext.program); + pop_gles2_debug(renderer); + + if (renderer->exts.KHR_debug) { + glDisable(GL_DEBUG_OUTPUT_KHR); + renderer->procs.glDebugMessageCallbackKHR(NULL, NULL); + } + + wlr_egl_unset_current(renderer->egl); + wlr_egl_destroy(renderer->egl); + + if (renderer->drm_fd >= 0) { + close(renderer->drm_fd); + } + + free(renderer); +} + +static struct wlr_render_pass *gles2_begin_buffer_pass(struct wlr_renderer *wlr_renderer, + struct wlr_buffer *wlr_buffer, const struct wlr_buffer_pass_options *options) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + + struct wlr_egl_context prev_ctx = {0}; + if (!wlr_egl_make_current(renderer->egl, &prev_ctx)) { + return NULL; + } + + struct wlr_gles2_render_timer *timer = NULL; + if (options->timer) { + timer = gles2_get_render_timer(options->timer); + clock_gettime(CLOCK_MONOTONIC, &timer->cpu_start); + } + + struct wlr_gles2_buffer *buffer = gles2_buffer_get_or_create(renderer, wlr_buffer); + if (!buffer) { + return NULL; + } + + struct wlr_gles2_render_pass *pass = begin_gles2_buffer_pass(buffer, &prev_ctx, timer); + if (!pass) { + return NULL; + } + return &pass->base; +} + +GLuint wlr_gles2_renderer_get_buffer_fbo(struct wlr_renderer *wlr_renderer, + struct wlr_buffer *wlr_buffer) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + GLuint fbo = 0; + + struct wlr_egl_context prev_ctx = {0}; + if (!wlr_egl_make_current(renderer->egl, &prev_ctx)) { + return 0; + } + + struct wlr_gles2_buffer *buffer = gles2_buffer_get_or_create(renderer, wlr_buffer); + if (buffer) { + fbo = gles2_buffer_get_fbo(buffer); + } + + wlr_egl_restore_context(&prev_ctx); + return fbo; +} + +static struct wlr_render_timer *gles2_render_timer_create(struct wlr_renderer *wlr_renderer) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + if (!renderer->exts.EXT_disjoint_timer_query) { + wlr_log(WLR_ERROR, "can't create timer, EXT_disjoint_timer_query not available"); + return NULL; + } + + struct wlr_gles2_render_timer *timer = calloc(1, sizeof(*timer)); + if (!timer) { + return NULL; + } + timer->base.impl = &render_timer_impl; + timer->renderer = renderer; + + struct wlr_egl_context prev_ctx; + wlr_egl_make_current(renderer->egl, &prev_ctx); + renderer->procs.glGenQueriesEXT(1, &timer->id); + wlr_egl_restore_context(&prev_ctx); + + return &timer->base; +} + +static int gles2_get_render_time(struct wlr_render_timer *wlr_timer) { + struct wlr_gles2_render_timer *timer = gles2_get_render_timer(wlr_timer); + struct wlr_gles2_renderer *renderer = timer->renderer; + + struct wlr_egl_context prev_ctx; + wlr_egl_make_current(renderer->egl, &prev_ctx); + + GLint64 disjoint; + renderer->procs.glGetInteger64vEXT(GL_GPU_DISJOINT_EXT, &disjoint); + if (disjoint) { + wlr_log(WLR_ERROR, "a disjoint operation occurred and the render timer is invalid"); + wlr_egl_restore_context(&prev_ctx); + return -1; + } + + GLint available; + renderer->procs.glGetQueryObjectivEXT(timer->id, + GL_QUERY_RESULT_AVAILABLE_EXT, &available); + if (!available) { + wlr_log(WLR_ERROR, "timer was read too early, gpu isn't done!"); + wlr_egl_restore_context(&prev_ctx); + return -1; + } + + GLuint64 gl_render_end; + renderer->procs.glGetQueryObjectui64vEXT(timer->id, GL_QUERY_RESULT_EXT, + &gl_render_end); + + int64_t cpu_nsec_total = timespec_to_nsec(&timer->cpu_end) - timespec_to_nsec(&timer->cpu_start); + + wlr_egl_restore_context(&prev_ctx); + return gl_render_end - timer->gl_cpu_end + cpu_nsec_total; +} + +static void gles2_render_timer_destroy(struct wlr_render_timer *wlr_timer) { + struct wlr_gles2_render_timer *timer = wl_container_of(wlr_timer, timer, base); + struct wlr_gles2_renderer *renderer = timer->renderer; + + struct wlr_egl_context prev_ctx; + wlr_egl_make_current(renderer->egl, &prev_ctx); + renderer->procs.glDeleteQueriesEXT(1, &timer->id); + wlr_egl_restore_context(&prev_ctx); + free(timer); +} + +static const struct wlr_renderer_impl renderer_impl = { + .destroy = gles2_destroy, + .get_shm_texture_formats = gles2_get_shm_texture_formats, + .get_dmabuf_texture_formats = gles2_get_dmabuf_texture_formats, + .get_render_formats = gles2_get_render_formats, + .get_drm_fd = gles2_get_drm_fd, + .get_render_buffer_caps = gles2_get_render_buffer_caps, + .texture_from_buffer = gles2_texture_from_buffer, + .begin_buffer_pass = gles2_begin_buffer_pass, + .render_timer_create = gles2_render_timer_create, +}; + +static const struct wlr_render_timer_impl render_timer_impl = { + .get_duration_ns = gles2_get_render_time, + .destroy = gles2_render_timer_destroy, +}; + +void push_gles2_debug_(struct wlr_gles2_renderer *renderer, + const char *file, const char *func) { + if (!renderer->procs.glPushDebugGroupKHR) { + return; + } + + int len = snprintf(NULL, 0, "%s:%s", file, func) + 1; + char str[len]; + snprintf(str, len, "%s:%s", file, func); + renderer->procs.glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION_KHR, 1, -1, str); +} + +void pop_gles2_debug(struct wlr_gles2_renderer *renderer) { + if (renderer->procs.glPopDebugGroupKHR) { + renderer->procs.glPopDebugGroupKHR(); + } +} + +static enum wlr_log_importance gles2_log_importance_to_wlr(GLenum type) { + switch (type) { + case GL_DEBUG_TYPE_ERROR_KHR: return WLR_ERROR; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR: return WLR_ERROR; + case GL_DEBUG_TYPE_PORTABILITY_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_PERFORMANCE_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_OTHER_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_MARKER_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_PUSH_GROUP_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_POP_GROUP_KHR: return WLR_DEBUG; + default: return WLR_DEBUG; + } +} + +static void gles2_log(GLenum src, GLenum type, GLuint id, GLenum severity, + GLsizei len, const GLchar *msg, const void *user) { + _wlr_log(gles2_log_importance_to_wlr(type), "[GLES2] %s", msg); +} + +static GLuint compile_shader(struct wlr_gles2_renderer *renderer, + GLenum type, const GLchar *src) { + push_gles2_debug(renderer); + + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &src, NULL); + glCompileShader(shader); + + GLint ok; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + if (ok == GL_FALSE) { + wlr_log(WLR_ERROR, "Failed to compile shader"); + glDeleteShader(shader); + shader = 0; + } + + pop_gles2_debug(renderer); + return shader; +} + +static GLuint link_program(struct wlr_gles2_renderer *renderer, + const GLchar *vert_src, const GLchar *frag_src) { + push_gles2_debug(renderer); + + GLuint vert = compile_shader(renderer, GL_VERTEX_SHADER, vert_src); + if (!vert) { + goto error; + } + + GLuint frag = compile_shader(renderer, GL_FRAGMENT_SHADER, frag_src); + if (!frag) { + glDeleteShader(vert); + goto error; + } + + GLuint prog = glCreateProgram(); + glAttachShader(prog, vert); + glAttachShader(prog, frag); + glLinkProgram(prog); + + glDetachShader(prog, vert); + glDetachShader(prog, frag); + glDeleteShader(vert); + glDeleteShader(frag); + + GLint ok; + glGetProgramiv(prog, GL_LINK_STATUS, &ok); + if (ok == GL_FALSE) { + wlr_log(WLR_ERROR, "Failed to link shader"); + glDeleteProgram(prog); + goto error; + } + + pop_gles2_debug(renderer); + return prog; + +error: + pop_gles2_debug(renderer); + return 0; +} + +static bool check_gl_ext(const char *exts, const char *ext) { + size_t extlen = strlen(ext); + const char *end = exts + strlen(exts); + + while (exts < end) { + if (exts[0] == ' ') { + exts++; + continue; + } + size_t n = strcspn(exts, " "); + if (n == extlen && strncmp(ext, exts, n) == 0) { + return true; + } + exts += n; + } + return false; +} + +static void load_gl_proc(void *proc_ptr, const char *name) { + void *proc = (void *)eglGetProcAddress(name); + if (proc == NULL) { + wlr_log(WLR_ERROR, "eglGetProcAddress(%s) failed", name); + abort(); + } + *(void **)proc_ptr = proc; +} + +struct wlr_renderer *wlr_gles2_renderer_create_with_drm_fd(int drm_fd) { + struct wlr_egl *egl = wlr_egl_create_with_drm_fd(drm_fd); + if (egl == NULL) { + wlr_log(WLR_ERROR, "Could not initialize EGL"); + return NULL; + } + + struct wlr_renderer *renderer = wlr_gles2_renderer_create(egl); + if (!renderer) { + wlr_log(WLR_ERROR, "Failed to create GLES2 renderer"); + wlr_egl_destroy(egl); + return NULL; + } + + return renderer; +} + +struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) { + if (!wlr_egl_make_current(egl, NULL)) { + return NULL; + } + + const char *exts_str = (const char *)glGetString(GL_EXTENSIONS); + if (exts_str == NULL) { + wlr_log(WLR_ERROR, "Failed to get GL_EXTENSIONS"); + return NULL; + } + + struct wlr_gles2_renderer *renderer = calloc(1, sizeof(*renderer)); + if (renderer == NULL) { + return NULL; + } + wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl); + + wl_list_init(&renderer->buffers); + wl_list_init(&renderer->textures); + + renderer->egl = egl; + renderer->exts_str = exts_str; + renderer->drm_fd = -1; + + wlr_log(WLR_INFO, "Creating GLES2 renderer"); + wlr_log(WLR_INFO, "Using %s", glGetString(GL_VERSION)); + wlr_log(WLR_INFO, "GL vendor: %s", glGetString(GL_VENDOR)); + wlr_log(WLR_INFO, "GL renderer: %s", glGetString(GL_RENDERER)); + wlr_log(WLR_INFO, "Supported GLES2 extensions: %s", exts_str); + + if (!renderer->egl->exts.EXT_image_dma_buf_import) { + wlr_log(WLR_ERROR, "EGL_EXT_image_dma_buf_import not supported"); + free(renderer); + return NULL; + } + if (!check_gl_ext(exts_str, "GL_EXT_texture_format_BGRA8888")) { + wlr_log(WLR_ERROR, "BGRA8888 format not supported by GLES2"); + free(renderer); + return NULL; + } + if (!check_gl_ext(exts_str, "GL_EXT_unpack_subimage")) { + wlr_log(WLR_ERROR, "GL_EXT_unpack_subimage not supported"); + free(renderer); + return NULL; + } + + renderer->exts.EXT_read_format_bgra = + check_gl_ext(exts_str, "GL_EXT_read_format_bgra"); + + renderer->exts.EXT_texture_type_2_10_10_10_REV = + check_gl_ext(exts_str, "GL_EXT_texture_type_2_10_10_10_REV"); + + renderer->exts.OES_texture_half_float_linear = + check_gl_ext(exts_str, "GL_OES_texture_half_float_linear"); + + renderer->exts.EXT_texture_norm16 = + check_gl_ext(exts_str, "GL_EXT_texture_norm16"); + + if (check_gl_ext(exts_str, "GL_KHR_debug")) { + renderer->exts.KHR_debug = true; + load_gl_proc(&renderer->procs.glDebugMessageCallbackKHR, + "glDebugMessageCallbackKHR"); + load_gl_proc(&renderer->procs.glDebugMessageControlKHR, + "glDebugMessageControlKHR"); + } + + if (check_gl_ext(exts_str, "GL_OES_EGL_image_external")) { + renderer->exts.OES_egl_image_external = true; + load_gl_proc(&renderer->procs.glEGLImageTargetTexture2DOES, + "glEGLImageTargetTexture2DOES"); + } + + if (check_gl_ext(exts_str, "GL_OES_EGL_image")) { + renderer->exts.OES_egl_image = true; + load_gl_proc(&renderer->procs.glEGLImageTargetRenderbufferStorageOES, + "glEGLImageTargetRenderbufferStorageOES"); + } + + if (check_gl_ext(exts_str, "GL_KHR_robustness")) { + GLint notif_strategy = 0; + glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_KHR, ¬if_strategy); + switch (notif_strategy) { + case GL_LOSE_CONTEXT_ON_RESET_KHR: + wlr_log(WLR_DEBUG, "GPU reset notifications are enabled"); + load_gl_proc(&renderer->procs.glGetGraphicsResetStatusKHR, + "glGetGraphicsResetStatusKHR"); + break; + case GL_NO_RESET_NOTIFICATION_KHR: + wlr_log(WLR_DEBUG, "GPU reset notifications are disabled"); + break; + } + } + + if (check_gl_ext(exts_str, "GL_EXT_disjoint_timer_query")) { + renderer->exts.EXT_disjoint_timer_query = true; + load_gl_proc(&renderer->procs.glGenQueriesEXT, "glGenQueriesEXT"); + load_gl_proc(&renderer->procs.glDeleteQueriesEXT, "glDeleteQueriesEXT"); + load_gl_proc(&renderer->procs.glQueryCounterEXT, "glQueryCounterEXT"); + load_gl_proc(&renderer->procs.glGetQueryObjectivEXT, "glGetQueryObjectivEXT"); + load_gl_proc(&renderer->procs.glGetQueryObjectui64vEXT, "glGetQueryObjectui64vEXT"); + load_gl_proc(&renderer->procs.glGetInteger64vEXT, "glGetInteger64vEXT"); + } + + if (renderer->exts.KHR_debug) { + glEnable(GL_DEBUG_OUTPUT_KHR); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); + renderer->procs.glDebugMessageCallbackKHR(gles2_log, NULL); + + // Silence unwanted message types + renderer->procs.glDebugMessageControlKHR(GL_DONT_CARE, + GL_DEBUG_TYPE_POP_GROUP_KHR, GL_DONT_CARE, 0, NULL, GL_FALSE); + renderer->procs.glDebugMessageControlKHR(GL_DONT_CARE, + GL_DEBUG_TYPE_PUSH_GROUP_KHR, GL_DONT_CARE, 0, NULL, GL_FALSE); + } + + push_gles2_debug(renderer); + + GLuint prog; + renderer->shaders.quad.program = prog = + link_program(renderer, common_vert_src, quad_frag_src); + if (!renderer->shaders.quad.program) { + goto error; + } + renderer->shaders.quad.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.quad.color = glGetUniformLocation(prog, "color"); + renderer->shaders.quad.pos_attrib = glGetAttribLocation(prog, "pos"); + + renderer->shaders.tex_rgba.program = prog = + link_program(renderer, common_vert_src, tex_rgba_frag_src); + if (!renderer->shaders.tex_rgba.program) { + goto error; + } + renderer->shaders.tex_rgba.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.tex_rgba.tex_proj = glGetUniformLocation(prog, "tex_proj"); + renderer->shaders.tex_rgba.tex = glGetUniformLocation(prog, "tex"); + renderer->shaders.tex_rgba.alpha = glGetUniformLocation(prog, "alpha"); + renderer->shaders.tex_rgba.pos_attrib = glGetAttribLocation(prog, "pos"); + + renderer->shaders.tex_rgbx.program = prog = + link_program(renderer, common_vert_src, tex_rgbx_frag_src); + if (!renderer->shaders.tex_rgbx.program) { + goto error; + } + renderer->shaders.tex_rgbx.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.tex_rgbx.tex_proj = glGetUniformLocation(prog, "tex_proj"); + renderer->shaders.tex_rgbx.tex = glGetUniformLocation(prog, "tex"); + renderer->shaders.tex_rgbx.alpha = glGetUniformLocation(prog, "alpha"); + renderer->shaders.tex_rgbx.pos_attrib = glGetAttribLocation(prog, "pos"); + + if (renderer->exts.OES_egl_image_external) { + renderer->shaders.tex_ext.program = prog = + link_program(renderer, common_vert_src, tex_external_frag_src); + if (!renderer->shaders.tex_ext.program) { + goto error; + } + renderer->shaders.tex_ext.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.tex_ext.tex_proj = glGetUniformLocation(prog, "tex_proj"); + renderer->shaders.tex_ext.tex = glGetUniformLocation(prog, "tex"); + renderer->shaders.tex_ext.alpha = glGetUniformLocation(prog, "alpha"); + renderer->shaders.tex_ext.pos_attrib = glGetAttribLocation(prog, "pos"); + } + + pop_gles2_debug(renderer); + + wlr_egl_unset_current(renderer->egl); + + return &renderer->wlr_renderer; + +error: + glDeleteProgram(renderer->shaders.quad.program); + glDeleteProgram(renderer->shaders.tex_rgba.program); + glDeleteProgram(renderer->shaders.tex_rgbx.program); + glDeleteProgram(renderer->shaders.tex_ext.program); + + pop_gles2_debug(renderer); + + if (renderer->exts.KHR_debug) { + glDisable(GL_DEBUG_OUTPUT_KHR); + renderer->procs.glDebugMessageCallbackKHR(NULL, NULL); + } + + wlr_egl_unset_current(renderer->egl); + + free(renderer); + return NULL; +} + +bool wlr_gles2_renderer_check_ext(struct wlr_renderer *wlr_renderer, + const char *ext) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + return check_gl_ext(renderer->exts_str, ext); +} diff --git a/render/gles2/shaders/common.vert b/render/gles2/shaders/common.vert new file mode 100644 index 0000000..f51548a --- /dev/null +++ b/render/gles2/shaders/common.vert @@ -0,0 +1,10 @@ +uniform mat3 proj; +uniform mat3 tex_proj; +attribute vec2 pos; +varying vec2 v_texcoord; + +void main() { + vec3 pos3 = vec3(pos, 1.0); + gl_Position = vec4(pos3 * proj, 1.0); + v_texcoord = (pos3 * tex_proj).xy; +} diff --git a/render/gles2/shaders/embed.sh b/render/gles2/shaders/embed.sh new file mode 100755 index 0000000..acd7a11 --- /dev/null +++ b/render/gles2/shaders/embed.sh @@ -0,0 +1,11 @@ +#!/bin/sh -eu + +var=${1:-data} +hex="$(od -A n -t x1 -v)" + +echo "static const char $var[] = {" +for byte in $hex; do + echo " 0x$byte," +done +echo " 0x00," +echo "};" diff --git a/render/gles2/shaders/meson.build b/render/gles2/shaders/meson.build new file mode 100644 index 0000000..79454d9 --- /dev/null +++ b/render/gles2/shaders/meson.build @@ -0,0 +1,22 @@ +embed = find_program('./embed.sh', native: true) + +shaders = [ + 'common.vert', + 'quad.frag', + 'tex_rgba.frag', + 'tex_rgbx.frag', + 'tex_external.frag', +] + +foreach name : shaders + output = name.underscorify() + '_src.h' + var = name.underscorify() + '_src' + wlr_files += custom_target( + output, + command: [embed, var], + input: name, + output: output, + feed: true, + capture: true, + ) +endforeach diff --git a/render/gles2/shaders/quad.frag b/render/gles2/shaders/quad.frag new file mode 100644 index 0000000..97d3a31 --- /dev/null +++ b/render/gles2/shaders/quad.frag @@ -0,0 +1,13 @@ +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif + +varying vec4 v_color; +varying vec2 v_texcoord; +uniform vec4 color; + +void main() { + gl_FragColor = color; +} diff --git a/render/gles2/shaders/tex_external.frag b/render/gles2/shaders/tex_external.frag new file mode 100644 index 0000000..73909fe --- /dev/null +++ b/render/gles2/shaders/tex_external.frag @@ -0,0 +1,15 @@ +#extension GL_OES_EGL_image_external : require + +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif + +varying vec2 v_texcoord; +uniform samplerExternalOES texture0; +uniform float alpha; + +void main() { + gl_FragColor = texture2D(texture0, v_texcoord) * alpha; +} diff --git a/render/gles2/shaders/tex_rgba.frag b/render/gles2/shaders/tex_rgba.frag new file mode 100644 index 0000000..c0a0dea --- /dev/null +++ b/render/gles2/shaders/tex_rgba.frag @@ -0,0 +1,13 @@ +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif + +varying vec2 v_texcoord; +uniform sampler2D tex; +uniform float alpha; + +void main() { + gl_FragColor = texture2D(tex, v_texcoord) * alpha; +} diff --git a/render/gles2/shaders/tex_rgbx.frag b/render/gles2/shaders/tex_rgbx.frag new file mode 100644 index 0000000..ae40ad5 --- /dev/null +++ b/render/gles2/shaders/tex_rgbx.frag @@ -0,0 +1,13 @@ +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif + +varying vec2 v_texcoord; +uniform sampler2D tex; +uniform float alpha; + +void main() { + gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0) * alpha; +} diff --git a/render/gles2/texture.c b/render/gles2/texture.c new file mode 100644 index 0000000..f9ad4cc --- /dev/null +++ b/render/gles2/texture.c @@ -0,0 +1,441 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "render/egl.h" +#include "render/gles2.h" +#include "render/pixel_format.h" +#include "types/wlr_buffer.h" + +static const struct wlr_texture_impl texture_impl; + +bool wlr_texture_is_gles2(struct wlr_texture *wlr_texture) { + return wlr_texture->impl == &texture_impl; +} + +struct wlr_gles2_texture *gles2_get_texture( + struct wlr_texture *wlr_texture) { + assert(wlr_texture_is_gles2(wlr_texture)); + struct wlr_gles2_texture *texture = wl_container_of(wlr_texture, texture, wlr_texture); + return texture; +} + +static bool gles2_texture_update_from_buffer(struct wlr_texture *wlr_texture, + struct wlr_buffer *buffer, const pixman_region32_t *damage) { + struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture); + + if (texture->drm_format == DRM_FORMAT_INVALID) { + return false; + } + + void *data; + uint32_t format; + size_t stride; + if (!wlr_buffer_begin_data_ptr_access(buffer, + WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { + return false; + } + + if (format != texture->drm_format) { + wlr_buffer_end_data_ptr_access(buffer); + return false; + } + + const struct wlr_gles2_pixel_format *fmt = + get_gles2_format_from_drm(texture->drm_format); + assert(fmt); + + const struct wlr_pixel_format_info *drm_fmt = + drm_get_pixel_format_info(texture->drm_format); + assert(drm_fmt); + if (pixel_format_info_pixels_per_block(drm_fmt) != 1) { + wlr_buffer_end_data_ptr_access(buffer); + wlr_log(WLR_ERROR, "Cannot update texture: block formats are not supported"); + return false; + } + + if (!pixel_format_info_check_stride(drm_fmt, stride, buffer->width)) { + wlr_buffer_end_data_ptr_access(buffer); + return false; + } + + struct wlr_egl_context prev_ctx; + wlr_egl_make_current(texture->renderer->egl, &prev_ctx); + + push_gles2_debug(texture->renderer); + + glBindTexture(GL_TEXTURE_2D, texture->tex); + + int rects_len = 0; + const pixman_box32_t *rects = pixman_region32_rectangles(damage, &rects_len); + + for (int i = 0; i < rects_len; i++) { + pixman_box32_t rect = rects[i]; + + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / drm_fmt->bytes_per_block); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, rect.x1); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, rect.y1); + + int width = rect.x2 - rect.x1; + int height = rect.y2 - rect.y1; + glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x1, rect.y1, width, height, + fmt->gl_format, fmt->gl_type, data); + } + + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + + pop_gles2_debug(texture->renderer); + + wlr_egl_restore_context(&prev_ctx); + + wlr_buffer_end_data_ptr_access(buffer); + + return true; +} + +void gles2_texture_destroy(struct wlr_gles2_texture *texture) { + wl_list_remove(&texture->link); + if (texture->buffer != NULL) { + wlr_buffer_unlock(texture->buffer->buffer); + } else { + struct wlr_egl_context prev_ctx; + wlr_egl_make_current(texture->renderer->egl, &prev_ctx); + + push_gles2_debug(texture->renderer); + + glDeleteTextures(1, &texture->tex); + glDeleteFramebuffers(1, &texture->fbo); + + pop_gles2_debug(texture->renderer); + + wlr_egl_restore_context(&prev_ctx); + } + + free(texture); +} + +static void handle_gles2_texture_destroy(struct wlr_texture *wlr_texture) { + gles2_texture_destroy(gles2_get_texture(wlr_texture)); +} + +static bool gles2_texture_bind(struct wlr_gles2_texture *texture) { + if (texture->fbo) { + glBindFramebuffer(GL_FRAMEBUFFER, texture->fbo); + } else if (texture->buffer) { + if (texture->buffer->external_only) { + return false; + } + + GLuint fbo = gles2_buffer_get_fbo(texture->buffer); + if (!fbo) { + return false; + } + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + } else { + glGenFramebuffers(1, &texture->fbo); + glBindFramebuffer(GL_FRAMEBUFFER, texture->fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + texture->target, texture->tex, 0); + + GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (fb_status != GL_FRAMEBUFFER_COMPLETE) { + wlr_log(WLR_ERROR, "Failed to create FBO"); + glDeleteFramebuffers(1, &texture->fbo); + texture->fbo = 0; + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return false; + } + } + + return true; +} + +static bool gles2_texture_read_pixels(struct wlr_texture *wlr_texture, + const struct wlr_texture_read_pixels_options *options) { + struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture); + + struct wlr_box src; + wlr_texture_read_pixels_options_get_src_box(options, wlr_texture, &src); + + const struct wlr_gles2_pixel_format *fmt = + get_gles2_format_from_drm(options->format); + if (fmt == NULL || !is_gles2_pixel_format_supported(texture->renderer, fmt)) { + wlr_log(WLR_ERROR, "Cannot read pixels: unsupported pixel format 0x%"PRIX32, options->format); + return false; + } + + if (fmt->gl_format == GL_BGRA_EXT && !texture->renderer->exts.EXT_read_format_bgra) { + wlr_log(WLR_ERROR, + "Cannot read pixels: missing GL_EXT_read_format_bgra extension"); + return false; + } + + const struct wlr_pixel_format_info *drm_fmt = + drm_get_pixel_format_info(fmt->drm_format); + assert(drm_fmt); + if (pixel_format_info_pixels_per_block(drm_fmt) != 1) { + wlr_log(WLR_ERROR, "Cannot read pixels: block formats are not supported"); + return false; + } + + push_gles2_debug(texture->renderer); + struct wlr_egl_context prev_ctx; + if (!wlr_egl_make_current(texture->renderer->egl, &prev_ctx)) { + return false; + } + + if (!gles2_texture_bind(texture)) { + return false; + } + + // Make sure any pending drawing is finished before we try to read it + glFinish(); + + glGetError(); // Clear the error flag + + unsigned char *p = wlr_texture_read_pixel_options_get_data(options); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + uint32_t pack_stride = pixel_format_info_min_stride(drm_fmt, src.width); + if (pack_stride == options->stride && options->dst_x == 0) { + // Under these particular conditions, we can read the pixels with only + // one glReadPixels call + + glReadPixels(src.x, src.y, src.width, src.height, fmt->gl_format, fmt->gl_type, p); + } else { + // Unfortunately GLES2 doesn't support GL_PACK_ROW_LENGTH, so we have to read + // the lines out row by row + for (int32_t i = 0; i < src.height; ++i) { + uint32_t y = src.y + i; + glReadPixels(src.x, y, src.width, 1, fmt->gl_format, + fmt->gl_type, p + i * options->stride); + } + } + + wlr_egl_restore_context(&prev_ctx); + pop_gles2_debug(texture->renderer); + + return glGetError() == GL_NO_ERROR; +} + +static uint32_t gles2_texture_preferred_read_format(struct wlr_texture *wlr_texture) { + struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture); + + push_gles2_debug(texture->renderer); + + uint32_t fmt = DRM_FORMAT_INVALID; + + struct wlr_egl_context prev_ctx; + if (!wlr_egl_make_current(texture->renderer->egl, &prev_ctx)) { + return fmt; + } + + if (!gles2_texture_bind(texture)) { + goto out; + } + + GLint gl_format = -1, gl_type = -1, alpha_size = -1; + glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &gl_format); + glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &gl_type); + glGetIntegerv(GL_ALPHA_BITS, &alpha_size); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + pop_gles2_debug(texture->renderer); + + const struct wlr_gles2_pixel_format *pix_fmt = + get_gles2_format_from_gl(gl_format, gl_type, alpha_size > 0); + if (pix_fmt != NULL) { + fmt = pix_fmt->drm_format; + goto out; + } + + if (texture->renderer->exts.EXT_read_format_bgra) { + fmt = DRM_FORMAT_XRGB8888; + goto out; + } + +out: + wlr_egl_restore_context(&prev_ctx); + return fmt; +} + +static const struct wlr_texture_impl texture_impl = { + .update_from_buffer = gles2_texture_update_from_buffer, + .read_pixels = gles2_texture_read_pixels, + .preferred_read_format = gles2_texture_preferred_read_format, + .destroy = handle_gles2_texture_destroy, +}; + +static struct wlr_gles2_texture *gles2_texture_create( + struct wlr_gles2_renderer *renderer, uint32_t width, uint32_t height) { + struct wlr_gles2_texture *texture = calloc(1, sizeof(*texture)); + if (texture == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + wlr_texture_init(&texture->wlr_texture, &renderer->wlr_renderer, + &texture_impl, width, height); + texture->renderer = renderer; + wl_list_insert(&renderer->textures, &texture->link); + return texture; +} + +static struct wlr_texture *gles2_texture_from_pixels( + struct wlr_renderer *wlr_renderer, + uint32_t drm_format, uint32_t stride, uint32_t width, + uint32_t height, const void *data) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + + const struct wlr_gles2_pixel_format *fmt = + get_gles2_format_from_drm(drm_format); + if (fmt == NULL) { + wlr_log(WLR_ERROR, "Unsupported pixel format 0x%"PRIX32, drm_format); + return NULL; + } + + const struct wlr_pixel_format_info *drm_fmt = + drm_get_pixel_format_info(drm_format); + assert(drm_fmt); + if (pixel_format_info_pixels_per_block(drm_fmt) != 1) { + wlr_log(WLR_ERROR, "Cannot upload texture: block formats are not supported"); + return NULL; + } + + if (!pixel_format_info_check_stride(drm_fmt, stride, width)) { + return NULL; + } + + struct wlr_gles2_texture *texture = + gles2_texture_create(renderer, width, height); + if (texture == NULL) { + return NULL; + } + texture->target = GL_TEXTURE_2D; + texture->has_alpha = pixel_format_has_alpha(fmt->drm_format); + texture->drm_format = fmt->drm_format; + + GLint internal_format = fmt->gl_internalformat; + if (!internal_format) { + internal_format = fmt->gl_format; + } + + struct wlr_egl_context prev_ctx; + wlr_egl_make_current(renderer->egl, &prev_ctx); + + push_gles2_debug(renderer); + + glGenTextures(1, &texture->tex); + glBindTexture(GL_TEXTURE_2D, texture->tex); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / drm_fmt->bytes_per_block); + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, + fmt->gl_format, fmt->gl_type, data); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + + pop_gles2_debug(renderer); + + wlr_egl_restore_context(&prev_ctx); + + return &texture->wlr_texture; +} + +static struct wlr_texture *gles2_texture_from_dmabuf( + struct wlr_gles2_renderer *renderer, struct wlr_buffer *wlr_buffer, + struct wlr_dmabuf_attributes *attribs) { + if (!renderer->procs.glEGLImageTargetTexture2DOES) { + return NULL; + } + + struct wlr_gles2_buffer *buffer = gles2_buffer_get_or_create(renderer, wlr_buffer); + if (!buffer) { + return NULL; + } + + struct wlr_gles2_texture *texture = + gles2_texture_create(renderer, attribs->width, attribs->height); + if (texture == NULL) { + return NULL; + } + + texture->target = buffer->external_only ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; + texture->buffer = buffer; + texture->drm_format = DRM_FORMAT_INVALID; // texture can't be written anyways + texture->has_alpha = pixel_format_has_alpha(attribs->format); + + struct wlr_egl_context prev_ctx; + wlr_egl_make_current(renderer->egl, &prev_ctx); + push_gles2_debug(texture->renderer); + + bool invalid; + if (!buffer->tex) { + glGenTextures(1, &buffer->tex); + invalid = true; + } else { + // External changes are immediately made visible by the GL implementation + invalid = !buffer->external_only; + } + + if (invalid) { + glBindTexture(texture->target, buffer->tex); + glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + renderer->procs.glEGLImageTargetTexture2DOES(texture->target, buffer->image); + glBindTexture(texture->target, 0); + } + + pop_gles2_debug(texture->renderer); + wlr_egl_restore_context(&prev_ctx); + + texture->tex = buffer->tex; + wlr_buffer_lock(texture->buffer->buffer); + return &texture->wlr_texture; +} + +struct wlr_texture *gles2_texture_from_buffer(struct wlr_renderer *wlr_renderer, + struct wlr_buffer *buffer) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + + void *data; + uint32_t format; + size_t stride; + struct wlr_dmabuf_attributes dmabuf; + if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) { + return gles2_texture_from_dmabuf(renderer, buffer, &dmabuf); + } else if (wlr_buffer_begin_data_ptr_access(buffer, + WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { + struct wlr_texture *tex = gles2_texture_from_pixels(wlr_renderer, + format, stride, buffer->width, buffer->height, data); + wlr_buffer_end_data_ptr_access(buffer); + return tex; + } else { + return NULL; + } +} + +void wlr_gles2_texture_get_attribs(struct wlr_texture *wlr_texture, + struct wlr_gles2_texture_attribs *attribs) { + struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture); + *attribs = (struct wlr_gles2_texture_attribs){ + .target = texture->target, + .tex = texture->tex, + .has_alpha = texture->has_alpha, + }; +} diff --git a/render/meson.build b/render/meson.build new file mode 100644 index 0000000..f09905c --- /dev/null +++ b/render/meson.build @@ -0,0 +1,41 @@ +renderers = get_option('renderers') +if 'auto' in renderers and get_option('auto_features').enabled() + renderers = ['gles2', 'vulkan'] +elif 'auto' in renderers and get_option('auto_features').disabled() + renderers = [] +endif + +wlr_files += files( + 'dmabuf.c', + 'drm_format_set.c', + 'pass.c', + 'pixel_format.c', + 'swapchain.c', + 'wlr_renderer.c', + 'wlr_texture.c', +) + +if cc.has_header('linux/dma-buf.h') and target_machine.system() == 'linux' + wlr_files += files('dmabuf_linux.c') +else + wlr_files += files('dmabuf_fallback.c') +endif + +if 'gles2' in renderers or 'auto' in renderers + egl = dependency('egl', required: 'gles2' in renderers) + gbm = dependency('gbm', required: 'gles2' in renderers) + if egl.found() and gbm.found() + wlr_deps += [egl, gbm] + wlr_files += files('egl.c') + internal_features += { 'egl': true } + endif + subdir('gles2') +endif + +if 'vulkan' in renderers or 'auto' in renderers + subdir('vulkan') +endif + +subdir('pixman') + +subdir('allocator') diff --git a/render/pass.c b/render/pass.c new file mode 100644 index 0000000..9022282 --- /dev/null +++ b/render/pass.c @@ -0,0 +1,76 @@ +#include +#include +#include + +void wlr_render_pass_init(struct wlr_render_pass *render_pass, + const struct wlr_render_pass_impl *impl) { + assert(impl->submit && impl->add_texture && impl->add_rect); + *render_pass = (struct wlr_render_pass){ + .impl = impl, + }; +} + +bool wlr_render_pass_submit(struct wlr_render_pass *render_pass) { + return render_pass->impl->submit(render_pass); +} + +void wlr_render_pass_add_texture(struct wlr_render_pass *render_pass, + const struct wlr_render_texture_options *options) { + // make sure the texture source box does not try and sample outside of the + // texture + if (!wlr_fbox_empty(&options->src_box)) { + const struct wlr_fbox *box = &options->src_box; + assert(box->x >= 0 && box->y >= 0 && + box->x + box->width <= options->texture->width && + box->y + box->height <= options->texture->height); + } + + render_pass->impl->add_texture(render_pass, options); +} + +void wlr_render_pass_add_rect(struct wlr_render_pass *render_pass, + const struct wlr_render_rect_options *options) { + assert(options->box.width >= 0 && options->box.height >= 0); + render_pass->impl->add_rect(render_pass, options); +} + +void wlr_render_texture_options_get_src_box(const struct wlr_render_texture_options *options, + struct wlr_fbox *box) { + *box = options->src_box; + if (wlr_fbox_empty(box)) { + *box = (struct wlr_fbox){ + .width = options->texture->width, + .height = options->texture->height, + }; + } +} + +void wlr_render_texture_options_get_dst_box(const struct wlr_render_texture_options *options, + struct wlr_box *box) { + *box = options->dst_box; + if (wlr_box_empty(box)) { + box->width = options->texture->width; + box->height = options->texture->height; + } +} + +float wlr_render_texture_options_get_alpha(const struct wlr_render_texture_options *options) { + if (options->alpha == NULL) { + return 1; + } + return *options->alpha; +} + +void wlr_render_rect_options_get_box(const struct wlr_render_rect_options *options, + const struct wlr_buffer *buffer, struct wlr_box *box) { + if (wlr_box_empty(&options->box)) { + *box = (struct wlr_box){ + .width = buffer->width, + .height = buffer->height, + }; + + return; + } + + *box = options->box; +} diff --git a/render/pixel_format.c b/render/pixel_format.c new file mode 100644 index 0000000..7befbf0 --- /dev/null +++ b/render/pixel_format.c @@ -0,0 +1,277 @@ +#include +#include +#include +#include "render/pixel_format.h" + +static const struct wlr_pixel_format_info pixel_format_info[] = { + { + .drm_format = DRM_FORMAT_XRGB8888, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_ARGB8888, + .opaque_substitute = DRM_FORMAT_XRGB8888, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_XBGR8888, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_ABGR8888, + .opaque_substitute = DRM_FORMAT_XBGR8888, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_RGBX8888, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_RGBA8888, + .opaque_substitute = DRM_FORMAT_RGBX8888, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_BGRX8888, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_BGRA8888, + .opaque_substitute = DRM_FORMAT_BGRX8888, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_R8, + .bytes_per_block = 1, + }, + { + .drm_format = DRM_FORMAT_GR88, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_RGB888, + .bytes_per_block = 3, + }, + { + .drm_format = DRM_FORMAT_BGR888, + .bytes_per_block = 3, + }, + { + .drm_format = DRM_FORMAT_RGBX4444, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_RGBA4444, + .opaque_substitute = DRM_FORMAT_RGBX4444, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_BGRX4444, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_BGRA4444, + .opaque_substitute = DRM_FORMAT_BGRX4444, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_RGBX5551, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_RGBA5551, + .opaque_substitute = DRM_FORMAT_RGBX5551, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_BGRX5551, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_BGRA5551, + .opaque_substitute = DRM_FORMAT_BGRX5551, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_XRGB1555, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_ARGB1555, + .opaque_substitute = DRM_FORMAT_XRGB1555, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_RGB565, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_BGR565, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_XRGB2101010, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_ARGB2101010, + .opaque_substitute = DRM_FORMAT_XRGB2101010, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_XBGR2101010, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_ABGR2101010, + .opaque_substitute = DRM_FORMAT_XBGR2101010, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_XBGR16161616F, + .bytes_per_block = 8, + }, + { + .drm_format = DRM_FORMAT_ABGR16161616F, + .opaque_substitute = DRM_FORMAT_XBGR16161616F, + .bytes_per_block = 8, + }, + { + .drm_format = DRM_FORMAT_XBGR16161616, + .bytes_per_block = 8, + }, + { + .drm_format = DRM_FORMAT_ABGR16161616, + .opaque_substitute = DRM_FORMAT_XBGR16161616, + .bytes_per_block = 8, + }, + { + .drm_format = DRM_FORMAT_YVYU, + .bytes_per_block = 4, + .block_width = 2, + .block_height = 1, + }, + { + .drm_format = DRM_FORMAT_VYUY, + .bytes_per_block = 4, + .block_width = 2, + .block_height = 1, + }, +}; + +static const uint32_t opaque_pixel_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_R8, + DRM_FORMAT_GR88, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGBX4444, + DRM_FORMAT_BGRX4444, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_XBGR2101010, + DRM_FORMAT_XBGR16161616F, + DRM_FORMAT_XBGR16161616, + DRM_FORMAT_YVYU, + DRM_FORMAT_VYUY, + DRM_FORMAT_NV12, + DRM_FORMAT_P010, +}; + +static const size_t pixel_format_info_size = + sizeof(pixel_format_info) / sizeof(pixel_format_info[0]); + +static const size_t opaque_pixel_formats_size = + sizeof(opaque_pixel_formats) / sizeof(opaque_pixel_formats[0]); + +const struct wlr_pixel_format_info *drm_get_pixel_format_info(uint32_t fmt) { + for (size_t i = 0; i < pixel_format_info_size; ++i) { + if (pixel_format_info[i].drm_format == fmt) { + return &pixel_format_info[i]; + } + } + + return NULL; +} + +uint32_t convert_wl_shm_format_to_drm(enum wl_shm_format fmt) { + switch (fmt) { + case WL_SHM_FORMAT_XRGB8888: + return DRM_FORMAT_XRGB8888; + case WL_SHM_FORMAT_ARGB8888: + return DRM_FORMAT_ARGB8888; + default: + return (uint32_t)fmt; + } +} + +enum wl_shm_format convert_drm_format_to_wl_shm(uint32_t fmt) { + switch (fmt) { + case DRM_FORMAT_XRGB8888: + return WL_SHM_FORMAT_XRGB8888; + case DRM_FORMAT_ARGB8888: + return WL_SHM_FORMAT_ARGB8888; + default: + return (enum wl_shm_format)fmt; + } +} + +uint32_t pixel_format_info_pixels_per_block(const struct wlr_pixel_format_info *info) { + uint32_t pixels = info->block_width * info->block_height; + return pixels > 0 ? pixels : 1; +} + +static int32_t div_round_up(int32_t dividend, int32_t divisor) { + int32_t quotient = dividend / divisor; + if (dividend % divisor != 0) { + quotient++; + } + return quotient; +} + +int32_t pixel_format_info_min_stride(const struct wlr_pixel_format_info *fmt, int32_t width) { + int32_t pixels_per_block = (int32_t)pixel_format_info_pixels_per_block(fmt); + int32_t bytes_per_block = (int32_t)fmt->bytes_per_block; + if (width > INT32_MAX / bytes_per_block) { + wlr_log(WLR_DEBUG, "Invalid width %d (overflow)", width); + return 0; + } + return div_round_up(width * bytes_per_block, pixels_per_block); +} + +bool pixel_format_info_check_stride(const struct wlr_pixel_format_info *fmt, + int32_t stride, int32_t width) { + int32_t bytes_per_block = (int32_t)fmt->bytes_per_block; + if (stride % bytes_per_block != 0) { + wlr_log(WLR_DEBUG, "Invalid stride %d (incompatible with %d " + "bytes-per-block)", stride, bytes_per_block); + return false; + } + + int32_t min_stride = pixel_format_info_min_stride(fmt, width); + if (min_stride <= 0) { + return false; + } else if (stride < min_stride) { + wlr_log(WLR_DEBUG, "Invalid stride %d (too small for %d " + "bytes-per-block and width %d)", stride, bytes_per_block, width); + return false; + } + + return true; +} + +bool pixel_format_has_alpha(uint32_t fmt) { + for (size_t i = 0; i < opaque_pixel_formats_size; i++) { + if (fmt == opaque_pixel_formats[i]) { + return false; + } + } + return true; +} diff --git a/render/pixman/meson.build b/render/pixman/meson.build new file mode 100644 index 0000000..c8fb875 --- /dev/null +++ b/render/pixman/meson.build @@ -0,0 +1,9 @@ +pixman = dependency('pixman-1') + +wlr_deps += pixman + +wlr_files += files( + 'pass.c', + 'pixel_format.c', + 'renderer.c', +) diff --git a/render/pixman/pass.c b/render/pixman/pass.c new file mode 100644 index 0000000..5fe73b0 --- /dev/null +++ b/render/pixman/pass.c @@ -0,0 +1,213 @@ +#include +#include +#include "render/pixman.h" + +static const struct wlr_render_pass_impl render_pass_impl; + +static struct wlr_pixman_render_pass *get_render_pass(struct wlr_render_pass *wlr_pass) { + assert(wlr_pass->impl == &render_pass_impl); + struct wlr_pixman_render_pass *pass = wl_container_of(wlr_pass, pass, base); + return pass; +} + +static struct wlr_pixman_texture *get_texture(struct wlr_texture *wlr_texture) { + assert(wlr_texture_is_pixman(wlr_texture)); + struct wlr_pixman_texture *texture = wl_container_of(wlr_texture, texture, wlr_texture); + return texture; +} + +static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { + struct wlr_pixman_render_pass *pass = get_render_pass(wlr_pass); + + wlr_buffer_end_data_ptr_access(pass->buffer->buffer); + wlr_buffer_unlock(pass->buffer->buffer); + free(pass); + + return true; +} + +static pixman_op_t get_pixman_blending(enum wlr_render_blend_mode mode) { + switch (mode) { + case WLR_RENDER_BLEND_MODE_PREMULTIPLIED: + return PIXMAN_OP_OVER; + case WLR_RENDER_BLEND_MODE_NONE: + return PIXMAN_OP_SRC; + } + abort(); +} + +static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, + const struct wlr_render_texture_options *options) { + struct wlr_pixman_render_pass *pass = get_render_pass(wlr_pass); + struct wlr_pixman_texture *texture = get_texture(options->texture); + struct wlr_pixman_buffer *buffer = pass->buffer; + + if (texture->buffer != NULL && !begin_pixman_data_ptr_access(texture->buffer, + &texture->image, WLR_BUFFER_DATA_PTR_ACCESS_READ)) { + return; + } + + struct wlr_fbox src_fbox; + wlr_render_texture_options_get_src_box(options, &src_fbox); + struct wlr_box src_box = { + .x = roundf(src_fbox.x), + .y = roundf(src_fbox.y), + .width = roundf(src_fbox.width), + .height = roundf(src_fbox.height), + }; + + struct wlr_box dst_box; + wlr_render_texture_options_get_dst_box(options, &dst_box); + + pixman_image_t *mask = NULL; + float alpha = wlr_render_texture_options_get_alpha(options); + if (alpha != 1) { + mask = pixman_image_create_solid_fill(&(struct pixman_color){ + .alpha = 0xFFFF * alpha, + }); + } + + struct wlr_box orig_box; + wlr_box_transform(&orig_box, &dst_box, options->transform, + buffer->buffer->width, buffer->buffer->height); + + int32_t dest_x, dest_y, width, height; + if (options->transform != WL_OUTPUT_TRANSFORM_NORMAL || + orig_box.width != src_box.width || + orig_box.height != src_box.height) { + // Cosinus/sinus values are extact integers for enum wl_output_transform entries + int tr_cos = 1, tr_sin = 0, tr_x = 0, tr_y = 0; + switch (options->transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_FLIPPED: + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + tr_cos = 0; + tr_sin = 1; + tr_x = buffer->buffer->height; + break; + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + tr_cos = -1; + tr_sin = 0; + tr_x = buffer->buffer->width; + tr_y = buffer->buffer->height; + break; + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + tr_cos = 0; + tr_sin = -1; + tr_y = buffer->buffer->width; + break; + } + + struct pixman_transform transform; + pixman_transform_init_identity(&transform); + pixman_transform_rotate(&transform, NULL, + pixman_int_to_fixed(tr_cos), pixman_int_to_fixed(tr_sin)); + if (options->transform >= WL_OUTPUT_TRANSFORM_FLIPPED) { + pixman_transform_scale(&transform, NULL, + pixman_int_to_fixed(-1), pixman_int_to_fixed(1)); + } + pixman_transform_translate(&transform, NULL, + pixman_int_to_fixed(tr_x), pixman_int_to_fixed(tr_y)); + pixman_transform_translate(&transform, NULL, + -pixman_int_to_fixed(orig_box.x), -pixman_int_to_fixed(orig_box.y)); + pixman_transform_scale(&transform, NULL, + pixman_double_to_fixed(src_box.width / (double)orig_box.width), + pixman_double_to_fixed(src_box.height / (double)orig_box.height)); + pixman_image_set_transform(texture->image, &transform); + + dest_x = dest_y = 0; + width = buffer->buffer->width; + height = buffer->buffer->height; + } else { + pixman_image_set_transform(texture->image, NULL); + dest_x = dst_box.x; + dest_y = dst_box.y; + width = src_box.width; + height = src_box.height; + } + + switch (options->filter_mode) { + case WLR_SCALE_FILTER_BILINEAR: + pixman_image_set_filter(texture->image, PIXMAN_FILTER_BILINEAR, NULL, 0); + break; + case WLR_SCALE_FILTER_NEAREST: + pixman_image_set_filter(texture->image, PIXMAN_FILTER_NEAREST, NULL, 0); + break; + } + + pixman_op_t op = get_pixman_blending(options->blend_mode); + + pixman_image_set_clip_region32(buffer->image, (pixman_region32_t *)options->clip); + pixman_image_composite32(op, texture->image, mask, + buffer->image, src_box.x, src_box.y, 0, 0, dest_x, dest_y, + width, height); + pixman_image_set_clip_region32(buffer->image, NULL); + + pixman_image_set_transform(texture->image, NULL); + + if (texture->buffer != NULL) { + wlr_buffer_end_data_ptr_access(texture->buffer); + } + + if (mask != NULL) { + pixman_image_unref(mask); + } +} + +static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, + const struct wlr_render_rect_options *options) { + struct wlr_pixman_render_pass *pass = get_render_pass(wlr_pass); + struct wlr_pixman_buffer *buffer = pass->buffer; + struct wlr_box box; + wlr_render_rect_options_get_box(options, pass->buffer->buffer, &box); + + pixman_op_t op = get_pixman_blending(options->color.a == 1 ? + WLR_RENDER_BLEND_MODE_NONE : options->blend_mode); + + struct pixman_color color = { + .red = options->color.r * 0xFFFF, + .green = options->color.g * 0xFFFF, + .blue = options->color.b * 0xFFFF, + .alpha = options->color.a * 0xFFFF, + }; + + pixman_image_t *fill = pixman_image_create_solid_fill(&color); + + pixman_image_set_clip_region32(buffer->image, (pixman_region32_t *)options->clip); + pixman_image_composite32(op, fill, NULL, buffer->image, + 0, 0, 0, 0, box.x, box.y, box.width, box.height); + pixman_image_set_clip_region32(buffer->image, NULL); + + pixman_image_unref(fill); +} + +static const struct wlr_render_pass_impl render_pass_impl = { + .submit = render_pass_submit, + .add_texture = render_pass_add_texture, + .add_rect = render_pass_add_rect, +}; + +struct wlr_pixman_render_pass *begin_pixman_render_pass( + struct wlr_pixman_buffer *buffer) { + struct wlr_pixman_render_pass *pass = calloc(1, sizeof(*pass)); + if (pass == NULL) { + return NULL; + } + + wlr_render_pass_init(&pass->base, &render_pass_impl); + + if (!begin_pixman_data_ptr_access(buffer->buffer, &buffer->image, + WLR_BUFFER_DATA_PTR_ACCESS_READ | WLR_BUFFER_DATA_PTR_ACCESS_WRITE)) { + free(pass); + return NULL; + } + + wlr_buffer_lock(buffer->buffer); + pass->buffer = buffer; + + return pass; +} diff --git a/render/pixman/pixel_format.c b/render/pixman/pixel_format.c new file mode 100644 index 0000000..5a460a0 --- /dev/null +++ b/render/pixman/pixel_format.c @@ -0,0 +1,131 @@ +#include +#include + +#include "render/pixman.h" + +static const struct wlr_pixman_pixel_format formats[] = { + { + .drm_format = DRM_FORMAT_ARGB8888, +#if WLR_BIG_ENDIAN + .pixman_format = PIXMAN_b8g8r8a8, +#else + .pixman_format = PIXMAN_a8r8g8b8, +#endif + }, + { + .drm_format = DRM_FORMAT_XBGR8888, +#if WLR_BIG_ENDIAN + .pixman_format = PIXMAN_r8g8b8x8, +#else + .pixman_format = PIXMAN_x8b8g8r8, +#endif + }, + { + .drm_format = DRM_FORMAT_XRGB8888, +#if WLR_BIG_ENDIAN + .pixman_format = PIXMAN_b8g8r8x8, +#else + .pixman_format = PIXMAN_x8r8g8b8, +#endif + }, + { + .drm_format = DRM_FORMAT_ABGR8888, +#if WLR_BIG_ENDIAN + .pixman_format = PIXMAN_r8g8b8a8, +#else + .pixman_format = PIXMAN_a8b8g8r8, +#endif + }, + { + .drm_format = DRM_FORMAT_RGBA8888, +#if WLR_BIG_ENDIAN + .pixman_format = PIXMAN_a8b8g8r8, +#else + .pixman_format = PIXMAN_r8g8b8a8, +#endif + }, + { + .drm_format = DRM_FORMAT_RGBX8888, +#if WLR_BIG_ENDIAN + .pixman_format = PIXMAN_x8b8g8r8, +#else + .pixman_format = PIXMAN_r8g8b8x8, +#endif + }, + { + .drm_format = DRM_FORMAT_BGRA8888, +#if WLR_BIG_ENDIAN + .pixman_format = PIXMAN_a8r8g8b8, +#else + .pixman_format = PIXMAN_b8g8r8a8, +#endif + }, + { + .drm_format = DRM_FORMAT_BGRX8888, +#if WLR_BIG_ENDIAN + .pixman_format = PIXMAN_x8r8g8b8, +#else + .pixman_format = PIXMAN_b8g8r8x8, +#endif + }, +#if WLR_LITTLE_ENDIAN + // Since DRM formats are always little-endian, they don't have an + // equivalent on big-endian if their components are spanning across + // multiple bytes. + { + .drm_format = DRM_FORMAT_RGB565, + .pixman_format = PIXMAN_r5g6b5, + }, + { + .drm_format = DRM_FORMAT_BGR565, + .pixman_format = PIXMAN_b5g6r5, + }, + { + .drm_format = DRM_FORMAT_ARGB2101010, + .pixman_format = PIXMAN_a2r10g10b10, + }, + { + .drm_format = DRM_FORMAT_XRGB2101010, + .pixman_format = PIXMAN_x2r10g10b10, + }, + { + .drm_format = DRM_FORMAT_ABGR2101010, + .pixman_format = PIXMAN_a2b10g10r10, + }, + { + .drm_format = DRM_FORMAT_XBGR2101010, + .pixman_format = PIXMAN_x2b10g10r10, + }, +#endif +}; + +pixman_format_code_t get_pixman_format_from_drm(uint32_t fmt) { + for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) { + if (formats[i].drm_format == fmt) { + return formats[i].pixman_format; + } + } + + wlr_log(WLR_ERROR, "DRM format 0x%"PRIX32" has no pixman equivalent", fmt); + return 0; +} + +uint32_t get_drm_format_from_pixman(pixman_format_code_t fmt) { + for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) { + if (formats[i].pixman_format == fmt) { + return formats[i].drm_format; + } + } + + wlr_log(WLR_ERROR, "pixman format 0x%"PRIX32" has no drm equivalent", fmt); + return DRM_FORMAT_INVALID; +} + +const uint32_t *get_pixman_drm_formats(size_t *len) { + static uint32_t drm_formats[sizeof(formats) / sizeof(formats[0])]; + *len = sizeof(formats) / sizeof(formats[0]); + for (size_t i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) { + drm_formats[i] = formats[i].drm_format; + } + return drm_formats; +} diff --git a/render/pixman/renderer.c b/render/pixman/renderer.c new file mode 100644 index 0000000..27cde93 --- /dev/null +++ b/render/pixman/renderer.c @@ -0,0 +1,362 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "render/pixman.h" +#include "types/wlr_buffer.h" + +static const struct wlr_renderer_impl renderer_impl; + +bool wlr_renderer_is_pixman(struct wlr_renderer *wlr_renderer) { + return wlr_renderer->impl == &renderer_impl; +} + +static struct wlr_pixman_renderer *get_renderer( + struct wlr_renderer *wlr_renderer) { + assert(wlr_renderer_is_pixman(wlr_renderer)); + struct wlr_pixman_renderer *renderer = wl_container_of(wlr_renderer, renderer, wlr_renderer); + return renderer; +} + +bool begin_pixman_data_ptr_access(struct wlr_buffer *wlr_buffer, pixman_image_t **image_ptr, + uint32_t flags) { + pixman_image_t *image = *image_ptr; + + void *data = NULL; + uint32_t drm_format; + size_t stride; + if (!wlr_buffer_begin_data_ptr_access(wlr_buffer, flags, + &data, &drm_format, &stride)) { + return false; + } + + // If the data pointer has changed, re-create the Pixman image. This can + // happen if it's a client buffer and the wl_shm_pool has been resized. + if (data != pixman_image_get_data(image)) { + pixman_format_code_t format = get_pixman_format_from_drm(drm_format); + assert(format != 0); + + pixman_image_t *new_image = pixman_image_create_bits_no_clear(format, + wlr_buffer->width, wlr_buffer->height, data, stride); + if (image == NULL) { + wlr_buffer_end_data_ptr_access(wlr_buffer); + return false; + } + + pixman_image_unref(image); + image = new_image; + } + + *image_ptr = image; + return true; +} + +static struct wlr_pixman_buffer *get_buffer( + struct wlr_pixman_renderer *renderer, struct wlr_buffer *wlr_buffer) { + struct wlr_pixman_buffer *buffer; + wl_list_for_each(buffer, &renderer->buffers, link) { + if (buffer->buffer == wlr_buffer) { + return buffer; + } + } + return NULL; +} + +static const struct wlr_texture_impl texture_impl; + +bool wlr_texture_is_pixman(struct wlr_texture *texture) { + return texture->impl == &texture_impl; +} + +static struct wlr_pixman_texture *get_texture( + struct wlr_texture *wlr_texture) { + assert(wlr_texture_is_pixman(wlr_texture)); + struct wlr_pixman_texture *texture = wl_container_of(wlr_texture, texture, wlr_texture); + return texture; +} + +static void texture_destroy(struct wlr_texture *wlr_texture) { + struct wlr_pixman_texture *texture = get_texture(wlr_texture); + wl_list_remove(&texture->link); + pixman_image_unref(texture->image); + wlr_buffer_unlock(texture->buffer); + free(texture->data); + free(texture); +} + +static bool texture_read_pixels(struct wlr_texture *wlr_texture, + const struct wlr_texture_read_pixels_options *options) { + struct wlr_pixman_texture *texture = get_texture(wlr_texture); + + struct wlr_box src; + wlr_texture_read_pixels_options_get_src_box(options, wlr_texture, &src); + + pixman_format_code_t fmt = get_pixman_format_from_drm(options->format); + if (fmt == 0) { + wlr_log(WLR_ERROR, "Cannot read pixels: unsupported pixel format"); + return false; + } + + void *p = wlr_texture_read_pixel_options_get_data(options); + + pixman_image_t *dst = pixman_image_create_bits_no_clear(fmt, + src.width, src.height, p, options->stride); + + pixman_image_composite32(PIXMAN_OP_SRC, texture->image, NULL, dst, + src.x, src.y, 0, 0, 0, 0, src.width, src.height); + + pixman_image_unref(dst); + + return true; +} + +static uint32_t pixman_texture_preferred_read_format(struct wlr_texture *wlr_texture) { + struct wlr_pixman_texture *texture = get_texture(wlr_texture); + + pixman_format_code_t pixman_format = pixman_image_get_format(texture->image); + return get_drm_format_from_pixman(pixman_format); +} + +static const struct wlr_texture_impl texture_impl = { + .read_pixels = texture_read_pixels, + .preferred_read_format = pixman_texture_preferred_read_format, + .destroy = texture_destroy, +}; + +static void destroy_buffer(struct wlr_pixman_buffer *buffer) { + wl_list_remove(&buffer->link); + wl_list_remove(&buffer->buffer_destroy.link); + + pixman_image_unref(buffer->image); + + free(buffer); +} + +static void handle_destroy_buffer(struct wl_listener *listener, void *data) { + struct wlr_pixman_buffer *buffer = + wl_container_of(listener, buffer, buffer_destroy); + destroy_buffer(buffer); +} + +static struct wlr_pixman_buffer *create_buffer( + struct wlr_pixman_renderer *renderer, struct wlr_buffer *wlr_buffer) { + struct wlr_pixman_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + buffer->buffer = wlr_buffer; + buffer->renderer = renderer; + + void *data = NULL; + uint32_t drm_format; + size_t stride; + if (!wlr_buffer_begin_data_ptr_access(wlr_buffer, + WLR_BUFFER_DATA_PTR_ACCESS_READ | WLR_BUFFER_DATA_PTR_ACCESS_WRITE, + &data, &drm_format, &stride)) { + wlr_log(WLR_ERROR, "Failed to get buffer data"); + goto error_buffer; + } + wlr_buffer_end_data_ptr_access(wlr_buffer); + + pixman_format_code_t format = get_pixman_format_from_drm(drm_format); + if (format == 0) { + wlr_log(WLR_ERROR, "Unsupported pixman drm format 0x%"PRIX32, + drm_format); + goto error_buffer; + } + + buffer->image = pixman_image_create_bits(format, wlr_buffer->width, + wlr_buffer->height, data, stride); + if (!buffer->image) { + wlr_log(WLR_ERROR, "Failed to allocate pixman image"); + goto error_buffer; + } + + buffer->buffer_destroy.notify = handle_destroy_buffer; + wl_signal_add(&wlr_buffer->events.destroy, &buffer->buffer_destroy); + + wl_list_insert(&renderer->buffers, &buffer->link); + + wlr_log(WLR_DEBUG, "Created pixman buffer %dx%d", + wlr_buffer->width, wlr_buffer->height); + + return buffer; + +error_buffer: + free(buffer); + return NULL; +} + +static const uint32_t *pixman_get_shm_texture_formats( + struct wlr_renderer *wlr_renderer, size_t *len) { + return get_pixman_drm_formats(len); +} + +static const struct wlr_drm_format_set *pixman_get_render_formats( + struct wlr_renderer *wlr_renderer) { + struct wlr_pixman_renderer *renderer = get_renderer(wlr_renderer); + return &renderer->drm_formats; +} + +static struct wlr_pixman_texture *pixman_texture_create( + struct wlr_pixman_renderer *renderer, uint32_t drm_format, + uint32_t width, uint32_t height) { + struct wlr_pixman_texture *texture = calloc(1, sizeof(*texture)); + if (texture == NULL) { + wlr_log_errno(WLR_ERROR, "Failed to allocate pixman texture"); + return NULL; + } + + wlr_texture_init(&texture->wlr_texture, &renderer->wlr_renderer, + &texture_impl, width, height); + + texture->format_info = drm_get_pixel_format_info(drm_format); + if (!texture->format_info) { + wlr_log(WLR_ERROR, "Unsupported drm format 0x%"PRIX32, drm_format); + free(texture); + return NULL; + } + + texture->format = get_pixman_format_from_drm(drm_format); + if (texture->format == 0) { + wlr_log(WLR_ERROR, "Unsupported pixman drm format 0x%"PRIX32, + drm_format); + free(texture); + return NULL; + } + + wl_list_insert(&renderer->textures, &texture->link); + + return texture; +} + +static struct wlr_texture *pixman_texture_from_buffer( + struct wlr_renderer *wlr_renderer, struct wlr_buffer *buffer) { + struct wlr_pixman_renderer *renderer = get_renderer(wlr_renderer); + + void *data = NULL; + uint32_t drm_format; + size_t stride; + if (!wlr_buffer_begin_data_ptr_access(buffer, WLR_BUFFER_DATA_PTR_ACCESS_READ, + &data, &drm_format, &stride)) { + return NULL; + } + wlr_buffer_end_data_ptr_access(buffer); + + struct wlr_pixman_texture *texture = pixman_texture_create(renderer, + drm_format, buffer->width, buffer->height); + if (texture == NULL) { + return NULL; + } + + texture->image = pixman_image_create_bits_no_clear(texture->format, + buffer->width, buffer->height, data, stride); + if (!texture->image) { + wlr_log(WLR_ERROR, "Failed to create pixman image"); + wl_list_remove(&texture->link); + free(texture); + return NULL; + } + + texture->buffer = wlr_buffer_lock(buffer); + + return &texture->wlr_texture; +} + +static void pixman_destroy(struct wlr_renderer *wlr_renderer) { + struct wlr_pixman_renderer *renderer = get_renderer(wlr_renderer); + + struct wlr_pixman_buffer *buffer, *buffer_tmp; + wl_list_for_each_safe(buffer, buffer_tmp, &renderer->buffers, link) { + destroy_buffer(buffer); + } + + struct wlr_pixman_texture *tex, *tex_tmp; + wl_list_for_each_safe(tex, tex_tmp, &renderer->textures, link) { + wlr_texture_destroy(&tex->wlr_texture); + } + + wlr_drm_format_set_finish(&renderer->drm_formats); + + free(renderer); +} + +static uint32_t pixman_get_render_buffer_caps(struct wlr_renderer *renderer) { + return WLR_BUFFER_CAP_DATA_PTR; +} + +static struct wlr_render_pass *pixman_begin_buffer_pass(struct wlr_renderer *wlr_renderer, + struct wlr_buffer *wlr_buffer, const struct wlr_buffer_pass_options *options) { + struct wlr_pixman_renderer *renderer = get_renderer(wlr_renderer); + + struct wlr_pixman_buffer *buffer = get_buffer(renderer, wlr_buffer); + if (buffer == NULL) { + buffer = create_buffer(renderer, wlr_buffer); + } + if (buffer == NULL) { + return NULL; + } + + struct wlr_pixman_render_pass *pass = begin_pixman_render_pass(buffer); + if (pass == NULL) { + return NULL; + } + return &pass->base; +} + +static const struct wlr_renderer_impl renderer_impl = { + .get_shm_texture_formats = pixman_get_shm_texture_formats, + .get_render_formats = pixman_get_render_formats, + .texture_from_buffer = pixman_texture_from_buffer, + .destroy = pixman_destroy, + .get_render_buffer_caps = pixman_get_render_buffer_caps, + .begin_buffer_pass = pixman_begin_buffer_pass, +}; + +struct wlr_renderer *wlr_pixman_renderer_create(void) { + struct wlr_pixman_renderer *renderer = calloc(1, sizeof(*renderer)); + if (renderer == NULL) { + return NULL; + } + + wlr_log(WLR_INFO, "Creating pixman renderer"); + wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl); + wl_list_init(&renderer->buffers); + wl_list_init(&renderer->textures); + + size_t len = 0; + const uint32_t *formats = get_pixman_drm_formats(&len); + + for (size_t i = 0; i < len; ++i) { + wlr_drm_format_set_add(&renderer->drm_formats, formats[i], + DRM_FORMAT_MOD_INVALID); + wlr_drm_format_set_add(&renderer->drm_formats, formats[i], + DRM_FORMAT_MOD_LINEAR); + } + + return &renderer->wlr_renderer; +} + +pixman_image_t *wlr_pixman_renderer_get_buffer_image( + struct wlr_renderer *wlr_renderer, struct wlr_buffer *wlr_buffer) { + struct wlr_pixman_renderer *renderer = get_renderer(wlr_renderer); + struct wlr_pixman_buffer *buffer = get_buffer(renderer, wlr_buffer); + if (!buffer) { + buffer = create_buffer(renderer, wlr_buffer); + } + if (!buffer) { + return NULL; + } + return buffer->image; +} + +pixman_image_t *wlr_pixman_texture_get_image(struct wlr_texture *wlr_texture) { + struct wlr_pixman_texture *texture = get_texture(wlr_texture); + return texture->image; +} diff --git a/render/swapchain.c b/render/swapchain.c new file mode 100644 index 0000000..a50dae5 --- /dev/null +++ b/render/swapchain.c @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include "render/allocator/allocator.h" +#include "render/drm_format_set.h" + +static void swapchain_handle_allocator_destroy(struct wl_listener *listener, + void *data) { + struct wlr_swapchain *swapchain = + wl_container_of(listener, swapchain, allocator_destroy); + swapchain->allocator = NULL; + wl_list_remove(&swapchain->allocator_destroy.link); + wl_list_init(&swapchain->allocator_destroy.link); +} + +struct wlr_swapchain *wlr_swapchain_create( + struct wlr_allocator *alloc, int width, int height, + const struct wlr_drm_format *format) { + struct wlr_swapchain *swapchain = calloc(1, sizeof(*swapchain)); + if (swapchain == NULL) { + return NULL; + } + swapchain->allocator = alloc; + swapchain->width = width; + swapchain->height = height; + + if (!wlr_drm_format_copy(&swapchain->format, format)) { + free(swapchain); + return NULL; + } + + swapchain->allocator_destroy.notify = swapchain_handle_allocator_destroy; + wl_signal_add(&alloc->events.destroy, &swapchain->allocator_destroy); + + return swapchain; +} + +static void slot_reset(struct wlr_swapchain_slot *slot) { + if (slot->acquired) { + wl_list_remove(&slot->release.link); + } + wlr_buffer_drop(slot->buffer); + *slot = (struct wlr_swapchain_slot){0}; +} + +void wlr_swapchain_destroy(struct wlr_swapchain *swapchain) { + if (swapchain == NULL) { + return; + } + for (size_t i = 0; i < WLR_SWAPCHAIN_CAP; i++) { + slot_reset(&swapchain->slots[i]); + } + wl_list_remove(&swapchain->allocator_destroy.link); + wlr_drm_format_finish(&swapchain->format); + free(swapchain); +} + +static void slot_handle_release(struct wl_listener *listener, void *data) { + struct wlr_swapchain_slot *slot = + wl_container_of(listener, slot, release); + wl_list_remove(&slot->release.link); + slot->acquired = false; +} + +static struct wlr_buffer *slot_acquire(struct wlr_swapchain *swapchain, + struct wlr_swapchain_slot *slot, int *age) { + assert(!slot->acquired); + assert(slot->buffer != NULL); + + slot->acquired = true; + + slot->release.notify = slot_handle_release; + wl_signal_add(&slot->buffer->events.release, &slot->release); + + if (age != NULL) { + *age = slot->age; + } + + return wlr_buffer_lock(slot->buffer); +} + +struct wlr_buffer *wlr_swapchain_acquire(struct wlr_swapchain *swapchain, + int *age) { + struct wlr_swapchain_slot *free_slot = NULL; + for (size_t i = 0; i < WLR_SWAPCHAIN_CAP; i++) { + struct wlr_swapchain_slot *slot = &swapchain->slots[i]; + if (slot->acquired) { + continue; + } + if (slot->buffer != NULL) { + return slot_acquire(swapchain, slot, age); + } + free_slot = slot; + } + if (free_slot == NULL) { + wlr_log(WLR_ERROR, "No free output buffer slot"); + return NULL; + } + + if (swapchain->allocator == NULL) { + return NULL; + } + + wlr_log(WLR_DEBUG, "Allocating new swapchain buffer"); + free_slot->buffer = wlr_allocator_create_buffer(swapchain->allocator, + swapchain->width, swapchain->height, &swapchain->format); + if (free_slot->buffer == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate buffer"); + return NULL; + } + return slot_acquire(swapchain, free_slot, age); +} + +bool wlr_swapchain_has_buffer(struct wlr_swapchain *swapchain, + struct wlr_buffer *buffer) { + for (size_t i = 0; i < WLR_SWAPCHAIN_CAP; i++) { + struct wlr_swapchain_slot *slot = &swapchain->slots[i]; + if (slot->buffer == buffer) { + return true; + } + } + return false; +} + +void wlr_swapchain_set_buffer_submitted(struct wlr_swapchain *swapchain, + struct wlr_buffer *buffer) { + assert(buffer != NULL); + + if (!wlr_swapchain_has_buffer(swapchain, buffer)) { + return; + } + + // See the algorithm described in: + // https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_buffer_age.txt + for (size_t i = 0; i < WLR_SWAPCHAIN_CAP; i++) { + struct wlr_swapchain_slot *slot = &swapchain->slots[i]; + if (slot->buffer == buffer) { + slot->age = 1; + } else if (slot->age > 0) { + slot->age++; + } + } +} diff --git a/render/vulkan/meson.build b/render/vulkan/meson.build new file mode 100644 index 0000000..36a822f --- /dev/null +++ b/render/vulkan/meson.build @@ -0,0 +1,51 @@ +msg = [] +if 'vulkan' in renderers + msg += 'Install "@0@" or pass "-Dvulkan=disabled" to disable it.' +else + msg += 'Required for vulkan renderer support.' +endif + +dep_vulkan = dependency('vulkan', + version: '>=1.2.182', + required: 'vulkan' in renderers, + not_found_message: '\n'.join(msg).format('vulkan') +) +if not dep_vulkan.found() + subdir_done() +endif + +# Vulkan headers are installed separately from the loader (which ships the +# pkg-config file) +if not cc.check_header('vulkan/vulkan.h', dependencies: dep_vulkan) + if 'vulkan' in renderers + error('\n'.join(msg).format('vulkan-headers')) + else + subdir_done() + endif +endif + +glslang = find_program('glslang', 'glslangValidator', native: true, required: false) +if not glslang.found() + if 'vulkan' in renderers + error('\n'.join(msg).format('glslang')) + else + subdir_done() + endif +endif + +glslang_version_info = run_command(glslang, '--version', check: true).stdout() +glslang_version = glslang_version_info.split('\n')[0].split(':')[-1] + +wlr_files += files( + 'pass.c', + 'renderer.c', + 'texture.c', + 'vulkan.c', + 'util.c', + 'pixel_format.c', +) + +wlr_deps += dep_vulkan +features += { 'vulkan-renderer': true } + +subdir('shaders') diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c new file mode 100644 index 0000000..e981a3e --- /dev/null +++ b/render/vulkan/pass.c @@ -0,0 +1,712 @@ +#include +#include +#include +#include + +#include "render/vulkan.h" +#include "types/wlr_matrix.h" + +static const struct wlr_render_pass_impl render_pass_impl; + +static struct wlr_vk_render_pass *get_render_pass(struct wlr_render_pass *wlr_pass) { + assert(wlr_pass->impl == &render_pass_impl); + struct wlr_vk_render_pass *pass = wl_container_of(wlr_pass, pass, base); + return pass; +} + +static void bind_pipeline(struct wlr_vk_render_pass *pass, VkPipeline pipeline) { + if (pipeline == pass->bound_pipeline) { + return; + } + + vkCmdBindPipeline(pass->command_buffer->vk, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + pass->bound_pipeline = pipeline; +} + +static void get_clip_region(struct wlr_vk_render_pass *pass, + const pixman_region32_t *in, pixman_region32_t *out) { + if (in != NULL) { + pixman_region32_init(out); + pixman_region32_copy(out, in); + } else { + struct wlr_buffer *buffer = pass->render_buffer->wlr_buffer; + pixman_region32_init_rect(out, 0, 0, buffer->width, buffer->height); + } +} + +static void convert_pixman_box_to_vk_rect(const pixman_box32_t *box, VkRect2D *rect) { + *rect = (VkRect2D){ + .offset = { .x = box->x1, .y = box->y1 }, + .extent = { .width = box->x2 - box->x1, .height = box->y2 - box->y1 }, + }; +} + +static float color_to_linear(float non_linear) { + // See https://www.w3.org/Graphics/Color/srgb + return (non_linear > 0.04045) ? + pow((non_linear + 0.055) / 1.055, 2.4) : + non_linear / 12.92; +} + +static void mat3_to_mat4(const float mat3[9], float mat4[4][4]) { + memset(mat4, 0, sizeof(float) * 16); + mat4[0][0] = mat3[0]; + mat4[0][1] = mat3[1]; + mat4[0][3] = mat3[2]; + + mat4[1][0] = mat3[3]; + mat4[1][1] = mat3[4]; + mat4[1][3] = mat3[5]; + + mat4[2][2] = 1.f; + mat4[3][3] = 1.f; +} + +static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { + struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass); + struct wlr_vk_renderer *renderer = pass->renderer; + struct wlr_vk_command_buffer *render_cb = pass->command_buffer; + struct wlr_vk_render_buffer *render_buffer = pass->render_buffer; + struct wlr_vk_command_buffer *stage_cb = NULL; + VkSemaphoreSubmitInfoKHR *render_wait = NULL; + bool device_lost = false; + + if (pass->failed) { + goto error; + } + + if (vulkan_record_stage_cb(renderer) == VK_NULL_HANDLE) { + goto error; + } + + stage_cb = renderer->stage.cb; + assert(stage_cb != NULL); + renderer->stage.cb = NULL; + + if (render_buffer->blend_image) { + // Apply output shader to map blend image to actual output image + vkCmdNextSubpass(render_cb->vk, VK_SUBPASS_CONTENTS_INLINE); + + int width = pass->render_buffer->wlr_buffer->width; + int height = pass->render_buffer->wlr_buffer->height; + + float final_matrix[9] = { + width, 0, -1, + 0, height, -1, + 0, 0, 0, + }; + struct wlr_vk_vert_pcr_data vert_pcr_data = { + .uv_off = { 0, 0 }, + .uv_size = { 1, 1 }, + }; + mat3_to_mat4(final_matrix, vert_pcr_data.mat4); + + bind_pipeline(pass, render_buffer->render_setup->output_pipe); + vkCmdPushConstants(render_cb->vk, renderer->output_pipe_layout, + VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data); + vkCmdBindDescriptorSets(render_cb->vk, + VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->output_pipe_layout, + 0, 1, &render_buffer->blend_descriptor_set, 0, NULL); + + const pixman_region32_t *clip = rect_union_evaluate(&pass->updated_region); + int clip_rects_len; + const pixman_box32_t *clip_rects = pixman_region32_rectangles( + clip, &clip_rects_len); + for (int i = 0; i < clip_rects_len; i++) { + VkRect2D rect; + convert_pixman_box_to_vk_rect(&clip_rects[i], &rect); + vkCmdSetScissor(render_cb->vk, 0, 1, &rect); + vkCmdDraw(render_cb->vk, 4, 1, 0, 0); + } + } + + vkCmdEndRenderPass(render_cb->vk); + + // insert acquire and release barriers for dmabuf-images + uint32_t barrier_count = wl_list_length(&renderer->foreign_textures) + 1; + render_wait = calloc(barrier_count * WLR_DMABUF_MAX_PLANES, sizeof(*render_wait)); + if (render_wait == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error; + } + + VkImageMemoryBarrier *acquire_barriers = calloc(barrier_count, sizeof(*acquire_barriers)); + VkImageMemoryBarrier *release_barriers = calloc(barrier_count, sizeof(*release_barriers)); + if (acquire_barriers == NULL || release_barriers == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + free(acquire_barriers); + free(release_barriers); + goto error; + } + + struct wlr_vk_texture *texture, *tmp_tex; + size_t idx = 0; + uint32_t render_wait_len = 0; + wl_list_for_each_safe(texture, tmp_tex, &renderer->foreign_textures, foreign_link) { + VkImageLayout src_layout = VK_IMAGE_LAYOUT_GENERAL; + if (!texture->transitioned) { + src_layout = VK_IMAGE_LAYOUT_UNDEFINED; + texture->transitioned = true; + } + + // acquire + acquire_barriers[idx] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, + .dstQueueFamilyIndex = renderer->dev->queue_family, + .image = texture->image, + .oldLayout = src_layout, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .srcAccessMask = 0, // ignored anyways + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .subresourceRange.layerCount = 1, + .subresourceRange.levelCount = 1, + }; + + // release + release_barriers[idx] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcQueueFamilyIndex = renderer->dev->queue_family, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, + .image = texture->image, + .oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, + .dstAccessMask = 0, // ignored anyways + .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .subresourceRange.layerCount = 1, + .subresourceRange.levelCount = 1, + }; + + ++idx; + + if (!vulkan_sync_foreign_texture(texture)) { + wlr_log(WLR_ERROR, "Failed to wait for foreign texture DMA-BUF fence"); + } else { + for (size_t i = 0; i < WLR_DMABUF_MAX_PLANES; i++) { + if (texture->foreign_semaphores[i] != VK_NULL_HANDLE) { + assert(render_wait_len < barrier_count * WLR_DMABUF_MAX_PLANES); + render_wait[render_wait_len++] = (VkSemaphoreSubmitInfoKHR){ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR, + .semaphore = texture->foreign_semaphores[i], + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT_KHR, + }; + } + } + } + + wl_list_remove(&texture->foreign_link); + texture->owned = false; + } + + // also add acquire/release barriers for the current render buffer + VkImageLayout src_layout = VK_IMAGE_LAYOUT_GENERAL; + if (!render_buffer->transitioned) { + src_layout = VK_IMAGE_LAYOUT_PREINITIALIZED; + render_buffer->transitioned = true; + } + + if (render_buffer->blend_image) { + // The render pass changes the blend image layout from + // color attachment to read only, so on each frame, before + // the render pass starts, we change it back + VkImageLayout blend_src_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + if (!render_buffer->blend_transitioned) { + blend_src_layout = VK_IMAGE_LAYOUT_UNDEFINED; + render_buffer->blend_transitioned = true; + } + + VkImageMemoryBarrier blend_acq_barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = render_buffer->blend_image, + .oldLayout = blend_src_layout, + .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1, + .levelCount = 1, + }, + }; + vkCmdPipelineBarrier(stage_cb->vk, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, 0, NULL, 0, NULL, 1, &blend_acq_barrier); + } + + // acquire render buffer before rendering + acquire_barriers[idx] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, + .dstQueueFamilyIndex = renderer->dev->queue_family, + .image = render_buffer->image, + .oldLayout = src_layout, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcAccessMask = 0, // ignored anyways + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .subresourceRange.layerCount = 1, + .subresourceRange.levelCount = 1, + }; + + // release render buffer after rendering + release_barriers[idx] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcQueueFamilyIndex = renderer->dev->queue_family, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, + .image = render_buffer->image, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = 0, // ignored anyways + .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .subresourceRange.layerCount = 1, + .subresourceRange.levelCount = 1, + }; + + ++idx; + + vkCmdPipelineBarrier(stage_cb->vk, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, 0, NULL, 0, NULL, barrier_count, acquire_barriers); + + vkCmdPipelineBarrier(render_cb->vk, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, + barrier_count, release_barriers); + + free(acquire_barriers); + free(release_barriers); + + // No semaphores needed here. + // We don't need a semaphore from the stage/transfer submission + // to the render submissions since they are on the same queue + // and we have a renderpass dependency for that. + uint64_t stage_timeline_point = vulkan_end_command_buffer(stage_cb, renderer); + if (stage_timeline_point == 0) { + goto error; + } + + VkCommandBufferSubmitInfoKHR stage_cb_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO_KHR, + .commandBuffer = stage_cb->vk, + }; + VkSemaphoreSubmitInfoKHR stage_signal = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR, + .semaphore = renderer->timeline_semaphore, + .value = stage_timeline_point, + }; + VkSubmitInfo2KHR stage_submit = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2_KHR, + .commandBufferInfoCount = 1, + .pCommandBufferInfos = &stage_cb_info, + .signalSemaphoreInfoCount = 1, + .pSignalSemaphoreInfos = &stage_signal, + }; + + VkSemaphoreSubmitInfoKHR stage_wait; + if (renderer->stage.last_timeline_point > 0) { + stage_wait = (VkSemaphoreSubmitInfoKHR){ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR, + .semaphore = renderer->timeline_semaphore, + .value = renderer->stage.last_timeline_point, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT_KHR, + }; + + stage_submit.waitSemaphoreInfoCount = 1; + stage_submit.pWaitSemaphoreInfos = &stage_wait; + } + + renderer->stage.last_timeline_point = stage_timeline_point; + + uint64_t render_timeline_point = vulkan_end_command_buffer(render_cb, renderer); + if (render_timeline_point == 0) { + goto error; + } + + uint32_t render_signal_len = 1; + VkSemaphoreSubmitInfoKHR render_signal[2] = {0}; + render_signal[0] = (VkSemaphoreSubmitInfoKHR){ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR, + .semaphore = renderer->timeline_semaphore, + .value = render_timeline_point, + }; + if (renderer->dev->implicit_sync_interop) { + if (render_cb->binary_semaphore == VK_NULL_HANDLE) { + VkExportSemaphoreCreateInfo export_info = { + .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + }; + VkSemaphoreCreateInfo semaphore_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = &export_info, + }; + VkResult res = vkCreateSemaphore(renderer->dev->dev, &semaphore_info, + NULL, &render_cb->binary_semaphore); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateSemaphore", res); + goto error; + } + } + + render_signal[render_signal_len++] = (VkSemaphoreSubmitInfoKHR){ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR, + .semaphore = render_cb->binary_semaphore, + }; + } + + VkCommandBufferSubmitInfoKHR render_cb_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO_KHR, + .commandBuffer = render_cb->vk, + }; + VkSubmitInfo2KHR render_submit = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2_KHR, + .waitSemaphoreInfoCount = render_wait_len, + .pWaitSemaphoreInfos = render_wait, + .commandBufferInfoCount = 1, + .pCommandBufferInfos = &render_cb_info, + .signalSemaphoreInfoCount = render_signal_len, + .pSignalSemaphoreInfos = render_signal, + }; + + VkSubmitInfo2KHR submit_info[] = { stage_submit, render_submit }; + VkResult res = renderer->dev->api.vkQueueSubmit2KHR(renderer->dev->queue, 2, submit_info, VK_NULL_HANDLE); + + if (res != VK_SUCCESS) { + device_lost = res == VK_ERROR_DEVICE_LOST; + wlr_vk_error("vkQueueSubmit", res); + goto error; + } + + free(render_wait); + + struct wlr_vk_shared_buffer *stage_buf, *stage_buf_tmp; + wl_list_for_each_safe(stage_buf, stage_buf_tmp, &renderer->stage.buffers, link) { + if (stage_buf->allocs.size == 0) { + continue; + } + wl_list_remove(&stage_buf->link); + wl_list_insert(&stage_cb->stage_buffers, &stage_buf->link); + } + + if (!vulkan_sync_render_buffer(renderer, render_buffer, render_cb)) { + wlr_log(WLR_ERROR, "Failed to sync render buffer"); + } + + wlr_buffer_unlock(render_buffer->wlr_buffer); + rect_union_finish(&pass->updated_region); + free(pass); + return true; + +error: + free(render_wait); + vulkan_reset_command_buffer(stage_cb); + vulkan_reset_command_buffer(render_cb); + wlr_buffer_unlock(render_buffer->wlr_buffer); + rect_union_finish(&pass->updated_region); + free(pass); + + if (device_lost) { + wl_signal_emit_mutable(&renderer->wlr_renderer.events.lost, NULL); + } + + return false; +} + +static void render_pass_mark_box_updated(struct wlr_vk_render_pass *pass, + const struct wlr_box *box) { + if (!pass->render_buffer->blend_image) { + return; + } + + pixman_box32_t pixman_box = { + .x1 = box->x, + .x2 = box->x + box->width, + .y1 = box->y, + .y2 = box->y + box->height, + }; + rect_union_add(&pass->updated_region, pixman_box); +} + +static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, + const struct wlr_render_rect_options *options) { + struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass); + VkCommandBuffer cb = pass->command_buffer->vk; + + // Input color values are given in sRGB space, shader expects + // them in linear space. The shader does all computation in linear + // space and expects in inputs in linear space since it outputs + // colors in linear space as well (and vulkan then automatically + // does the conversion for out sRGB render targets). + float linear_color[] = { + color_to_linear(options->color.r), + color_to_linear(options->color.g), + color_to_linear(options->color.b), + options->color.a, // no conversion for alpha + }; + + pixman_region32_t clip; + get_clip_region(pass, options->clip, &clip); + + int clip_rects_len; + const pixman_box32_t *clip_rects = pixman_region32_rectangles(&clip, &clip_rects_len); + // Record regions possibly updated for use in second subpass + for (int i = 0; i < clip_rects_len; i++) { + struct wlr_box clip_box = { + .x = clip_rects[i].x1, + .y = clip_rects[i].y1, + .width = clip_rects[i].x2 - clip_rects[i].x1, + .height = clip_rects[i].y2 - clip_rects[i].y1, + }; + struct wlr_box intersection; + if (!wlr_box_intersection(&intersection, &options->box, &clip_box)) { + continue; + } + render_pass_mark_box_updated(pass, &intersection); + } + + struct wlr_box box; + wlr_render_rect_options_get_box(options, pass->render_buffer->wlr_buffer, &box); + + switch (options->blend_mode) { + case WLR_RENDER_BLEND_MODE_PREMULTIPLIED:; + float proj[9], matrix[9]; + wlr_matrix_identity(proj); + wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0, proj); + wlr_matrix_multiply(matrix, pass->projection, matrix); + + struct wlr_vk_pipeline *pipe = setup_get_or_create_pipeline( + pass->render_buffer->render_setup, + &(struct wlr_vk_pipeline_key) { + .source = WLR_VK_SHADER_SOURCE_SINGLE_COLOR, + .layout = { .ycbcr_format = NULL }, + }); + if (!pipe) { + pass->failed = true; + break; + } + + struct wlr_vk_vert_pcr_data vert_pcr_data = { + .uv_off = { 0, 0 }, + .uv_size = { 1, 1 }, + }; + mat3_to_mat4(matrix, vert_pcr_data.mat4); + + bind_pipeline(pass, pipe->vk); + vkCmdPushConstants(cb, pipe->layout->vk, + VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data); + vkCmdPushConstants(cb, pipe->layout->vk, + VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(vert_pcr_data), sizeof(float) * 4, + linear_color); + + for (int i = 0; i < clip_rects_len; i++) { + VkRect2D rect; + convert_pixman_box_to_vk_rect(&clip_rects[i], &rect); + vkCmdSetScissor(cb, 0, 1, &rect); + vkCmdDraw(cb, 4, 1, 0, 0); + } + break; + case WLR_RENDER_BLEND_MODE_NONE:; + VkClearAttachment clear_att = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .colorAttachment = 0, + .clearValue.color.float32 = { + linear_color[0], + linear_color[1], + linear_color[2], + linear_color[3], + }, + }; + VkClearRect clear_rect = { + .rect = { + .offset = { box.x, box.y }, + .extent = { box.width, box.height }, + }, + .layerCount = 1, + }; + for (int i = 0; i < clip_rects_len; i++) { + VkRect2D rect; + convert_pixman_box_to_vk_rect(&clip_rects[i], &rect); + vkCmdSetScissor(cb, 0, 1, &rect); + vkCmdClearAttachments(cb, 1, &clear_att, 1, &clear_rect); + } + break; + } + + pixman_region32_fini(&clip); +} + +static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, + const struct wlr_render_texture_options *options) { + struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass); + struct wlr_vk_renderer *renderer = pass->renderer; + struct wlr_vk_render_buffer *render_buffer = pass->render_buffer; + VkCommandBuffer cb = pass->command_buffer->vk; + + struct wlr_vk_texture *texture = vulkan_get_texture(options->texture); + assert(texture->renderer == renderer); + + if (texture->dmabuf_imported && !texture->owned) { + // Store this texture in the list of textures that need to be + // acquired before rendering and released after rendering. + // We don't do it here immediately since barriers inside + // a renderpass are suboptimal (would require additional renderpass + // dependency and potentially multiple barriers) and it's + // better to issue one barrier for all used textures anyways. + texture->owned = true; + assert(texture->foreign_link.prev == NULL); + assert(texture->foreign_link.next == NULL); + wl_list_insert(&renderer->foreign_textures, &texture->foreign_link); + } + + struct wlr_fbox src_box; + wlr_render_texture_options_get_src_box(options, &src_box); + struct wlr_box dst_box; + wlr_render_texture_options_get_dst_box(options, &dst_box); + float alpha = wlr_render_texture_options_get_alpha(options); + + pixman_region32_t clip; + get_clip_region(pass, options->clip, &clip); + + float proj[9], matrix[9]; + wlr_matrix_identity(proj); + wlr_matrix_project_box(matrix, &dst_box, options->transform, 0, proj); + wlr_matrix_multiply(matrix, pass->projection, matrix); + + struct wlr_vk_vert_pcr_data vert_pcr_data = { + .uv_off = { + src_box.x / options->texture->width, + src_box.y / options->texture->height, + }, + .uv_size = { + src_box.width / options->texture->width, + src_box.height / options->texture->height, + }, + }; + mat3_to_mat4(matrix, vert_pcr_data.mat4); + + struct wlr_vk_pipeline *pipe = setup_get_or_create_pipeline( + render_buffer->render_setup, + &(struct wlr_vk_pipeline_key) { + .source = WLR_VK_SHADER_SOURCE_TEXTURE, + .layout = { + .ycbcr_format = texture->format->is_ycbcr ? texture->format : NULL, + .filter_mode = options->filter_mode, + }, + .texture_transform = texture->transform, + .blend_mode = !texture->has_alpha && alpha == 1.0 ? + WLR_RENDER_BLEND_MODE_NONE : options->blend_mode, + }); + if (!pipe) { + pass->failed = true; + return; + } + + struct wlr_vk_texture_view *view = + vulkan_texture_get_or_create_view(texture, pipe->layout); + if (!view) { + pass->failed = true; + return; + } + + bind_pipeline(pass, pipe->vk); + + vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, + pipe->layout->vk, 0, 1, &view->ds, 0, NULL); + + vkCmdPushConstants(cb, pipe->layout->vk, + VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data); + vkCmdPushConstants(cb, pipe->layout->vk, + VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(vert_pcr_data), sizeof(float), + &alpha); + + int clip_rects_len; + const pixman_box32_t *clip_rects = pixman_region32_rectangles(&clip, &clip_rects_len); + for (int i = 0; i < clip_rects_len; i++) { + VkRect2D rect; + convert_pixman_box_to_vk_rect(&clip_rects[i], &rect); + vkCmdSetScissor(cb, 0, 1, &rect); + vkCmdDraw(cb, 4, 1, 0, 0); + + struct wlr_box clip_box = { + .x = clip_rects[i].x1, + .y = clip_rects[i].y1, + .width = clip_rects[i].x2 - clip_rects[i].x1, + .height = clip_rects[i].y2 - clip_rects[i].y1, + }; + struct wlr_box intersection; + if (!wlr_box_intersection(&intersection, &dst_box, &clip_box)) { + continue; + } + render_pass_mark_box_updated(pass, &intersection); + } + + texture->last_used_cb = pass->command_buffer; +} + +static const struct wlr_render_pass_impl render_pass_impl = { + .submit = render_pass_submit, + .add_rect = render_pass_add_rect, + .add_texture = render_pass_add_texture, +}; + +struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *renderer, + struct wlr_vk_render_buffer *buffer) { + struct wlr_vk_render_pass *pass = calloc(1, sizeof(*pass)); + if (pass == NULL) { + return NULL; + } + wlr_render_pass_init(&pass->base, &render_pass_impl); + pass->renderer = renderer; + + rect_union_init(&pass->updated_region); + + struct wlr_vk_command_buffer *cb = vulkan_acquire_command_buffer(renderer); + if (cb == NULL) { + free(pass); + return NULL; + } + + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + }; + VkResult res = vkBeginCommandBuffer(cb->vk, &begin_info); + if (res != VK_SUCCESS) { + wlr_vk_error("vkBeginCommandBuffer", res); + vulkan_reset_command_buffer(cb); + free(pass); + return NULL; + } + + int width = buffer->wlr_buffer->width; + int height = buffer->wlr_buffer->height; + VkRect2D rect = { .extent = { width, height } }; + + VkRenderPassBeginInfo rp_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .renderArea = rect, + .renderPass = buffer->render_setup->render_pass, + .framebuffer = buffer->framebuffer, + .clearValueCount = 0, + }; + vkCmdBeginRenderPass(cb->vk, &rp_info, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdSetViewport(cb->vk, 0, 1, &(VkViewport){ + .width = width, + .height = height, + .maxDepth = 1, + }); + + // matrix_projection() assumes a GL coordinate system so we need + // to pass WL_OUTPUT_TRANSFORM_FLIPPED_180 to adjust it for vulkan. + matrix_projection(pass->projection, width, height, WL_OUTPUT_TRANSFORM_FLIPPED_180); + + wlr_buffer_lock(buffer->wlr_buffer); + pass->render_buffer = buffer; + pass->command_buffer = cb; + return pass; +} diff --git a/render/vulkan/pixel_format.c b/render/vulkan/pixel_format.c new file mode 100644 index 0000000..f55691e --- /dev/null +++ b/render/vulkan/pixel_format.c @@ -0,0 +1,591 @@ +#include +#include +#include +#include +#include +#include +#include "render/pixel_format.h" +#include "render/vulkan.h" + +static const struct wlr_vk_format formats[] = { + // Vulkan non-packed 8-bits-per-channel formats have an inverted channel + // order compared to the DRM formats, because DRM format channel order + // is little-endian while Vulkan format channel order is in memory byte + // order. + { + .drm = DRM_FORMAT_R8, + .vk = VK_FORMAT_R8_UNORM, + .vk_srgb = VK_FORMAT_R8_SRGB, + }, + { + .drm = DRM_FORMAT_GR88, + .vk = VK_FORMAT_R8G8_UNORM, + .vk_srgb = VK_FORMAT_R8G8_SRGB, + }, + { + .drm = DRM_FORMAT_RGB888, + .vk = VK_FORMAT_B8G8R8_UNORM, + .vk_srgb = VK_FORMAT_B8G8R8_SRGB, + }, + { + .drm = DRM_FORMAT_BGR888, + .vk = VK_FORMAT_R8G8B8_UNORM, + .vk_srgb = VK_FORMAT_R8G8B8_SRGB, + }, + { + .drm = DRM_FORMAT_XRGB8888, + .vk = VK_FORMAT_B8G8R8A8_UNORM, + .vk_srgb = VK_FORMAT_B8G8R8A8_SRGB, + }, + { + .drm = DRM_FORMAT_XBGR8888, + .vk = VK_FORMAT_R8G8B8A8_UNORM, + .vk_srgb = VK_FORMAT_R8G8B8A8_SRGB, + }, + // The Vulkan _SRGB formats correspond to unpremultiplied alpha, but + // the Wayland protocol specifies premultiplied alpha on electrical values + { + .drm = DRM_FORMAT_ARGB8888, + .vk = VK_FORMAT_B8G8R8A8_UNORM, + }, + { + .drm = DRM_FORMAT_ABGR8888, + .vk = VK_FORMAT_R8G8B8A8_UNORM, + }, + // Vulkan packed formats have the same channel order as DRM formats on + // little endian systems. +#if WLR_LITTLE_ENDIAN + { + .drm = DRM_FORMAT_RGBA4444, + .vk = VK_FORMAT_R4G4B4A4_UNORM_PACK16, + }, + { + .drm = DRM_FORMAT_RGBX4444, + .vk = VK_FORMAT_R4G4B4A4_UNORM_PACK16, + }, + { + .drm = DRM_FORMAT_BGRA4444, + .vk = VK_FORMAT_B4G4R4A4_UNORM_PACK16, + }, + { + .drm = DRM_FORMAT_BGRX4444, + .vk = VK_FORMAT_B4G4R4A4_UNORM_PACK16, + }, + { + .drm = DRM_FORMAT_RGB565, + .vk = VK_FORMAT_R5G6B5_UNORM_PACK16, + }, + { + .drm = DRM_FORMAT_BGR565, + .vk = VK_FORMAT_B5G6R5_UNORM_PACK16, + }, + { + .drm = DRM_FORMAT_RGBA5551, + .vk = VK_FORMAT_R5G5B5A1_UNORM_PACK16, + }, + { + .drm = DRM_FORMAT_RGBX5551, + .vk = VK_FORMAT_R5G5B5A1_UNORM_PACK16, + }, + { + .drm = DRM_FORMAT_BGRA5551, + .vk = VK_FORMAT_B5G5R5A1_UNORM_PACK16, + }, + { + .drm = DRM_FORMAT_BGRX5551, + .vk = VK_FORMAT_B5G5R5A1_UNORM_PACK16, + }, + { + .drm = DRM_FORMAT_ARGB1555, + .vk = VK_FORMAT_A1R5G5B5_UNORM_PACK16, + }, + { + .drm = DRM_FORMAT_XRGB1555, + .vk = VK_FORMAT_A1R5G5B5_UNORM_PACK16, + }, + { + .drm = DRM_FORMAT_ARGB2101010, + .vk = VK_FORMAT_A2R10G10B10_UNORM_PACK32, + }, + { + .drm = DRM_FORMAT_XRGB2101010, + .vk = VK_FORMAT_A2R10G10B10_UNORM_PACK32, + }, + { + .drm = DRM_FORMAT_ABGR2101010, + .vk = VK_FORMAT_A2B10G10R10_UNORM_PACK32, + }, + { + .drm = DRM_FORMAT_XBGR2101010, + .vk = VK_FORMAT_A2B10G10R10_UNORM_PACK32, + }, +#endif + + // Vulkan 16-bits-per-channel formats have an inverted channel order + // compared to DRM formats, just like the 8-bits-per-channel ones. + // On little endian systems the memory representation of each channel + // matches the DRM formats'. +#if WLR_LITTLE_ENDIAN + { + .drm = DRM_FORMAT_ABGR16161616, + .vk = VK_FORMAT_R16G16B16A16_UNORM, + }, + { + .drm = DRM_FORMAT_XBGR16161616, + .vk = VK_FORMAT_R16G16B16A16_UNORM, + }, + { + .drm = DRM_FORMAT_ABGR16161616F, + .vk = VK_FORMAT_R16G16B16A16_SFLOAT, + }, + { + .drm = DRM_FORMAT_XBGR16161616F, + .vk = VK_FORMAT_R16G16B16A16_SFLOAT, + }, +#endif + + // YCbCr formats + // R -> V, G -> Y, B -> U + // 420 -> 2x2 subsampled, 422 -> 2x1 subsampled, 444 -> non-subsampled + { + .drm = DRM_FORMAT_UYVY, + .vk = VK_FORMAT_B8G8R8G8_422_UNORM, + .is_ycbcr = true, + }, + { + .drm = DRM_FORMAT_YUYV, + .vk = VK_FORMAT_G8B8G8R8_422_UNORM, + .is_ycbcr = true, + }, + { + .drm = DRM_FORMAT_NV12, + .vk = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + .is_ycbcr = true, + }, + { + .drm = DRM_FORMAT_NV16, + .vk = VK_FORMAT_G8_B8R8_2PLANE_422_UNORM, + .is_ycbcr = true, + }, + { + .drm = DRM_FORMAT_YUV420, + .vk = VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, + .is_ycbcr = true, + }, + { + .drm = DRM_FORMAT_YUV422, + .vk = VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM, + .is_ycbcr = true, + }, + { + .drm = DRM_FORMAT_YUV444, + .vk = VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM, + .is_ycbcr = true, + }, + // 3PACK16 formats split the memory in three 16-bit words, so they have an + // inverted channel order compared to DRM formats. +#if WLR_LITTLE_ENDIAN + { + .drm = DRM_FORMAT_P010, + .vk = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, + .is_ycbcr = true, + }, + { + .drm = DRM_FORMAT_P210, + .vk = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, + .is_ycbcr = true, + }, + { + .drm = DRM_FORMAT_P012, + .vk = VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, + .is_ycbcr = true, + }, + { + .drm = DRM_FORMAT_P016, + .vk = VK_FORMAT_G16_B16R16_2PLANE_420_UNORM, + .is_ycbcr = true, + }, + { + .drm = DRM_FORMAT_Q410, + .vk = VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, + .is_ycbcr = true, + }, +#endif + // TODO: add DRM_FORMAT_NV24/VK_FORMAT_G8_B8R8_2PLANE_444_UNORM (requires + // Vulkan 1.3 or VK_EXT_ycbcr_2plane_444_formats) +}; + +const struct wlr_vk_format *vulkan_get_format_list(size_t *len) { + *len = sizeof(formats) / sizeof(formats[0]); + return formats; +} + +const struct wlr_vk_format *vulkan_get_format_from_drm(uint32_t drm_format) { + for (unsigned i = 0; i < sizeof(formats) / sizeof(formats[0]); ++i) { + if (formats[i].drm == drm_format) { + return &formats[i]; + } + } + return NULL; +} + +const VkImageUsageFlags vulkan_render_usage = + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +const VkImageUsageFlags vulkan_shm_tex_usage = + VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT; +const VkImageUsageFlags vulkan_dma_tex_usage = + VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + +static const VkFormatFeatureFlags render_features = + VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | + VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT; +static const VkFormatFeatureFlags shm_tex_features = + VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | + VK_FORMAT_FEATURE_TRANSFER_DST_BIT | + VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | + // NOTE: we don't strictly require this, we could create a NEAREST + // sampler for formats that need it, in case this ever makes problems. + VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; +static const VkFormatFeatureFlags dma_tex_features = + VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | + // NOTE: we don't strictly require this, we could create a NEAREST + // sampler for formats that need it, in case this ever makes problems. + VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; +static const VkFormatFeatureFlags ycbcr_tex_features = + VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT | + VK_FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT; + +// vk_format_variant should be set to 0=VK_FORMAT_UNDEFINED when not used +static bool query_modifier_usage_support(struct wlr_vk_device *dev, VkFormat vk_format, + VkFormat vk_format_variant, VkImageUsageFlags usage, + const VkDrmFormatModifierPropertiesEXT *m, + struct wlr_vk_format_modifier_props *out, const char **errmsg) { + VkResult res; + *errmsg = NULL; + + VkFormat view_formats[2] = { + vk_format, + vk_format_variant, + }; + VkImageFormatListCreateInfoKHR listi = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR, + .pViewFormats = view_formats, + .viewFormatCount = vk_format_variant ? 2 : 1, + }; + VkPhysicalDeviceImageDrmFormatModifierInfoEXT modi = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT, + .drmFormatModifier = m->drmFormatModifier, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .pNext = &listi, + }; + VkPhysicalDeviceExternalImageFormatInfo efmti = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + .pNext = &modi, + }; + VkPhysicalDeviceImageFormatInfo2 fmti = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, + .type = VK_IMAGE_TYPE_2D, + .format = vk_format, + .usage = usage, + .flags = vk_format_variant ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0, + .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, + .pNext = &efmti, + }; + + VkExternalImageFormatProperties efmtp = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES, + }; + VkImageFormatProperties2 ifmtp = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, + .pNext = &efmtp, + }; + const VkExternalMemoryProperties *emp = &efmtp.externalMemoryProperties; + + res = vkGetPhysicalDeviceImageFormatProperties2(dev->phdev, &fmti, &ifmtp); + if (res != VK_SUCCESS) { + if (res == VK_ERROR_FORMAT_NOT_SUPPORTED) { + *errmsg = "unsupported format"; + } else { + wlr_vk_error("vkGetPhysicalDeviceImageFormatProperties2", res); + *errmsg = "failed to get format properties"; + } + return false; + } else if (!(emp->externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT)) { + *errmsg = "import not supported"; + return false; + } + + VkExtent3D me = ifmtp.imageFormatProperties.maxExtent; + *out = (struct wlr_vk_format_modifier_props){ + .props = *m, + .max_extent.width = me.width, + .max_extent.height = me.height, + }; + return true; +} + +static bool query_shm_support(struct wlr_vk_device *dev, VkFormat vk_format, + VkFormat vk_format_variant, VkImageFormatProperties *out, + const char **errmsg) { + VkResult res; + *errmsg = NULL; + + VkFormat view_formats[2] = { + vk_format, + vk_format_variant, + }; + VkImageFormatListCreateInfoKHR listi = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR, + .pViewFormats = view_formats, + .viewFormatCount = vk_format_variant ? 2 : 1, + .pNext = NULL, + }; + VkPhysicalDeviceImageFormatInfo2 fmti = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, + .type = VK_IMAGE_TYPE_2D, + .format = vk_format, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = vulkan_shm_tex_usage, + .flags = vk_format_variant ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0, + .pNext = &listi, + }; + VkImageFormatProperties2 ifmtp = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, + }; + + res = vkGetPhysicalDeviceImageFormatProperties2(dev->phdev, &fmti, &ifmtp); + if (res != VK_SUCCESS) { + if (res == VK_ERROR_FORMAT_NOT_SUPPORTED) { + *errmsg = "unsupported format"; + } else { + wlr_vk_error("vkGetPhysicalDeviceImageFormatProperties2", res); + *errmsg = "failed to get format properties"; + } + return false; + } + + *out = ifmtp.imageFormatProperties; + return true; +} + +static bool query_modifier_support(struct wlr_vk_device *dev, + struct wlr_vk_format_props *props, size_t modifier_count) { + VkDrmFormatModifierPropertiesListEXT modp = { + .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT, + .drmFormatModifierCount = modifier_count, + }; + VkFormatProperties2 fmtp = { + .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2, + .pNext = &modp, + }; + + modp.pDrmFormatModifierProperties = + calloc(modifier_count, sizeof(*modp.pDrmFormatModifierProperties)); + if (!modp.pDrmFormatModifierProperties) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return false; + } + + vkGetPhysicalDeviceFormatProperties2(dev->phdev, props->format.vk, &fmtp); + + props->dmabuf.render_mods = + calloc(modp.drmFormatModifierCount, sizeof(*props->dmabuf.render_mods)); + props->dmabuf.texture_mods = + calloc(modp.drmFormatModifierCount, sizeof(*props->dmabuf.texture_mods)); + if (!props->dmabuf.render_mods || !props->dmabuf.texture_mods) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + free(modp.pDrmFormatModifierProperties); + free(props->dmabuf.render_mods); + free(props->dmabuf.texture_mods); + props->dmabuf.render_mods = NULL; + props->dmabuf.texture_mods = NULL; + return false; + } + + bool found = false; + for (uint32_t i = 0; i < modp.drmFormatModifierCount; ++i) { + VkDrmFormatModifierPropertiesEXT m = modp.pDrmFormatModifierProperties[i]; + char render_status[256], texture_status[256]; + + // check that specific modifier for render usage + const char *errmsg = "unknown error"; + if ((m.drmFormatModifierTilingFeatures & render_features) == render_features && + !props->format.is_ycbcr) { + struct wlr_vk_format_modifier_props p = {0}; + bool supported = false; + if (query_modifier_usage_support(dev, props->format.vk, + props->format.vk_srgb, vulkan_render_usage, &m, &p, &errmsg)) { + supported = true; + p.has_mutable_srgb = props->format.vk_srgb != 0; + } + if (!supported && props->format.vk_srgb) { + supported = query_modifier_usage_support(dev, props->format.vk, + 0, vulkan_render_usage, &m, &p, &errmsg); + } + + if (supported) { + props->dmabuf.render_mods[props->dmabuf.render_mod_count++] = p; + wlr_drm_format_set_add(&dev->dmabuf_render_formats, + props->format.drm, m.drmFormatModifier); + found = true; + } + } else { + errmsg = "missing required features"; + } + if (errmsg != NULL) { + snprintf(render_status, sizeof(render_status), "✗ render (%s)", errmsg); + } else { + snprintf(render_status, sizeof(render_status), "✓ render"); + } + + // check that specific modifier for texture usage + errmsg = "unknown error"; + VkFormatFeatureFlags features = dma_tex_features; + if (props->format.is_ycbcr) { + features |= ycbcr_tex_features; + } + if ((m.drmFormatModifierTilingFeatures & features) == features) { + struct wlr_vk_format_modifier_props p = {0}; + bool supported = false; + if (query_modifier_usage_support(dev, props->format.vk, + props->format.vk_srgb, vulkan_dma_tex_usage, &m, &p, &errmsg)) { + supported = true; + p.has_mutable_srgb = props->format.vk_srgb != 0; + } + if (!supported && props->format.vk_srgb) { + supported = query_modifier_usage_support(dev, props->format.vk, + 0, vulkan_dma_tex_usage, &m, &p, &errmsg); + } + + if (supported) { + props->dmabuf.texture_mods[props->dmabuf.texture_mod_count++] = p; + wlr_drm_format_set_add(&dev->dmabuf_texture_formats, + props->format.drm, m.drmFormatModifier); + found = true; + } + } else { + errmsg = "missing required features"; + } + if (errmsg != NULL) { + snprintf(texture_status, sizeof(texture_status), "✗ texture (%s)", errmsg); + } else { + snprintf(texture_status, sizeof(texture_status), "✓ texture"); + } + + char *modifier_name = drmGetFormatModifierName(m.drmFormatModifier); + wlr_log(WLR_DEBUG, " DMA-BUF modifier %s " + "(0x%016"PRIX64", %"PRIu32" planes): %s %s", + modifier_name ? modifier_name : "", m.drmFormatModifier, + m.drmFormatModifierPlaneCount, texture_status, render_status); + free(modifier_name); + } + + free(modp.pDrmFormatModifierProperties); + return found; +} + +void vulkan_format_props_query(struct wlr_vk_device *dev, + const struct wlr_vk_format *format) { + if (format->is_ycbcr && !dev->sampler_ycbcr_conversion) { + return; + } + + char *format_name = drmGetFormatName(format->drm); + wlr_log(WLR_DEBUG, " %s (0x%08"PRIX32")", + format_name ? format_name : "", format->drm); + free(format_name); + + VkDrmFormatModifierPropertiesListEXT modp = { + .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT, + }; + VkFormatProperties2 fmtp = { + .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2, + .pNext = &modp, + }; + + vkGetPhysicalDeviceFormatProperties2(dev->phdev, format->vk, &fmtp); + + bool add_fmt_props = false; + struct wlr_vk_format_props props = {0}; + props.format = *format; + + const struct wlr_pixel_format_info *format_info = drm_get_pixel_format_info(format->drm); + + // shm texture properties + char shm_texture_status[256]; + const char *errmsg = "unknown error"; + if ((fmtp.formatProperties.optimalTilingFeatures & shm_tex_features) == shm_tex_features && + !format->is_ycbcr && format_info != NULL) { + VkImageFormatProperties ifmtp; + bool supported = false, has_mutable_srgb = false; + if (query_shm_support(dev, format->vk, format->vk_srgb, &ifmtp, &errmsg)) { + supported = true; + has_mutable_srgb = format->vk_srgb != 0; + } + if (!supported && format->vk_srgb) { + supported = query_shm_support(dev, format->vk, 0, &ifmtp, &errmsg); + } + + if (supported) { + props.shm.max_extent.width = ifmtp.maxExtent.width; + props.shm.max_extent.height = ifmtp.maxExtent.height; + props.shm.features = fmtp.formatProperties.optimalTilingFeatures; + props.shm.has_mutable_srgb = has_mutable_srgb; + + dev->shm_formats[dev->shm_format_count] = format->drm; + ++dev->shm_format_count; + + add_fmt_props = true; + } + } else { + errmsg = "missing required features"; + } + + if (errmsg != NULL) { + snprintf(shm_texture_status, sizeof(shm_texture_status), "✗ texture (%s)", errmsg); + } else { + snprintf(shm_texture_status, sizeof(shm_texture_status), "✓ texture"); + } + wlr_log(WLR_DEBUG, " Shared memory: %s", shm_texture_status); + + if (modp.drmFormatModifierCount > 0) { + add_fmt_props |= query_modifier_support(dev, &props, + modp.drmFormatModifierCount); + } + + if (add_fmt_props) { + dev->format_props[dev->format_prop_count] = props; + ++dev->format_prop_count; + } else { + vulkan_format_props_finish(&props); + } +} + +void vulkan_format_props_finish(struct wlr_vk_format_props *props) { + free(props->dmabuf.texture_mods); + free(props->dmabuf.render_mods); +} + +const struct wlr_vk_format_modifier_props *vulkan_format_props_find_modifier( + const struct wlr_vk_format_props *props, uint64_t mod, bool render) { + uint32_t len; + const struct wlr_vk_format_modifier_props *mods; + if (render) { + len = props->dmabuf.render_mod_count; + mods = props->dmabuf.render_mods; + } else { + len = props->dmabuf.texture_mod_count; + mods = props->dmabuf.texture_mods; + } + + for (uint32_t i = 0; i < len; ++i) { + if (mods[i].props.drmFormatModifier == mod) { + return &mods[i]; + } + } + return NULL; +} diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c new file mode 100644 index 0000000..d433f49 --- /dev/null +++ b/render/vulkan/renderer.c @@ -0,0 +1,2261 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "render/dmabuf.h" +#include "render/pixel_format.h" +#include "render/vulkan.h" +#include "render/vulkan/shaders/common.vert.h" +#include "render/vulkan/shaders/texture.frag.h" +#include "render/vulkan/shaders/quad.frag.h" +#include "render/vulkan/shaders/output.frag.h" +#include "types/wlr_buffer.h" +#include "types/wlr_matrix.h" + +// TODO: +// - simplify stage allocation, don't track allocations but use ringbuffer-like +// - use a pipeline cache (not sure when to save though, after every pipeline +// creation?) +// - create pipelines as derivatives of each other +// - evaluate if creating VkDeviceMemory pools is a good idea. +// We can expect wayland client images to be fairly large (and shouldn't +// have more than 4k of those I guess) but pooling memory allocations +// might still be a good idea. + +static const VkDeviceSize min_stage_size = 1024 * 1024; // 1MB +static const VkDeviceSize max_stage_size = 256 * min_stage_size; // 256MB +static const size_t start_descriptor_pool_size = 256u; +static bool default_debug = true; + +static const struct wlr_renderer_impl renderer_impl; + +bool wlr_renderer_is_vk(struct wlr_renderer *wlr_renderer) { + return wlr_renderer->impl == &renderer_impl; +} + +struct wlr_vk_renderer *vulkan_get_renderer(struct wlr_renderer *wlr_renderer) { + assert(wlr_renderer_is_vk(wlr_renderer)); + struct wlr_vk_renderer *renderer = wl_container_of(wlr_renderer, renderer, wlr_renderer); + return renderer; +} + +static struct wlr_vk_render_format_setup *find_or_create_render_setup( + struct wlr_vk_renderer *renderer, const struct wlr_vk_format *format, + bool has_blending_buffer); + +static struct wlr_vk_descriptor_pool *alloc_ds( + struct wlr_vk_renderer *renderer, VkDescriptorSet *ds, + VkDescriptorType type, const VkDescriptorSetLayout *layout, + struct wl_list *pool_list, size_t *last_pool_size) { + VkResult res; + + bool found = false; + struct wlr_vk_descriptor_pool *pool; + wl_list_for_each(pool, pool_list, link) { + if (pool->free > 0) { + found = true; + break; + } + } + + if (!found) { // create new pool + pool = calloc(1, sizeof(*pool)); + if (!pool) { + wlr_log_errno(WLR_ERROR, "allocation failed"); + return NULL; + } + + size_t count = 2 * (*last_pool_size); + if (!count) { + count = start_descriptor_pool_size; + } + + pool->free = count; + VkDescriptorPoolSize pool_size = { + .descriptorCount = count, + .type = type, + }; + + VkDescriptorPoolCreateInfo dpool_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .maxSets = count, + .poolSizeCount = 1, + .pPoolSizes = &pool_size, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + }; + + res = vkCreateDescriptorPool(renderer->dev->dev, &dpool_info, NULL, + &pool->pool); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateDescriptorPool", res); + free(pool); + return NULL; + } + + *last_pool_size = count; + wl_list_insert(pool_list, &pool->link); + } + + VkDescriptorSetAllocateInfo ds_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorSetCount = 1, + .pSetLayouts = layout, + .descriptorPool = pool->pool, + }; + res = vkAllocateDescriptorSets(renderer->dev->dev, &ds_info, ds); + if (res != VK_SUCCESS) { + wlr_vk_error("vkAllocateDescriptorSets", res); + return NULL; + } + + --pool->free; + return pool; +} + +struct wlr_vk_descriptor_pool *vulkan_alloc_texture_ds( + struct wlr_vk_renderer *renderer, VkDescriptorSetLayout ds_layout, + VkDescriptorSet *ds) { + return alloc_ds(renderer, ds, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + &ds_layout, &renderer->descriptor_pools, + &renderer->last_pool_size); +} + +struct wlr_vk_descriptor_pool *vulkan_alloc_blend_ds( + struct wlr_vk_renderer *renderer, VkDescriptorSet *ds) { + return alloc_ds(renderer, ds, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, + &renderer->output_ds_layout, &renderer->output_descriptor_pools, + &renderer->last_output_pool_size); +} + +void vulkan_free_ds(struct wlr_vk_renderer *renderer, + struct wlr_vk_descriptor_pool *pool, VkDescriptorSet ds) { + vkFreeDescriptorSets(renderer->dev->dev, pool->pool, 1, &ds); + ++pool->free; +} + +static void destroy_render_format_setup(struct wlr_vk_renderer *renderer, + struct wlr_vk_render_format_setup *setup) { + if (!setup) { + return; + } + + VkDevice dev = renderer->dev->dev; + vkDestroyRenderPass(dev, setup->render_pass, NULL); + vkDestroyPipeline(dev, setup->output_pipe, NULL); + + struct wlr_vk_pipeline *pipeline, *tmp_pipeline; + wl_list_for_each_safe(pipeline, tmp_pipeline, &setup->pipelines, link) { + vkDestroyPipeline(dev, pipeline->vk, NULL); + free(pipeline); + } +} + +static void shared_buffer_destroy(struct wlr_vk_renderer *r, + struct wlr_vk_shared_buffer *buffer) { + if (!buffer) { + return; + } + + if (buffer->allocs.size > 0) { + wlr_log(WLR_ERROR, "shared_buffer_finish: %zu allocations left", + buffer->allocs.size / sizeof(struct wlr_vk_allocation)); + } + + wl_array_release(&buffer->allocs); + if (buffer->buffer) { + vkDestroyBuffer(r->dev->dev, buffer->buffer, NULL); + } + if (buffer->memory) { + vkFreeMemory(r->dev->dev, buffer->memory, NULL); + } + + wl_list_remove(&buffer->link); + free(buffer); +} + +struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r, + VkDeviceSize size, VkDeviceSize alignment) { + // try to find free span + // simple greedy allocation algorithm - should be enough for this usecase + // since all allocations are freed together after the frame + struct wlr_vk_shared_buffer *buf; + wl_list_for_each_reverse(buf, &r->stage.buffers, link) { + VkDeviceSize start = 0u; + if (buf->allocs.size > 0) { + const struct wlr_vk_allocation *allocs = buf->allocs.data; + size_t allocs_len = buf->allocs.size / sizeof(struct wlr_vk_allocation); + const struct wlr_vk_allocation *last = &allocs[allocs_len - 1]; + start = last->start + last->size; + } + + assert(start <= buf->buf_size); + + // ensure the proposed start is a multiple of alignment + start += alignment - 1 - ((start + alignment - 1) % alignment); + + if (buf->buf_size - start < size) { + continue; + } + + struct wlr_vk_allocation *a = wl_array_add(&buf->allocs, sizeof(*a)); + if (a == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error_alloc; + } + + *a = (struct wlr_vk_allocation){ + .start = start, + .size = size, + }; + return (struct wlr_vk_buffer_span) { + .buffer = buf, + .alloc = *a, + }; + } + + if (size > max_stage_size) { + wlr_log(WLR_ERROR, "cannot vulkan stage buffer: " + "requested size (%zu bytes) exceeds maximum (%zu bytes)", + (size_t)size, (size_t)max_stage_size); + goto error_alloc; + } + + // we didn't find a free buffer - create one + // size = clamp(max(size * 2, prev_size * 2), min_size, max_size) + VkDeviceSize bsize = size * 2; + bsize = bsize < min_stage_size ? min_stage_size : bsize; + if (!wl_list_empty(&r->stage.buffers)) { + struct wl_list *last_link = r->stage.buffers.prev; + struct wlr_vk_shared_buffer *prev = wl_container_of( + last_link, prev, link); + VkDeviceSize last_size = 2 * prev->buf_size; + bsize = bsize < last_size ? last_size : bsize; + } + + if (bsize > max_stage_size) { + wlr_log(WLR_INFO, "vulkan stage buffers have reached max size"); + bsize = max_stage_size; + } + + // create buffer + buf = calloc(1, sizeof(*buf)); + if (!buf) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error_alloc; + } + + VkResult res; + VkBufferCreateInfo buf_info = { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = bsize, + .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + }; + res = vkCreateBuffer(r->dev->dev, &buf_info, NULL, &buf->buffer); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateBuffer", res); + goto error; + } + + VkMemoryRequirements mem_reqs; + vkGetBufferMemoryRequirements(r->dev->dev, buf->buffer, &mem_reqs); + + int mem_type_index = vulkan_find_mem_type(r->dev, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, mem_reqs.memoryTypeBits); + if (mem_type_index < 0) { + wlr_log(WLR_ERROR, "Failed to find memory type"); + goto error; + } + + VkMemoryAllocateInfo mem_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = mem_reqs.size, + .memoryTypeIndex = (uint32_t)mem_type_index, + }; + res = vkAllocateMemory(r->dev->dev, &mem_info, NULL, &buf->memory); + if (res != VK_SUCCESS) { + wlr_vk_error("vkAllocatorMemory", res); + goto error; + } + + res = vkBindBufferMemory(r->dev->dev, buf->buffer, buf->memory, 0); + if (res != VK_SUCCESS) { + wlr_vk_error("vkBindBufferMemory", res); + goto error; + } + + struct wlr_vk_allocation *a = wl_array_add(&buf->allocs, sizeof(*a)); + if (a == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error; + } + + wlr_log(WLR_DEBUG, "Created new vk staging buffer of size %" PRIu64, bsize); + buf->buf_size = bsize; + wl_list_insert(&r->stage.buffers, &buf->link); + + *a = (struct wlr_vk_allocation){ + .start = 0, + .size = size, + }; + return (struct wlr_vk_buffer_span) { + .buffer = buf, + .alloc = *a, + }; + +error: + shared_buffer_destroy(r, buf); + +error_alloc: + return (struct wlr_vk_buffer_span) { + .buffer = NULL, + .alloc = (struct wlr_vk_allocation) {0, 0}, + }; +} + +VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer) { + if (renderer->stage.cb == NULL) { + renderer->stage.cb = vulkan_acquire_command_buffer(renderer); + if (renderer->stage.cb == NULL) { + return VK_NULL_HANDLE; + } + + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + }; + vkBeginCommandBuffer(renderer->stage.cb->vk, &begin_info); + } + + return renderer->stage.cb->vk; +} + +bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer) { + if (renderer->stage.cb == NULL) { + return false; + } + + struct wlr_vk_command_buffer *cb = renderer->stage.cb; + renderer->stage.cb = NULL; + + uint64_t timeline_point = vulkan_end_command_buffer(cb, renderer); + if (timeline_point == 0) { + return false; + } + + VkTimelineSemaphoreSubmitInfoKHR timeline_submit_info = { + .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR, + .signalSemaphoreValueCount = 1, + .pSignalSemaphoreValues = &timeline_point, + }; + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = &timeline_submit_info, + .commandBufferCount = 1, + .pCommandBuffers = &cb->vk, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &renderer->timeline_semaphore, + }; + VkResult res = vkQueueSubmit(renderer->dev->queue, 1, &submit_info, VK_NULL_HANDLE); + if (res != VK_SUCCESS) { + wlr_vk_error("vkQueueSubmit", res); + return false; + } + + // NOTE: don't release stage allocations here since they may still be + // used for reading. Will be done next frame. + + return vulkan_wait_command_buffer(cb, renderer); +} + +struct wlr_vk_format_props *vulkan_format_props_from_drm( + struct wlr_vk_device *dev, uint32_t drm_fmt) { + for (size_t i = 0u; i < dev->format_prop_count; ++i) { + if (dev->format_props[i].format.drm == drm_fmt) { + return &dev->format_props[i]; + } + } + return NULL; +} + +static bool init_command_buffer(struct wlr_vk_command_buffer *cb, + struct wlr_vk_renderer *renderer) { + VkResult res; + + VkCommandBuffer vk_cb = VK_NULL_HANDLE; + VkCommandBufferAllocateInfo cmd_buf_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = renderer->command_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + res = vkAllocateCommandBuffers(renderer->dev->dev, &cmd_buf_info, &vk_cb); + if (res != VK_SUCCESS) { + wlr_vk_error("vkAllocateCommandBuffers", res); + return false; + } + + *cb = (struct wlr_vk_command_buffer){ + .vk = vk_cb, + }; + wl_list_init(&cb->destroy_textures); + wl_list_init(&cb->stage_buffers); + return true; +} + +bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb, + struct wlr_vk_renderer *renderer) { + VkResult res; + + assert(cb->vk != VK_NULL_HANDLE && !cb->recording); + + VkSemaphoreWaitInfoKHR wait_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO_KHR, + .semaphoreCount = 1, + .pSemaphores = &renderer->timeline_semaphore, + .pValues = &cb->timeline_point, + }; + res = renderer->dev->api.vkWaitSemaphoresKHR(renderer->dev->dev, &wait_info, UINT64_MAX); + if (res != VK_SUCCESS) { + wlr_vk_error("vkWaitSemaphoresKHR", res); + return false; + } + + return true; +} + +static void release_command_buffer_resources(struct wlr_vk_command_buffer *cb, + struct wlr_vk_renderer *renderer) { + struct wlr_vk_texture *texture, *texture_tmp; + wl_list_for_each_safe(texture, texture_tmp, &cb->destroy_textures, destroy_link) { + wl_list_remove(&texture->destroy_link); + texture->last_used_cb = NULL; + wlr_texture_destroy(&texture->wlr_texture); + } + + struct wlr_vk_shared_buffer *buf, *buf_tmp; + wl_list_for_each_safe(buf, buf_tmp, &cb->stage_buffers, link) { + buf->allocs.size = 0; + + wl_list_remove(&buf->link); + wl_list_insert(&renderer->stage.buffers, &buf->link); + } +} + +static struct wlr_vk_command_buffer *get_command_buffer( + struct wlr_vk_renderer *renderer) { + VkResult res; + + uint64_t current_point; + res = renderer->dev->api.vkGetSemaphoreCounterValueKHR(renderer->dev->dev, + renderer->timeline_semaphore, ¤t_point); + if (res != VK_SUCCESS) { + wlr_vk_error("vkGetSemaphoreCounterValueKHR", res); + return NULL; + } + + // Destroy textures for completed command buffers + for (size_t i = 0; i < VULKAN_COMMAND_BUFFERS_CAP; i++) { + struct wlr_vk_command_buffer *cb = &renderer->command_buffers[i]; + if (cb->vk != VK_NULL_HANDLE && !cb->recording && + cb->timeline_point <= current_point) { + release_command_buffer_resources(cb, renderer); + } + } + + // First try to find an existing command buffer which isn't busy + struct wlr_vk_command_buffer *unused = NULL; + struct wlr_vk_command_buffer *wait = NULL; + for (size_t i = 0; i < VULKAN_COMMAND_BUFFERS_CAP; i++) { + struct wlr_vk_command_buffer *cb = &renderer->command_buffers[i]; + if (cb->vk == VK_NULL_HANDLE) { + unused = cb; + break; + } + if (cb->recording) { + continue; + } + + if (cb->timeline_point <= current_point) { + return cb; + } + if (wait == NULL || cb->timeline_point < wait->timeline_point) { + wait = cb; + } + } + + // If there is an unused slot, initialize it + if (unused != NULL) { + if (!init_command_buffer(unused, renderer)) { + return NULL; + } + return unused; + } + + // Block until a busy command buffer becomes available + if (!vulkan_wait_command_buffer(wait, renderer)) { + return NULL; + } + return wait; +} + +struct wlr_vk_command_buffer *vulkan_acquire_command_buffer( + struct wlr_vk_renderer *renderer) { + struct wlr_vk_command_buffer *cb = get_command_buffer(renderer); + if (cb == NULL) { + return NULL; + } + + assert(!cb->recording); + cb->recording = true; + + return cb; +} + +uint64_t vulkan_end_command_buffer(struct wlr_vk_command_buffer *cb, + struct wlr_vk_renderer *renderer) { + assert(cb->recording); + cb->recording = false; + + VkResult res = vkEndCommandBuffer(cb->vk); + if (res != VK_SUCCESS) { + wlr_vk_error("vkEndCommandBuffer", res); + return 0; + } + + renderer->timeline_point++; + cb->timeline_point = renderer->timeline_point; + return cb->timeline_point; +} + +void vulkan_reset_command_buffer(struct wlr_vk_command_buffer *cb) { + if (cb == NULL) { + return; + } + + cb->recording = false; + + VkResult res = vkResetCommandBuffer(cb->vk, 0); + if (res != VK_SUCCESS) { + wlr_vk_error("vkResetCommandBuffer", res); + } +} + +static void destroy_render_buffer(struct wlr_vk_render_buffer *buffer) { + wl_list_remove(&buffer->link); + wlr_addon_finish(&buffer->addon); + + VkDevice dev = buffer->renderer->dev->dev; + + // TODO: asynchronously wait for the command buffers using this render + // buffer to complete (just like we do for textures) + VkResult res = vkQueueWaitIdle(buffer->renderer->dev->queue); + if (res != VK_SUCCESS) { + wlr_vk_error("vkQueueWaitIdle", res); + } + + vkDestroyFramebuffer(dev, buffer->framebuffer, NULL); + vkDestroyImageView(dev, buffer->image_view, NULL); + vkDestroyImage(dev, buffer->image, NULL); + + for (size_t i = 0u; i < buffer->mem_count; ++i) { + vkFreeMemory(dev, buffer->memories[i], NULL); + } + + vkDestroyImage(dev, buffer->blend_image, NULL); + vkFreeMemory(dev, buffer->blend_memory, NULL); + vkDestroyImageView(dev, buffer->blend_image_view, NULL); + if (buffer->blend_attachment_pool) { + vulkan_free_ds(buffer->renderer, buffer->blend_attachment_pool, + buffer->blend_descriptor_set); + } + + free(buffer); +} + +static void handle_render_buffer_destroy(struct wlr_addon *addon) { + struct wlr_vk_render_buffer *buffer = wl_container_of(addon, buffer, addon); + destroy_render_buffer(buffer); +} + +static struct wlr_addon_interface render_buffer_addon_impl = { + .name = "wlr_vk_render_buffer", + .destroy = handle_render_buffer_destroy, +}; + +static bool setup_blend_image(struct wlr_vk_renderer *renderer, + struct wlr_vk_render_buffer *buffer, int32_t width, int32_t height) { + VkResult res; + VkDevice dev = renderer->dev->dev; + + // Set up an extra 16F buffer on which to do linear blending, + // and afterwards to render onto the target + VkImageCreateInfo img_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .format = VK_FORMAT_R16G16B16A16_SFLOAT, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .extent = (VkExtent3D) { width, height, 1 }, + .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT, + }; + + res = vkCreateImage(dev, &img_info, NULL, &buffer->blend_image); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateImage failed", res); + goto error; + } + + VkMemoryRequirements mem_reqs; + vkGetImageMemoryRequirements(dev, buffer->blend_image, &mem_reqs); + + int mem_type_index = vulkan_find_mem_type(renderer->dev, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mem_reqs.memoryTypeBits); + if (mem_type_index == -1) { + wlr_log(WLR_ERROR, "failed to find suitable vulkan memory type"); + goto error; + } + + VkMemoryAllocateInfo mem_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = mem_reqs.size, + .memoryTypeIndex = mem_type_index, + }; + + res = vkAllocateMemory(dev, &mem_info, NULL, &buffer->blend_memory); + if (res != VK_SUCCESS) { + wlr_vk_error("vkAllocatorMemory failed", res); + goto error; + } + + res = vkBindImageMemory(dev, buffer->blend_image, buffer->blend_memory, 0); + if (res != VK_SUCCESS) { + wlr_vk_error("vkBindMemory failed", res); + goto error; + } + + VkImageViewCreateInfo blend_view_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = buffer->blend_image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = img_info.format, + .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.a = VK_COMPONENT_SWIZZLE_IDENTITY, + .subresourceRange = (VkImageSubresourceRange) { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + res = vkCreateImageView(dev, &blend_view_info, NULL, &buffer->blend_image_view); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateImageView failed", res); + goto error; + } + + buffer->blend_attachment_pool = vulkan_alloc_blend_ds(renderer, + &buffer->blend_descriptor_set); + if (!buffer->blend_attachment_pool) { + wlr_log(WLR_ERROR, "failed to allocate descriptor"); + goto error; + } + + VkDescriptorImageInfo ds_attach_info = { + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .imageView = buffer->blend_image_view, + .sampler = VK_NULL_HANDLE, + }; + VkWriteDescriptorSet ds_write = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, + .dstSet = buffer->blend_descriptor_set, + .dstBinding = 0, + .pImageInfo = &ds_attach_info, + }; + vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL); + return true; + +error: + // cleaning up blend_attachment_pool, blend_descriptor_set, blend_image, + // blend_memory, and blend_image_view is the caller's responsibility, + // since it will need to do this anyway if framebuffer setup fails + return false; +} + +static struct wlr_vk_render_buffer *create_render_buffer( + struct wlr_vk_renderer *renderer, struct wlr_buffer *wlr_buffer) { + VkResult res; + VkDevice dev = renderer->dev->dev; + + struct wlr_vk_render_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + buffer->wlr_buffer = wlr_buffer; + buffer->renderer = renderer; + + struct wlr_dmabuf_attributes dmabuf = {0}; + if (!wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf)) { + goto error; + } + + wlr_log(WLR_DEBUG, "vulkan create_render_buffer: %.4s, %dx%d", + (const char*) &dmabuf.format, dmabuf.width, dmabuf.height); + + bool using_mutable_srgb = false; + buffer->image = vulkan_import_dmabuf(renderer, &dmabuf, + buffer->memories, &buffer->mem_count, true, &using_mutable_srgb); + if (!buffer->image) { + goto error; + } + + const struct wlr_vk_format_props *fmt = vulkan_format_props_from_drm( + renderer->dev, dmabuf.format); + if (fmt == NULL) { + wlr_log(WLR_ERROR, "Unsupported pixel format %"PRIx32 " (%.4s)", + dmabuf.format, (const char*) &dmabuf.format); + goto error; + } + + VkImageViewCreateInfo view_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = buffer->image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = using_mutable_srgb ? fmt->format.vk_srgb : fmt->format.vk, + .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.a = VK_COMPONENT_SWIZZLE_IDENTITY, + .subresourceRange = (VkImageSubresourceRange) { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + res = vkCreateImageView(dev, &view_info, NULL, &buffer->image_view); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateImageView failed", res); + goto error; + } + + bool has_blending_buffer = !using_mutable_srgb; + + buffer->render_setup = find_or_create_render_setup( + renderer, &fmt->format, has_blending_buffer); + if (!buffer->render_setup) { + goto error; + } + + VkImageView attachments[2] = {0}; + uint32_t attachment_count = 0; + + if (has_blending_buffer) { + if (!setup_blend_image(renderer, buffer, dmabuf.width, dmabuf.height)) { + goto error; + } + attachments[attachment_count++] = buffer->blend_image_view; + } + attachments[attachment_count++] = buffer->image_view; + + VkFramebufferCreateInfo fb_info = { + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .attachmentCount = attachment_count, + .pAttachments = attachments, + .flags = 0u, + .width = dmabuf.width, + .height = dmabuf.height, + .layers = 1u, + .renderPass = buffer->render_setup->render_pass, + }; + + res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->framebuffer); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateFramebuffer", res); + goto error; + } + + + wlr_addon_init(&buffer->addon, &wlr_buffer->addons, renderer, + &render_buffer_addon_impl); + wl_list_insert(&renderer->render_buffers, &buffer->link); + + return buffer; + +error: + if (buffer->blend_attachment_pool) { + vulkan_free_ds(buffer->renderer, buffer->blend_attachment_pool, + buffer->blend_descriptor_set); + } + vkDestroyImage(dev, buffer->blend_image, NULL); + vkFreeMemory(dev, buffer->blend_memory, NULL); + vkDestroyImageView(dev, buffer->blend_image_view, NULL); + + vkDestroyFramebuffer(dev, buffer->framebuffer, NULL); + vkDestroyImageView(dev, buffer->image_view, NULL); + vkDestroyImage(dev, buffer->image, NULL); + for (size_t i = 0u; i < buffer->mem_count; ++i) { + vkFreeMemory(dev, buffer->memories[i], NULL); + } + + wlr_dmabuf_attributes_finish(&dmabuf); + free(buffer); + return NULL; +} + +static struct wlr_vk_render_buffer *get_render_buffer( + struct wlr_vk_renderer *renderer, struct wlr_buffer *wlr_buffer) { + struct wlr_addon *addon = + wlr_addon_find(&wlr_buffer->addons, renderer, &render_buffer_addon_impl); + if (addon == NULL) { + return NULL; + } + + struct wlr_vk_render_buffer *buffer = wl_container_of(addon, buffer, addon); + return buffer; +} + +bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture) { + struct wlr_vk_renderer *renderer = texture->renderer; + VkResult res; + + struct wlr_dmabuf_attributes dmabuf = {0}; + if (!wlr_buffer_get_dmabuf(texture->buffer, &dmabuf)) { + wlr_log(WLR_ERROR, "Failed to get texture DMA-BUF"); + return false; + } + + if (!renderer->dev->implicit_sync_interop) { + // We have no choice but to block here sadly + + for (int i = 0; i < dmabuf.n_planes; i++) { + struct pollfd pollfd = { + .fd = dmabuf.fd[i], + .events = POLLIN, + }; + int timeout_ms = 1000; + int ret = poll(&pollfd, 1, timeout_ms); + if (ret < 0) { + wlr_log_errno(WLR_ERROR, "Failed to wait for DMA-BUF fence"); + return false; + } else if (ret == 0) { + wlr_log(WLR_ERROR, "Timed out while waiting for DMA-BUF fence"); + return false; + } + } + + return true; + } + + for (int i = 0; i < dmabuf.n_planes; i++) { + int sync_file_fd = dmabuf_export_sync_file(dmabuf.fd[i], DMA_BUF_SYNC_READ); + if (sync_file_fd < 0) { + wlr_log(WLR_ERROR, "Failed to extract DMA-BUF fence"); + return false; + } + + if (texture->foreign_semaphores[i] == VK_NULL_HANDLE) { + VkSemaphoreCreateInfo semaphore_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + res = vkCreateSemaphore(renderer->dev->dev, &semaphore_info, NULL, + &texture->foreign_semaphores[i]); + if (res != VK_SUCCESS) { + close(sync_file_fd); + wlr_vk_error("vkCreateSemaphore", res); + return false; + } + } + + VkImportSemaphoreFdInfoKHR import_info = { + .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + .flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT, + .semaphore = texture->foreign_semaphores[i], + .fd = sync_file_fd, + }; + res = renderer->dev->api.vkImportSemaphoreFdKHR(renderer->dev->dev, &import_info); + if (res != VK_SUCCESS) { + close(sync_file_fd); + wlr_vk_error("vkImportSemaphoreFdKHR", res); + return false; + } + } + + return true; +} + +bool vulkan_sync_render_buffer(struct wlr_vk_renderer *renderer, + struct wlr_vk_render_buffer *render_buffer, struct wlr_vk_command_buffer *cb) { + VkResult res; + + if (!renderer->dev->implicit_sync_interop) { + // We have no choice but to block here sadly + return vulkan_wait_command_buffer(cb, renderer); + } + + struct wlr_dmabuf_attributes dmabuf = {0}; + if (!wlr_buffer_get_dmabuf(render_buffer->wlr_buffer, &dmabuf)) { + wlr_log(WLR_ERROR, "wlr_buffer_get_dmabuf failed"); + return false; + } + + // Note: vkGetSemaphoreFdKHR implicitly resets the semaphore + const VkSemaphoreGetFdInfoKHR get_fence_fd_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, + .semaphore = cb->binary_semaphore, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + }; + int sync_file_fd = -1; + res = renderer->dev->api.vkGetSemaphoreFdKHR(renderer->dev->dev, + &get_fence_fd_info, &sync_file_fd); + if (res != VK_SUCCESS) { + wlr_vk_error("vkGetSemaphoreFdKHR", res); + return false; + } + + for (int i = 0; i < dmabuf.n_planes; i++) { + if (!dmabuf_import_sync_file(dmabuf.fd[i], DMA_BUF_SYNC_WRITE, + sync_file_fd)) { + close(sync_file_fd); + return false; + } + } + + close(sync_file_fd); + + return true; +} + +static const uint32_t *vulkan_get_shm_texture_formats( + struct wlr_renderer *wlr_renderer, size_t *len) { + struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer); + *len = renderer->dev->shm_format_count; + return renderer->dev->shm_formats; +} + +static const struct wlr_drm_format_set *vulkan_get_dmabuf_texture_formats( + struct wlr_renderer *wlr_renderer) { + struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer); + return &renderer->dev->dmabuf_texture_formats; +} + +static const struct wlr_drm_format_set *vulkan_get_render_formats( + struct wlr_renderer *wlr_renderer) { + struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer); + return &renderer->dev->dmabuf_render_formats; +} + +static void vulkan_destroy(struct wlr_renderer *wlr_renderer) { + struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer); + struct wlr_vk_device *dev = renderer->dev; + if (!dev) { + free(renderer); + return; + } + + VkResult res = vkDeviceWaitIdle(renderer->dev->dev); + if (res != VK_SUCCESS) { + wlr_vk_error("vkDeviceWaitIdle", res); + } + + for (size_t i = 0; i < VULKAN_COMMAND_BUFFERS_CAP; i++) { + struct wlr_vk_command_buffer *cb = &renderer->command_buffers[i]; + if (cb->vk == VK_NULL_HANDLE) { + continue; + } + release_command_buffer_resources(cb, renderer); + if (cb->binary_semaphore != VK_NULL_HANDLE) { + vkDestroySemaphore(renderer->dev->dev, cb->binary_semaphore, NULL); + } + } + + // stage.cb automatically freed with command pool + struct wlr_vk_shared_buffer *buf, *tmp_buf; + wl_list_for_each_safe(buf, tmp_buf, &renderer->stage.buffers, link) { + shared_buffer_destroy(renderer, buf); + } + + struct wlr_vk_texture *tex, *tex_tmp; + wl_list_for_each_safe(tex, tex_tmp, &renderer->textures, link) { + vulkan_texture_destroy(tex); + } + + struct wlr_vk_render_buffer *render_buffer, *render_buffer_tmp; + wl_list_for_each_safe(render_buffer, render_buffer_tmp, + &renderer->render_buffers, link) { + destroy_render_buffer(render_buffer); + } + + struct wlr_vk_render_format_setup *setup, *tmp_setup; + wl_list_for_each_safe(setup, tmp_setup, + &renderer->render_format_setups, link) { + destroy_render_format_setup(renderer, setup); + } + + struct wlr_vk_descriptor_pool *pool, *tmp_pool; + wl_list_for_each_safe(pool, tmp_pool, &renderer->descriptor_pools, link) { + vkDestroyDescriptorPool(dev->dev, pool->pool, NULL); + free(pool); + } + wl_list_for_each_safe(pool, tmp_pool, &renderer->output_descriptor_pools, link) { + vkDestroyDescriptorPool(dev->dev, pool->pool, NULL); + free(pool); + } + + vkDestroyShaderModule(dev->dev, renderer->vert_module, NULL); + vkDestroyShaderModule(dev->dev, renderer->tex_frag_module, NULL); + vkDestroyShaderModule(dev->dev, renderer->quad_frag_module, NULL); + vkDestroyShaderModule(dev->dev, renderer->output_module, NULL); + + struct wlr_vk_pipeline_layout *pipeline_layout, *pipeline_layout_tmp; + wl_list_for_each_safe(pipeline_layout, pipeline_layout_tmp, + &renderer->pipeline_layouts, link) { + vkDestroyPipelineLayout(dev->dev, pipeline_layout->vk, NULL); + vkDestroyDescriptorSetLayout(dev->dev, pipeline_layout->ds, NULL); + vkDestroySampler(dev->dev, pipeline_layout->sampler, NULL); + vkDestroySamplerYcbcrConversion(dev->dev, pipeline_layout->ycbcr.conversion, NULL); + } + + vkDestroySemaphore(dev->dev, renderer->timeline_semaphore, NULL); + vkDestroyPipelineLayout(dev->dev, renderer->output_pipe_layout, NULL); + vkDestroyDescriptorSetLayout(dev->dev, renderer->output_ds_layout, NULL); + vkDestroyCommandPool(dev->dev, renderer->command_pool, NULL); + + if (renderer->read_pixels_cache.initialized) { + vkFreeMemory(dev->dev, renderer->read_pixels_cache.dst_img_memory, NULL); + vkDestroyImage(dev->dev, renderer->read_pixels_cache.dst_image, NULL); + } + + struct wlr_vk_instance *ini = dev->instance; + vulkan_device_destroy(dev); + vulkan_instance_destroy(ini); + free(renderer); +} + +bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, + VkFormat src_format, VkImage src_image, + uint32_t drm_format, uint32_t stride, + uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y, + uint32_t dst_x, uint32_t dst_y, void *data) { + VkDevice dev = vk_renderer->dev->dev; + + const struct wlr_pixel_format_info *pixel_format_info = drm_get_pixel_format_info(drm_format); + if (!pixel_format_info) { + wlr_log(WLR_ERROR, "vulkan_read_pixels: could not find pixel format info " + "for DRM format 0x%08x", drm_format); + return false; + } else if (pixel_format_info_pixels_per_block(pixel_format_info) != 1) { + wlr_log(WLR_ERROR, "vulkan_read_pixels: block formats are not supported"); + return false; + } + + const struct wlr_vk_format *wlr_vk_format = vulkan_get_format_from_drm(drm_format); + if (!wlr_vk_format) { + wlr_log(WLR_ERROR, "vulkan_read_pixels: no vulkan format " + "matching drm format 0x%08x available", drm_format); + return false; + } + VkFormat dst_format = wlr_vk_format->vk; + VkFormatProperties dst_format_props = {0}, src_format_props = {0}; + vkGetPhysicalDeviceFormatProperties(vk_renderer->dev->phdev, dst_format, &dst_format_props); + vkGetPhysicalDeviceFormatProperties(vk_renderer->dev->phdev, src_format, &src_format_props); + + bool blit_supported = src_format_props.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT && + dst_format_props.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT; + if (!blit_supported && src_format != dst_format) { + wlr_log(WLR_ERROR, "vulkan_read_pixels: blit unsupported and no manual " + "conversion available from src to dst format."); + return false; + } + + VkResult res; + VkImage dst_image; + VkDeviceMemory dst_img_memory; + bool use_cached = vk_renderer->read_pixels_cache.initialized && + vk_renderer->read_pixels_cache.drm_format == drm_format && + vk_renderer->read_pixels_cache.width == width && + vk_renderer->read_pixels_cache.height == height; + + if (use_cached) { + dst_image = vk_renderer->read_pixels_cache.dst_image; + dst_img_memory = vk_renderer->read_pixels_cache.dst_img_memory; + } else { + VkImageCreateInfo image_create_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .format = dst_format, + .extent.width = width, + .extent.height = height, + .extent.depth = 1, + .arrayLayers = 1, + .mipLevels = 1, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_LINEAR, + .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT + }; + res = vkCreateImage(dev, &image_create_info, NULL, &dst_image); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateImage", res); + return false; + } + + VkMemoryRequirements mem_reqs; + vkGetImageMemoryRequirements(dev, dst_image, &mem_reqs); + + int mem_type = vulkan_find_mem_type(vk_renderer->dev, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | + VK_MEMORY_PROPERTY_HOST_CACHED_BIT, + mem_reqs.memoryTypeBits); + if (mem_type < 0) { + wlr_log(WLR_ERROR, "vulkan_read_pixels: could not find adequate memory type"); + goto destroy_image; + } + + VkMemoryAllocateInfo mem_alloc_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + }; + mem_alloc_info.allocationSize = mem_reqs.size; + mem_alloc_info.memoryTypeIndex = mem_type; + + res = vkAllocateMemory(dev, &mem_alloc_info, NULL, &dst_img_memory); + if (res != VK_SUCCESS) { + wlr_vk_error("vkAllocateMemory", res); + goto destroy_image; + } + res = vkBindImageMemory(dev, dst_image, dst_img_memory, 0); + if (res != VK_SUCCESS) { + wlr_vk_error("vkBindImageMemory", res); + goto free_memory; + } + + if (vk_renderer->read_pixels_cache.initialized) { + vkFreeMemory(dev, vk_renderer->read_pixels_cache.dst_img_memory, NULL); + vkDestroyImage(dev, vk_renderer->read_pixels_cache.dst_image, NULL); + } + vk_renderer->read_pixels_cache.initialized = true; + vk_renderer->read_pixels_cache.drm_format = drm_format; + vk_renderer->read_pixels_cache.dst_image = dst_image; + vk_renderer->read_pixels_cache.dst_img_memory = dst_img_memory; + vk_renderer->read_pixels_cache.width = width; + vk_renderer->read_pixels_cache.height = height; + } + + VkCommandBuffer cb = vulkan_record_stage_cb(vk_renderer); + if (cb == VK_NULL_HANDLE) { + return false; + } + + vulkan_change_layout(cb, dst_image, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT); + vulkan_change_layout(cb, src_image, + VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_MEMORY_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_READ_BIT); + + if (blit_supported) { + VkImageBlit image_blit_region = { + .srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .srcSubresource.layerCount = 1, + .srcOffsets[0] = { + .x = src_x, + .y = src_y, + .z = 0, + }, + .srcOffsets[1] = { + .x = src_x + width, + .y = src_y + height, + .z = 1, + }, + .dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .dstSubresource.layerCount = 1, + .dstOffsets[1] = { + .x = width, + .y = height, + .z = 1, + } + }; + vkCmdBlitImage(cb, src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &image_blit_region, VK_FILTER_NEAREST); + } else { + wlr_log(WLR_DEBUG, "vulkan_read_pixels: blit unsupported, falling back to vkCmdCopyImage."); + VkImageCopy image_region = { + .srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .srcSubresource.layerCount = 1, + .srcOffset = { + .x = src_x, + .y = src_y, + }, + .dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .dstSubresource.layerCount = 1, + .extent = { + .width = width, + .height = height, + .depth = 1, + } + }; + vkCmdCopyImage(cb, src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_region); + } + + vulkan_change_layout(cb, dst_image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0); + vulkan_change_layout(cb, src_image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_READ_BIT, + VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_MEMORY_READ_BIT); + + if (!vulkan_submit_stage_wait(vk_renderer)) { + return false; + } + + VkImageSubresource img_sub_res = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .arrayLayer = 0, + .mipLevel = 0 + }; + VkSubresourceLayout img_sub_layout; + vkGetImageSubresourceLayout(dev, dst_image, &img_sub_res, &img_sub_layout); + + void *v; + res = vkMapMemory(dev, dst_img_memory, 0, VK_WHOLE_SIZE, 0, &v); + if (res != VK_SUCCESS) { + wlr_vk_error("vkMapMemory", res); + return false; + } + + const char *d = (const char *)v + img_sub_layout.offset; + unsigned char *p = (unsigned char *)data + dst_y * stride; + uint32_t bytes_per_pixel = pixel_format_info->bytes_per_block; + uint32_t pack_stride = img_sub_layout.rowPitch; + if (pack_stride == stride && dst_x == 0) { + memcpy(p, d, height * stride); + } else { + for (size_t i = 0; i < height; ++i) { + memcpy(p + i * stride + dst_x * bytes_per_pixel, d + i * pack_stride, width * bytes_per_pixel); + } + } + + vkUnmapMemory(dev, dst_img_memory); + // Don't need to free anything else, since memory and image are cached + return true; +free_memory: + vkFreeMemory(dev, dst_img_memory, NULL); +destroy_image: + vkDestroyImage(dev, dst_image, NULL); + + return false; +} + +static int vulkan_get_drm_fd(struct wlr_renderer *wlr_renderer) { + struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer); + return renderer->dev->drm_fd; +} + +static uint32_t vulkan_get_render_buffer_caps(struct wlr_renderer *wlr_renderer) { + return WLR_BUFFER_CAP_DMABUF; +} + +static struct wlr_render_pass *vulkan_begin_buffer_pass(struct wlr_renderer *wlr_renderer, + struct wlr_buffer *buffer, const struct wlr_buffer_pass_options *options) { + struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer); + + struct wlr_vk_render_buffer *render_buffer = get_render_buffer(renderer, buffer); + if (!render_buffer) { + render_buffer = create_render_buffer(renderer, buffer); + if (!render_buffer) { + return NULL; + } + } + + struct wlr_vk_render_pass *render_pass = vulkan_begin_render_pass(renderer, render_buffer); + if (render_pass == NULL) { + return NULL; + } + return &render_pass->base; +} + +static const struct wlr_renderer_impl renderer_impl = { + .get_shm_texture_formats = vulkan_get_shm_texture_formats, + .get_dmabuf_texture_formats = vulkan_get_dmabuf_texture_formats, + .get_render_formats = vulkan_get_render_formats, + .destroy = vulkan_destroy, + .get_drm_fd = vulkan_get_drm_fd, + .get_render_buffer_caps = vulkan_get_render_buffer_caps, + .texture_from_buffer = vulkan_texture_from_buffer, + .begin_buffer_pass = vulkan_begin_buffer_pass, +}; + +// Initializes the VkDescriptorSetLayout and VkPipelineLayout needed +// for the texture rendering pipeline using the given VkSampler. +static bool init_tex_layouts(struct wlr_vk_renderer *renderer, + VkSampler tex_sampler, VkDescriptorSetLayout *out_ds_layout, + VkPipelineLayout *out_pipe_layout) { + VkResult res; + VkDevice dev = renderer->dev->dev; + + VkDescriptorSetLayoutBinding ds_binding = { + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = &tex_sampler, + }; + + VkDescriptorSetLayoutCreateInfo ds_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = 1, + .pBindings = &ds_binding, + }; + + res = vkCreateDescriptorSetLayout(dev, &ds_info, NULL, out_ds_layout); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateDescriptorSetLayout", res); + return false; + } + + VkPushConstantRange pc_ranges[2] = { + { + .size = sizeof(struct wlr_vk_vert_pcr_data), + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + }, + { + .offset = pc_ranges[0].size, + .size = sizeof(float) * 4, // alpha or color + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + }, + }; + + VkPipelineLayoutCreateInfo pl_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .setLayoutCount = 1, + .pSetLayouts = out_ds_layout, + .pushConstantRangeCount = 2, + .pPushConstantRanges = pc_ranges, + }; + + res = vkCreatePipelineLayout(dev, &pl_info, NULL, out_pipe_layout); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreatePipelineLayout", res); + return false; + } + + return true; +} + +static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer, + VkDescriptorSetLayout *out_ds_layout, + VkPipelineLayout *out_pipe_layout) { + VkResult res; + VkDevice dev = renderer->dev->dev; + + VkDescriptorSetLayoutBinding ds_binding = { + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = NULL, + }; + + VkDescriptorSetLayoutCreateInfo ds_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = 1, + .pBindings = &ds_binding, + }; + + res = vkCreateDescriptorSetLayout(dev, &ds_info, NULL, out_ds_layout); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateDescriptorSetLayout", res); + return false; + } + + // pipeline layout -- standard vertex uniforms, no shader uniforms + VkPushConstantRange pc_ranges[1] = { + { + .size = sizeof(struct wlr_vk_vert_pcr_data), + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + }, + }; + + VkPipelineLayoutCreateInfo pl_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .setLayoutCount = 1, + .pSetLayouts = out_ds_layout, + .pushConstantRangeCount = 1, + .pPushConstantRanges = pc_ranges, + }; + + res = vkCreatePipelineLayout(dev, &pl_info, NULL, out_pipe_layout); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreatePipelineLayout", res); + return false; + } + + return true; +} + +static bool pipeline_layout_key_equals( + const struct wlr_vk_pipeline_layout_key *a, + const struct wlr_vk_pipeline_layout_key *b) { + assert(!a->ycbcr_format || a->ycbcr_format->is_ycbcr); + assert(!b->ycbcr_format || b->ycbcr_format->is_ycbcr); + + if (a->filter_mode != b->filter_mode) { + return false; + } + + if (a->ycbcr_format != b->ycbcr_format) { + return false; + } + + return true; +} + +static bool pipeline_key_equals(const struct wlr_vk_pipeline_key *a, + const struct wlr_vk_pipeline_key *b) { + if (!pipeline_layout_key_equals(&a->layout, &b->layout)) { + return false; + } + + if (a->blend_mode != b->blend_mode) { + return false; + } + + if (a->source != b->source) { + return false; + } + + if (a->source == WLR_VK_SHADER_SOURCE_TEXTURE && + a->texture_transform != b->texture_transform) { + return false; + } + + return true; +} + +// Initializes the pipeline for rendering textures and using the given +// VkRenderPass and VkPipelineLayout. +struct wlr_vk_pipeline *setup_get_or_create_pipeline( + struct wlr_vk_render_format_setup *setup, + const struct wlr_vk_pipeline_key *key) { + struct wlr_vk_pipeline *pipeline; + wl_list_for_each(pipeline, &setup->pipelines, link) { + if (pipeline_key_equals(&pipeline->key, key)) { + return pipeline; + } + } + + struct wlr_vk_renderer *renderer = setup->renderer; + + struct wlr_vk_pipeline_layout *pipeline_layout = get_or_create_pipeline_layout( + renderer, &key->layout); + if (!pipeline_layout) { + return NULL; + } + + pipeline = calloc(1, sizeof(*pipeline)); + if (!pipeline) { + return NULL; + } + + pipeline->setup = setup; + pipeline->key = *key; + pipeline->layout = pipeline_layout; + + VkResult res; + VkDevice dev = renderer->dev->dev; + + uint32_t color_transform_type = key->texture_transform; + + VkSpecializationMapEntry spec_entry = { + .constantID = 0, + .offset = 0, + .size = sizeof(uint32_t), + }; + + VkSpecializationInfo specialization = { + .mapEntryCount = 1, + .pMapEntries = &spec_entry, + .dataSize = sizeof(uint32_t), + .pData = &color_transform_type, + }; + + VkPipelineShaderStageCreateInfo stages[2]; + stages[0] = (VkPipelineShaderStageCreateInfo) { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = renderer->vert_module, + .pName = "main", + }; + + switch (key->source) { + case WLR_VK_SHADER_SOURCE_SINGLE_COLOR: + stages[1] = (VkPipelineShaderStageCreateInfo) { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = renderer->quad_frag_module, + .pName = "main", + }; + break; + case WLR_VK_SHADER_SOURCE_TEXTURE: + stages[1] = (VkPipelineShaderStageCreateInfo) { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = renderer->tex_frag_module, + .pName = "main", + .pSpecializationInfo = &specialization, + }; + break; + } + + VkPipelineInputAssemblyStateCreateInfo assembly = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, + }; + + VkPipelineRasterizationStateCreateInfo rasterization = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .polygonMode = VK_POLYGON_MODE_FILL, + .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, + .lineWidth = 1.f, + }; + + VkPipelineColorBlendAttachmentState blend_attachment = { + .blendEnable = key->blend_mode == WLR_RENDER_BLEND_MODE_PREMULTIPLIED, + // we generally work with pre-multiplied alpha + .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, + .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | + VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | + VK_COLOR_COMPONENT_A_BIT, + }; + + VkPipelineColorBlendStateCreateInfo blend = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .attachmentCount = 1, + .pAttachments = &blend_attachment, + }; + + VkPipelineMultisampleStateCreateInfo multisample = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + }; + + VkPipelineViewportStateCreateInfo viewport = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .scissorCount = 1, + }; + + VkDynamicState dynStates[2] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + }; + VkPipelineDynamicStateCreateInfo dynamic = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .pDynamicStates = dynStates, + .dynamicStateCount = 2, + }; + + VkPipelineVertexInputStateCreateInfo vertex = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + }; + + VkGraphicsPipelineCreateInfo pinfo = { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .layout = pipeline_layout->vk, + .renderPass = setup->render_pass, + .subpass = 0, + .stageCount = 2, + .pStages = stages, + + .pInputAssemblyState = &assembly, + .pRasterizationState = &rasterization, + .pColorBlendState = &blend, + .pMultisampleState = &multisample, + .pViewportState = &viewport, + .pDynamicState = &dynamic, + .pVertexInputState = &vertex, + }; + + VkPipelineCache cache = VK_NULL_HANDLE; + res = vkCreateGraphicsPipelines(dev, cache, 1, &pinfo, NULL, &pipeline->vk); + if (res != VK_SUCCESS) { + wlr_vk_error("failed to create vulkan pipelines:", res); + free(pipeline); + return NULL; + } + + wl_list_insert(&setup->pipelines, &pipeline->link); + return pipeline; +} + +static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer, + VkRenderPass rp, VkPipelineLayout pipe_layout, VkPipeline *pipe) { + VkResult res; + VkDevice dev = renderer->dev->dev; + + VkPipelineShaderStageCreateInfo tex_stages[2] = { + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = renderer->vert_module, + .pName = "main", + }, + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = renderer->output_module, + .pName = "main", + }, + }; + + VkPipelineInputAssemblyStateCreateInfo assembly = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, + }; + + VkPipelineRasterizationStateCreateInfo rasterization = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .polygonMode = VK_POLYGON_MODE_FILL, + .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, + .lineWidth = 1.f, + }; + + VkPipelineColorBlendAttachmentState blend_attachment = { + .blendEnable = false, + .colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | + VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | + VK_COLOR_COMPONENT_A_BIT, + }; + + VkPipelineColorBlendStateCreateInfo blend = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .attachmentCount = 1, + .pAttachments = &blend_attachment, + }; + + VkPipelineMultisampleStateCreateInfo multisample = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + }; + + VkPipelineViewportStateCreateInfo viewport = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .scissorCount = 1, + }; + + VkDynamicState dynStates[2] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + }; + VkPipelineDynamicStateCreateInfo dynamic = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .pDynamicStates = dynStates, + .dynamicStateCount = 2, + }; + + VkPipelineVertexInputStateCreateInfo vertex = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + }; + + VkGraphicsPipelineCreateInfo pinfo = { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = NULL, + .layout = pipe_layout, + .renderPass = rp, + .subpass = 1, // second subpass! + .stageCount = 2, + .pStages = tex_stages, + .pInputAssemblyState = &assembly, + .pRasterizationState = &rasterization, + .pColorBlendState = &blend, + .pMultisampleState = &multisample, + .pViewportState = &viewport, + .pDynamicState = &dynamic, + .pVertexInputState = &vertex, + }; + + VkPipelineCache cache = VK_NULL_HANDLE; + res = vkCreateGraphicsPipelines(dev, cache, 1, &pinfo, NULL, pipe); + if (res != VK_SUCCESS) { + wlr_vk_error("failed to create vulkan pipelines:", res); + return false; + } + + return true; +} + +struct wlr_vk_pipeline_layout *get_or_create_pipeline_layout( + struct wlr_vk_renderer *renderer, + const struct wlr_vk_pipeline_layout_key *key) { + struct wlr_vk_pipeline_layout *pipeline_layout; + wl_list_for_each(pipeline_layout, &renderer->pipeline_layouts, link) { + if (pipeline_layout_key_equals(&pipeline_layout->key, key)) { + return pipeline_layout; + } + } + + pipeline_layout = calloc(1, sizeof(*pipeline_layout)); + if (!pipeline_layout) { + return NULL; + } + + pipeline_layout->key = *key; + + VkResult res; + VkFilter filter = VK_FILTER_LINEAR; + switch (key->filter_mode) { + case WLR_SCALE_FILTER_BILINEAR: + filter = VK_FILTER_LINEAR; + break; + case WLR_SCALE_FILTER_NEAREST: + filter = VK_FILTER_NEAREST; + break; + } + + VkSamplerYcbcrConversionInfo conversion_info; + VkSamplerCreateInfo sampler_create_info = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .magFilter = filter, + .minFilter = filter, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .minLod = 0.f, + .maxLod = 0.25f, + }; + + if (key->ycbcr_format) { + VkSamplerYcbcrConversionCreateInfo conversion_create_info = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO, + .format = key->ycbcr_format->vk, + .ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601, + .ycbcrRange = VK_SAMPLER_YCBCR_RANGE_ITU_NARROW, + .xChromaOffset = VK_CHROMA_LOCATION_MIDPOINT, + .yChromaOffset = VK_CHROMA_LOCATION_MIDPOINT, + .chromaFilter = VK_FILTER_LINEAR, + }; + res = vkCreateSamplerYcbcrConversion(renderer->dev->dev, + &conversion_create_info, NULL, &pipeline_layout->ycbcr.conversion); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateSamplerYcbcrConversion", res); + free(pipeline_layout); + return NULL; + } + + conversion_info = (VkSamplerYcbcrConversionInfo){ + .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO, + .conversion = pipeline_layout->ycbcr.conversion, + }; + sampler_create_info.pNext = &conversion_info; + } + + res = vkCreateSampler(renderer->dev->dev, &sampler_create_info, NULL, &pipeline_layout->sampler); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateSampler", res); + free(pipeline_layout); + return NULL; + } + + if (!init_tex_layouts(renderer, pipeline_layout->sampler, &pipeline_layout->ds, &pipeline_layout->vk)) { + free(pipeline_layout); + return NULL; + } + + wl_list_insert(&renderer->pipeline_layouts, &pipeline_layout->link); + return pipeline_layout; +} + +// Creates static render data, such as sampler, layouts and shader modules +// for the given renderer. +// Cleanup is done by destroying the renderer. +static bool init_static_render_data(struct wlr_vk_renderer *renderer) { + VkResult res; + VkDevice dev = renderer->dev->dev; + + if (!init_blend_to_output_layouts(renderer, &renderer->output_ds_layout, + &renderer->output_pipe_layout)) { + return false; + } + + // load vert module and tex frag module since they are needed to + // initialize the tex pipeline + VkShaderModuleCreateInfo sinfo = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = sizeof(common_vert_data), + .pCode = common_vert_data, + }; + res = vkCreateShaderModule(dev, &sinfo, NULL, &renderer->vert_module); + if (res != VK_SUCCESS) { + wlr_vk_error("Failed to create vertex shader module", res); + return false; + } + + sinfo = (VkShaderModuleCreateInfo){ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = sizeof(texture_frag_data), + .pCode = texture_frag_data, + }; + res = vkCreateShaderModule(dev, &sinfo, NULL, &renderer->tex_frag_module); + if (res != VK_SUCCESS) { + wlr_vk_error("Failed to create tex fragment shader module", res); + return false; + } + + sinfo = (VkShaderModuleCreateInfo){ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = sizeof(quad_frag_data), + .pCode = quad_frag_data, + }; + res = vkCreateShaderModule(dev, &sinfo, NULL, &renderer->quad_frag_module); + if (res != VK_SUCCESS) { + wlr_vk_error("Failed to create quad fragment shader module", res); + return false; + } + + sinfo = (VkShaderModuleCreateInfo){ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = sizeof(output_frag_data), + .pCode = output_frag_data, + }; + res = vkCreateShaderModule(dev, &sinfo, NULL, &renderer->output_module); + if (res != VK_SUCCESS) { + wlr_vk_error("Failed to create blend->output fragment shader module", res); + return false; + } + + return true; +} + +static struct wlr_vk_render_format_setup *find_or_create_render_setup( + struct wlr_vk_renderer *renderer, const struct wlr_vk_format *format, + bool has_blending_buffer) { + struct wlr_vk_render_format_setup *setup; + wl_list_for_each(setup, &renderer->render_format_setups, link) { + if (setup->render_format == format) { + return setup; + } + } + + setup = calloc(1u, sizeof(*setup)); + if (!setup) { + wlr_log(WLR_ERROR, "Allocation failed"); + return NULL; + } + + setup->render_format = format; + setup->renderer = renderer; + wl_list_init(&setup->pipelines); + + VkDevice dev = renderer->dev->dev; + VkResult res; + + if (has_blending_buffer) { + VkAttachmentDescription attachments[2] = { + { + .format = VK_FORMAT_R16G16B16A16_SFLOAT, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }, + { + .format = format->vk, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_GENERAL, + .finalLayout = VK_IMAGE_LAYOUT_GENERAL, + } + }; + + VkAttachmentReference blend_write_ref = { + .attachment = 0u, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }; + + VkAttachmentReference blend_read_ref = { + .attachment = 0u, + .layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }; + + VkAttachmentReference color_ref = { + .attachment = 1u, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }; + + VkSubpassDescription subpasses[2] = { + { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .colorAttachmentCount = 1, + .pColorAttachments = &blend_write_ref, + }, + { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .inputAttachmentCount = 1, + .pInputAttachments = &blend_read_ref, + .colorAttachmentCount = 1, + .pColorAttachments = &color_ref, + } + }; + + VkSubpassDependency deps[3] = { + { + .srcSubpass = VK_SUBPASS_EXTERNAL, + .srcStageMask = VK_PIPELINE_STAGE_HOST_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT | + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstSubpass = 0, + .dstStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, + .dstAccessMask = VK_ACCESS_UNIFORM_READ_BIT | + VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | + VK_ACCESS_INDIRECT_COMMAND_READ_BIT | + VK_ACCESS_SHADER_READ_BIT, + }, + { + .srcSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstSubpass = 1, + .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT, + }, + { + .srcSubpass = 1, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstSubpass = VK_SUBPASS_EXTERNAL, + .dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT | + VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | + VK_ACCESS_MEMORY_READ_BIT, + }, + }; + + VkRenderPassCreateInfo rp_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .attachmentCount = 2u, + .pAttachments = attachments, + .subpassCount = 2u, + .pSubpasses = subpasses, + .dependencyCount = 3u, + .pDependencies = deps, + }; + + res = vkCreateRenderPass(dev, &rp_info, NULL, &setup->render_pass); + if (res != VK_SUCCESS) { + wlr_vk_error("Failed to create 2-step render pass", res); + goto error; + } + + // this is only well defined if render pass has a 2nd subpass + if (!init_blend_to_output_pipeline( + renderer, setup->render_pass, renderer->output_pipe_layout, + &setup->output_pipe)) { + goto error; + } + } else { + assert(format->vk_srgb); + VkAttachmentDescription attachment = { + .format = format->vk_srgb, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_GENERAL, + .finalLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + + VkAttachmentReference color_ref = { + .attachment = 0u, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }; + + VkSubpassDescription subpass = { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .colorAttachmentCount = 1, + .pColorAttachments = &color_ref, + }; + + VkSubpassDependency deps[2] = { + { + .srcSubpass = VK_SUBPASS_EXTERNAL, + .srcStageMask = VK_PIPELINE_STAGE_HOST_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT | + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstSubpass = 0, + .dstStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, + .dstAccessMask = VK_ACCESS_UNIFORM_READ_BIT | + VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | + VK_ACCESS_INDIRECT_COMMAND_READ_BIT | + VK_ACCESS_SHADER_READ_BIT, + }, + { + .srcSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstSubpass = VK_SUBPASS_EXTERNAL, + .dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT | + VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | + VK_ACCESS_MEMORY_READ_BIT, + }, + }; + + VkRenderPassCreateInfo rp_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .attachmentCount = 1, + .pAttachments = &attachment, + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 2u, + .pDependencies = deps, + }; + + res = vkCreateRenderPass(dev, &rp_info, NULL, &setup->render_pass); + if (res != VK_SUCCESS) { + wlr_vk_error("Failed to create render pass", res); + goto error; + } + } + + if (!setup_get_or_create_pipeline(setup, &(struct wlr_vk_pipeline_key){ + .source = WLR_VK_SHADER_SOURCE_SINGLE_COLOR, + .layout = { .ycbcr_format = NULL }, + })) { + goto error; + } + + if (!setup_get_or_create_pipeline(setup, &(struct wlr_vk_pipeline_key){ + .source = WLR_VK_SHADER_SOURCE_TEXTURE, + .texture_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY, + .layout = {.ycbcr_format = NULL }, + })) { + goto error; + } + + if (!setup_get_or_create_pipeline(setup, &(struct wlr_vk_pipeline_key){ + .source = WLR_VK_SHADER_SOURCE_TEXTURE, + .texture_transform = WLR_VK_TEXTURE_TRANSFORM_SRGB, + .layout = {.ycbcr_format = NULL }, + })) { + goto error; + } + + for (size_t i = 0; i < renderer->dev->format_prop_count; i++) { + const struct wlr_vk_format *format = &renderer->dev->format_props[i].format; + const struct wlr_vk_pipeline_layout_key layout = { + .ycbcr_format = format, + }; + + if (format->is_ycbcr) { + if (!setup_get_or_create_pipeline(setup, &(struct wlr_vk_pipeline_key){ + .texture_transform = WLR_VK_TEXTURE_TRANSFORM_SRGB, + .layout = layout + })) { + goto error; + } + } + } + + wl_list_insert(&renderer->render_format_setups, &setup->link); + return setup; + +error: + destroy_render_format_setup(renderer, setup); + return NULL; +} + +struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev) { + struct wlr_vk_renderer *renderer; + VkResult res; + if (!(renderer = calloc(1, sizeof(*renderer)))) { + wlr_log_errno(WLR_ERROR, "failed to allocate wlr_vk_renderer"); + return NULL; + } + + renderer->dev = dev; + wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl); + wl_list_init(&renderer->stage.buffers); + wl_list_init(&renderer->foreign_textures); + wl_list_init(&renderer->textures); + wl_list_init(&renderer->descriptor_pools); + wl_list_init(&renderer->output_descriptor_pools); + wl_list_init(&renderer->render_format_setups); + wl_list_init(&renderer->render_buffers); + wl_list_init(&renderer->pipeline_layouts); + + if (!init_static_render_data(renderer)) { + goto error; + } + + VkCommandPoolCreateInfo cpool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = dev->queue_family, + }; + res = vkCreateCommandPool(dev->dev, &cpool_info, NULL, + &renderer->command_pool); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateCommandPool", res); + goto error; + } + + VkSemaphoreTypeCreateInfoKHR semaphore_type_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR, + .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE_KHR, + .initialValue = 0, + }; + VkSemaphoreCreateInfo semaphore_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = &semaphore_type_info, + }; + res = vkCreateSemaphore(dev->dev, &semaphore_info, NULL, + &renderer->timeline_semaphore); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateSemaphore", res); + goto error; + } + + return &renderer->wlr_renderer; + +error: + vulkan_destroy(&renderer->wlr_renderer); + return NULL; +} + +struct wlr_renderer *wlr_vk_renderer_create_with_drm_fd(int drm_fd) { + wlr_log(WLR_INFO, "The vulkan renderer is only experimental and " + "not expected to be ready for daily use"); + wlr_log(WLR_INFO, "Run with VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation " + "to enable the validation layer"); + + struct wlr_vk_instance *ini = vulkan_instance_create(default_debug); + if (!ini) { + wlr_log(WLR_ERROR, "creating vulkan instance for renderer failed"); + return NULL; + } + + VkPhysicalDevice phdev = vulkan_find_drm_phdev(ini, drm_fd); + if (!phdev) { + // We rather fail here than doing some guesswork + wlr_log(WLR_ERROR, "Could not match drm and vulkan device"); + return NULL; + } + + struct wlr_vk_device *dev = vulkan_device_create(ini, phdev); + if (!dev) { + wlr_log(WLR_ERROR, "Failed to create vulkan device"); + vulkan_instance_destroy(ini); + return NULL; + } + + // Do not use the drm_fd that was passed in: we should prefer the render + // node even if a primary node was provided + dev->drm_fd = vulkan_open_phdev_drm_fd(phdev); + if (dev->drm_fd < 0) { + vulkan_device_destroy(dev); + vulkan_instance_destroy(ini); + return NULL; + } + + return vulkan_renderer_create_for_device(dev); +} + +VkInstance wlr_vk_renderer_get_instance(struct wlr_renderer *renderer) { + struct wlr_vk_renderer *vk_renderer = vulkan_get_renderer(renderer); + return vk_renderer->dev->instance->instance; +} + +VkPhysicalDevice wlr_vk_renderer_get_physical_device(struct wlr_renderer *renderer) { + struct wlr_vk_renderer *vk_renderer = vulkan_get_renderer(renderer); + return vk_renderer->dev->phdev; +} + +VkDevice wlr_vk_renderer_get_device(struct wlr_renderer *renderer) { + struct wlr_vk_renderer *vk_renderer = vulkan_get_renderer(renderer); + return vk_renderer->dev->dev; +} + +uint32_t wlr_vk_renderer_get_queue_family(struct wlr_renderer *renderer) { + struct wlr_vk_renderer *vk_renderer = vulkan_get_renderer(renderer); + return vk_renderer->dev->queue_family; +} diff --git a/render/vulkan/shaders/common.vert b/render/vulkan/shaders/common.vert new file mode 100644 index 0000000..c6175d2 --- /dev/null +++ b/render/vulkan/shaders/common.vert @@ -0,0 +1,18 @@ +#version 450 + +// we use a mat4 since it uses the same size as mat3 due to +// alignment. Easier to deal with (tighly-packed) mat4 though. +layout(push_constant, row_major) uniform UBO { + mat4 proj; + vec2 uv_offset; + vec2 uv_size; +} data; + +layout(location = 0) out vec2 uv; + +void main() { + vec2 pos = vec2(float((gl_VertexIndex + 1) & 2) * 0.5f, + float(gl_VertexIndex & 2) * 0.5f); + uv = data.uv_offset + pos * data.uv_size; + gl_Position = data.proj * vec4(pos, 0.0, 1.0); +} diff --git a/render/vulkan/shaders/meson.build b/render/vulkan/shaders/meson.build new file mode 100644 index 0000000..50f4a1f --- /dev/null +++ b/render/vulkan/shaders/meson.build @@ -0,0 +1,24 @@ +vulkan_shaders_src = [ + 'common.vert', + 'texture.frag', + 'quad.frag', + 'output.frag', +] + +vulkan_shaders = [] +foreach shader : vulkan_shaders_src + name = shader.underscorify() + '_data' + args = [glslang, '-V', '@INPUT@', '-o', '@OUTPUT@', '--vn', name] + if glslang_version.version_compare('>=11.0.0') + args += '--quiet' + endif + header = custom_target( + shader + '_spv', + output: shader + '.h', + input: shader, + command: args) + + vulkan_shaders += [header] +endforeach + +wlr_files += vulkan_shaders diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag new file mode 100644 index 0000000..263f3e1 --- /dev/null +++ b/render/vulkan/shaders/output.frag @@ -0,0 +1,29 @@ +#version 450 + +layout (input_attachment_index = 0, binding = 0) uniform subpassInput in_color; + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 out_color; + +float linear_channel_to_srgb(float x) { + return max(min(x * 12.92, 0.04045), 1.055 * pow(x, 1. / 2.4) - 0.055); +} + +vec4 linear_color_to_srgb(vec4 color) { + if (color.a == 0) { + return vec4(0); + } + color.rgb /= color.a; + color.rgb = vec3( + linear_channel_to_srgb(color.r), + linear_channel_to_srgb(color.g), + linear_channel_to_srgb(color.b) + ); + color.rgb *= color.a; + return color; +} + +void main() { + vec4 val = subpassLoad(in_color).rgba; + out_color = linear_color_to_srgb(val); +} diff --git a/render/vulkan/shaders/quad.frag b/render/vulkan/shaders/quad.frag new file mode 100644 index 0000000..affd1f1 --- /dev/null +++ b/render/vulkan/shaders/quad.frag @@ -0,0 +1,10 @@ +#version 450 + +layout(location = 0) out vec4 out_color; +layout(push_constant) uniform UBO { + layout(offset = 80) vec4 color; +} data; + +void main() { + out_color = data.color; +} diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag new file mode 100644 index 0000000..6f2f347 --- /dev/null +++ b/render/vulkan/shaders/texture.frag @@ -0,0 +1,47 @@ +#version 450 + +layout(set = 0, binding = 0) uniform sampler2D tex; + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 out_color; + +layout(push_constant) uniform UBO { + layout(offset = 80) float alpha; +} data; + +layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; + +// Matches enum wlr_vk_texture_transform +#define TEXTURE_TRANSFORM_IDENTITY 0 +#define TEXTURE_TRANSFORM_SRGB 1 + +float srgb_channel_to_linear(float x) { + return mix(x / 12.92, + pow((x + 0.055) / 1.055, 2.4), + x > 0.04045); +} + +vec4 srgb_color_to_linear(vec4 color) { + if (color.a == 0) { + return vec4(0); + } + color.rgb /= color.a; + color.rgb = vec3( + srgb_channel_to_linear(color.r), + srgb_channel_to_linear(color.g), + srgb_channel_to_linear(color.b) + ); + color.rgb *= color.a; + return color; +} + +void main() { + vec4 val = textureLod(tex, uv, 0); + if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_SRGB) { + out_color = srgb_color_to_linear(val); + } else { // TEXTURE_TRANSFORM_IDENTITY + out_color = val; + } + + out_color *= data.alpha; +} diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c new file mode 100644 index 0000000..51372b5 --- /dev/null +++ b/render/vulkan/texture.c @@ -0,0 +1,851 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "render/pixel_format.h" +#include "render/vulkan.h" + +static const struct wlr_texture_impl texture_impl; + +bool wlr_texture_is_vk(struct wlr_texture *wlr_texture) { + return wlr_texture->impl == &texture_impl; +} + +struct wlr_vk_texture *vulkan_get_texture(struct wlr_texture *wlr_texture) { + assert(wlr_texture_is_vk(wlr_texture)); + struct wlr_vk_texture *texture = wl_container_of(wlr_texture, texture, wlr_texture); + return texture; +} + +static VkImageAspectFlagBits mem_plane_aspect(unsigned i) { + switch (i) { + case 0: return VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT; + case 1: return VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT; + case 2: return VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT; + case 3: return VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT; + default: abort(); // unreachable + } +} + +// Will transition the texture to shaderReadOnlyOptimal layout for reading +// from fragment shader later on +static bool write_pixels(struct wlr_vk_texture *texture, + uint32_t stride, const pixman_region32_t *region, const void *vdata, + VkImageLayout old_layout, VkPipelineStageFlags src_stage, + VkAccessFlags src_access) { + VkResult res; + struct wlr_vk_renderer *renderer = texture->renderer; + VkDevice dev = texture->renderer->dev->dev; + + const struct wlr_pixel_format_info *format_info = drm_get_pixel_format_info(texture->format->drm); + assert(format_info); + + uint32_t bsize = 0; + + // deferred upload by transfer; using staging buffer + // calculate maximum side needed + int rects_len = 0; + const pixman_box32_t *rects = pixman_region32_rectangles(region, &rects_len); + for (int i = 0; i < rects_len; i++) { + pixman_box32_t rect = rects[i]; + uint32_t width = rect.x2 - rect.x1; + uint32_t height = rect.y2 - rect.y1; + + // make sure assumptions are met + assert((uint32_t)rect.x2 <= texture->wlr_texture.width); + assert((uint32_t)rect.y2 <= texture->wlr_texture.height); + + bsize += height * pixel_format_info_min_stride(format_info, width); + } + + VkBufferImageCopy *copies = calloc((size_t)rects_len, sizeof(*copies)); + if (!copies) { + wlr_log(WLR_ERROR, "Failed to allocate image copy parameters"); + return false; + } + + // get staging buffer + struct wlr_vk_buffer_span span = vulkan_get_stage_span(renderer, bsize, format_info->bytes_per_block); + if (!span.buffer || span.alloc.size != bsize) { + wlr_log(WLR_ERROR, "Failed to retrieve staging buffer"); + free(copies); + return false; + } + + void *vmap; + res = vkMapMemory(dev, span.buffer->memory, span.alloc.start, + bsize, 0, &vmap); + if (res != VK_SUCCESS) { + wlr_vk_error("vkMapMemory", res); + free(copies); + return false; + } + char *map = (char *)vmap; + + // upload data + + uint32_t buf_off = span.alloc.start + (map - (char *)vmap); + for (int i = 0; i < rects_len; i++) { + pixman_box32_t rect = rects[i]; + uint32_t width = rect.x2 - rect.x1; + uint32_t height = rect.y2 - rect.y1; + uint32_t src_x = rect.x1; + uint32_t src_y = rect.y1; + uint32_t packed_stride = (uint32_t)pixel_format_info_min_stride(format_info, width); + + // write data into staging buffer span + const char *pdata = vdata; // data iterator + pdata += stride * src_y; + pdata += format_info->bytes_per_block * src_x; + if (src_x == 0 && width == texture->wlr_texture.width && + stride == packed_stride) { + memcpy(map, pdata, packed_stride * height); + map += packed_stride * height; + } else { + for (unsigned i = 0u; i < height; ++i) { + memcpy(map, pdata, packed_stride); + pdata += stride; + map += packed_stride; + } + } + + copies[i] = (VkBufferImageCopy) { + .imageExtent.width = width, + .imageExtent.height = height, + .imageExtent.depth = 1, + .imageOffset.x = src_x, + .imageOffset.y = src_y, + .imageOffset.z = 0, + .bufferOffset = buf_off, + .bufferRowLength = width, + .bufferImageHeight = height, + .imageSubresource.mipLevel = 0, + .imageSubresource.baseArrayLayer = 0, + .imageSubresource.layerCount = 1, + .imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + }; + + + buf_off += height * packed_stride; + } + + assert((uint32_t)(map - (char *)vmap) == bsize); + vkUnmapMemory(dev, span.buffer->memory); + + // record staging cb + // will be executed before next frame + VkCommandBuffer cb = vulkan_record_stage_cb(renderer); + if (cb == VK_NULL_HANDLE) { + free(copies); + return false; + } + + vulkan_change_layout(cb, texture->image, + old_layout, src_stage, src_access, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT); + + vkCmdCopyBufferToImage(cb, span.buffer->buffer, texture->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (uint32_t)rects_len, copies); + vulkan_change_layout(cb, texture->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT); + texture->last_used_cb = renderer->stage.cb; + + free(copies); + + return true; +} + +static bool vulkan_texture_update_from_buffer(struct wlr_texture *wlr_texture, + struct wlr_buffer *buffer, const pixman_region32_t *damage) { + struct wlr_vk_texture *texture = vulkan_get_texture(wlr_texture); + + void *data; + uint32_t format; + size_t stride; + if (!wlr_buffer_begin_data_ptr_access(buffer, + WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { + return false; + } + + bool ok = true; + + if (format != texture->format->drm) { + ok = false; + goto out; + } + + ok = write_pixels(texture, stride, damage, data, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT); + +out: + wlr_buffer_end_data_ptr_access(buffer); + return ok; +} + +void vulkan_texture_destroy(struct wlr_vk_texture *texture) { + if (texture->buffer != NULL) { + wlr_addon_finish(&texture->buffer_addon); + texture->buffer = NULL; + } + + // when we recorded a command to fill this image _this_ frame, + // it has to be executed before the texture can be destroyed. + // Add it to the renderer->destroy_textures list, destroying + // _after_ the stage command buffer has exectued + if (texture->last_used_cb != NULL) { + assert(texture->destroy_link.next == NULL); // not already inserted + wl_list_insert(&texture->last_used_cb->destroy_textures, + &texture->destroy_link); + return; + } + + wl_list_remove(&texture->link); + + VkDevice dev = texture->renderer->dev->dev; + + struct wlr_vk_texture_view *view, *tmp_view; + wl_list_for_each_safe(view, tmp_view, &texture->views, link) { + vulkan_free_ds(texture->renderer, view->ds_pool, view->ds); + vkDestroyImageView(dev, view->image_view, NULL); + free(view); + } + + for (size_t i = 0; i < WLR_DMABUF_MAX_PLANES; i++) { + if (texture->foreign_semaphores[i] != VK_NULL_HANDLE) { + vkDestroySemaphore(dev, texture->foreign_semaphores[i], NULL); + } + } + + vkDestroyImage(dev, texture->image, NULL); + + for (unsigned i = 0u; i < texture->mem_count; ++i) { + vkFreeMemory(dev, texture->memories[i], NULL); + } + + free(texture); +} + +static void vulkan_texture_unref(struct wlr_texture *wlr_texture) { + struct wlr_vk_texture *texture = vulkan_get_texture(wlr_texture); + if (texture->buffer != NULL) { + // Keep the texture around, in case the buffer is re-used later. We're + // still listening to the buffer's destroy event. + wlr_buffer_unlock(texture->buffer); + } else { + vulkan_texture_destroy(texture); + } +} + +static bool vulkan_texture_read_pixels(struct wlr_texture *wlr_texture, + const struct wlr_texture_read_pixels_options *options) { + struct wlr_vk_texture *texture = vulkan_get_texture(wlr_texture); + + struct wlr_box src; + wlr_texture_read_pixels_options_get_src_box(options, wlr_texture, &src); + + void *p = wlr_texture_read_pixel_options_get_data(options); + + return vulkan_read_pixels(texture->renderer, texture->format->vk, texture->image, + options->format, options->stride, src.width, src.height, src.x, src.y, 0, 0, p); +} + +static uint32_t vulkan_texture_preferred_read_format(struct wlr_texture *wlr_texture) { + struct wlr_vk_texture *texture = vulkan_get_texture(wlr_texture); + return texture->format->drm; +} + +static const struct wlr_texture_impl texture_impl = { + .update_from_buffer = vulkan_texture_update_from_buffer, + .read_pixels = vulkan_texture_read_pixels, + .preferred_read_format = vulkan_texture_preferred_read_format, + .destroy = vulkan_texture_unref, +}; + +static struct wlr_vk_texture *vulkan_texture_create( + struct wlr_vk_renderer *renderer, uint32_t width, uint32_t height) { + struct wlr_vk_texture *texture = calloc(1, sizeof(*texture)); + if (texture == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + wlr_texture_init(&texture->wlr_texture, &renderer->wlr_renderer, + &texture_impl, width, height); + texture->renderer = renderer; + wl_list_insert(&renderer->textures, &texture->link); + wl_list_init(&texture->views); + return texture; +} + +struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_texture *texture, + const struct wlr_vk_pipeline_layout *pipeline_layout) { + struct wlr_vk_texture_view *view; + wl_list_for_each(view, &texture->views, link) { + if (view->layout == pipeline_layout) { + return view; + } + } + + view = calloc(1, sizeof(*view)); + if (!view) { + return NULL; + } + + view->layout = pipeline_layout; + + VkResult res; + VkDevice dev = texture->renderer->dev->dev; + + VkImageViewCreateInfo view_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = texture->using_mutable_srgb ? texture->format->vk_srgb + : texture->format->vk, + .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.a = texture->has_alpha || texture->format->is_ycbcr + ? VK_COMPONENT_SWIZZLE_IDENTITY + : VK_COMPONENT_SWIZZLE_ONE, + .subresourceRange = (VkImageSubresourceRange){ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .image = texture->image, + }; + + VkSamplerYcbcrConversionInfo ycbcr_conversion_info; + if (texture->format->is_ycbcr) { + assert(pipeline_layout->ycbcr.conversion != VK_NULL_HANDLE); + ycbcr_conversion_info = (VkSamplerYcbcrConversionInfo){ + .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO, + .conversion = pipeline_layout->ycbcr.conversion, + }; + view_info.pNext = &ycbcr_conversion_info; + } + + res = vkCreateImageView(dev, &view_info, NULL, &view->image_view); + if (res != VK_SUCCESS) { + free(view); + wlr_vk_error("vkCreateImageView failed", res); + return NULL; + } + + view->ds_pool = vulkan_alloc_texture_ds(texture->renderer, pipeline_layout->ds, &view->ds); + if (!view->ds_pool) { + free(view); + wlr_log(WLR_ERROR, "failed to allocate descriptor"); + return NULL; + } + + VkDescriptorImageInfo ds_img_info = { + .imageView = view->image_view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }; + + VkWriteDescriptorSet ds_write = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .dstSet = view->ds, + .pImageInfo = &ds_img_info, + }; + + vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL); + + wl_list_insert(&texture->views, &view->link); + return view; +} + +static void texture_set_format(struct wlr_vk_texture *texture, + const struct wlr_vk_format *format, bool has_mutable_srgb) { + texture->format = format; + texture->using_mutable_srgb = has_mutable_srgb; + texture->transform = !format->is_ycbcr && has_mutable_srgb ? + WLR_VK_TEXTURE_TRANSFORM_IDENTITY : WLR_VK_TEXTURE_TRANSFORM_SRGB; + + const struct wlr_pixel_format_info *format_info = + drm_get_pixel_format_info(format->drm); + if (format_info != NULL) { + texture->has_alpha = pixel_format_has_alpha(format->drm); + } else { + // We don't have format info for multi-planar formats + assert(texture->format->is_ycbcr); + } +} + +static struct wlr_texture *vulkan_texture_from_pixels( + struct wlr_vk_renderer *renderer, uint32_t drm_fmt, uint32_t stride, + uint32_t width, uint32_t height, const void *data) { + VkResult res; + VkDevice dev = renderer->dev->dev; + + const struct wlr_vk_format_props *fmt = + vulkan_format_props_from_drm(renderer->dev, drm_fmt); + if (fmt == NULL || fmt->format.is_ycbcr) { + char *format_name = drmGetFormatName(drm_fmt); + wlr_log(WLR_ERROR, "Unsupported pixel format %s (0x%08"PRIX32")", + format_name, drm_fmt); + free(format_name); + return NULL; + } + + struct wlr_vk_texture *texture = vulkan_texture_create(renderer, width, height); + if (texture == NULL) { + return NULL; + } + + texture_set_format(texture, &fmt->format, fmt->shm.has_mutable_srgb); + + VkFormat view_formats[2] = { + fmt->format.vk, + fmt->format.vk_srgb, + }; + VkImageFormatListCreateInfoKHR list_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR, + .pViewFormats = view_formats, + .viewFormatCount = 2, + }; + VkImageCreateInfo img_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .format = texture->format->vk, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .extent = (VkExtent3D) { width, height, 1 }, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = vulkan_shm_tex_usage, + .pNext = fmt->shm.has_mutable_srgb ? &list_info : NULL, + }; + if (fmt->shm.has_mutable_srgb) { + img_info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + } + + res = vkCreateImage(dev, &img_info, NULL, &texture->image); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateImage failed", res); + goto error; + } + + VkMemoryRequirements mem_reqs; + vkGetImageMemoryRequirements(dev, texture->image, &mem_reqs); + + int mem_type_index = vulkan_find_mem_type(renderer->dev, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mem_reqs.memoryTypeBits); + if (mem_type_index == -1) { + wlr_log(WLR_ERROR, "failed to find suitable vulkan memory type"); + goto error; + } + + VkMemoryAllocateInfo mem_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = mem_reqs.size, + .memoryTypeIndex = mem_type_index, + }; + + res = vkAllocateMemory(dev, &mem_info, NULL, &texture->memories[0]); + if (res != VK_SUCCESS) { + wlr_vk_error("vkAllocatorMemory failed", res); + goto error; + } + + texture->mem_count = 1; + res = vkBindImageMemory(dev, texture->image, texture->memories[0], 0); + if (res != VK_SUCCESS) { + wlr_vk_error("vkBindMemory failed", res); + goto error; + } + + pixman_region32_t region; + pixman_region32_init_rect(®ion, 0, 0, width, height); + if (!write_pixels(texture, stride, ®ion, data, VK_IMAGE_LAYOUT_UNDEFINED, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0)) { + goto error; + } + + return &texture->wlr_texture; + +error: + vulkan_texture_destroy(texture); + return NULL; +} + +static bool is_dmabuf_disjoint(const struct wlr_dmabuf_attributes *attribs) { + if (attribs->n_planes == 1) { + return false; + } + + struct stat first_stat; + if (fstat(attribs->fd[0], &first_stat) != 0) { + wlr_log_errno(WLR_ERROR, "fstat failed"); + return true; + } + + for (int i = 1; i < attribs->n_planes; i++) { + struct stat plane_stat; + if (fstat(attribs->fd[i], &plane_stat) != 0) { + wlr_log_errno(WLR_ERROR, "fstat failed"); + return true; + } + + if (first_stat.st_ino != plane_stat.st_ino) { + return true; + } + } + + return false; +} + +VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, + const struct wlr_dmabuf_attributes *attribs, + VkDeviceMemory mems[static WLR_DMABUF_MAX_PLANES], uint32_t *n_mems, + bool for_render, bool *using_mutable_srgb) { + VkResult res; + VkDevice dev = renderer->dev->dev; + *n_mems = 0u; + + struct wlr_vk_format_props *fmt = vulkan_format_props_from_drm(renderer->dev, + attribs->format); + if (fmt == NULL) { + char *format_name = drmGetFormatName(attribs->format); + wlr_log(WLR_ERROR, "Unsupported pixel format %s (0x%08"PRIX32")", + format_name, attribs->format); + free(format_name); + return VK_NULL_HANDLE; + } + + uint32_t plane_count = attribs->n_planes; + assert(plane_count < WLR_DMABUF_MAX_PLANES); + const struct wlr_vk_format_modifier_props *mod = + vulkan_format_props_find_modifier(fmt, attribs->modifier, for_render); + if (!mod) { + char *format_name = drmGetFormatName(attribs->format); + char *modifier_name = drmGetFormatModifierName(attribs->modifier); + wlr_log(WLR_ERROR, "Format %s (0x%08"PRIX32") can't be used with modifier " + "%s (0x%016"PRIX64")", format_name, attribs->format, + modifier_name, attribs->modifier); + free(format_name); + free(modifier_name); + return VK_NULL_HANDLE; + } + + if ((uint32_t) attribs->width > mod->max_extent.width || + (uint32_t) attribs->height > mod->max_extent.height) { + wlr_log(WLR_ERROR, "DMA-BUF is too large to import"); + return VK_NULL_HANDLE; + } + + if (mod->props.drmFormatModifierPlaneCount != plane_count) { + wlr_log(WLR_ERROR, "Number of planes (%d) does not match format (%d)", + plane_count, mod->props.drmFormatModifierPlaneCount); + return VK_NULL_HANDLE; + } + + // check if we have to create the image disjoint + bool disjoint = is_dmabuf_disjoint(attribs); + if (disjoint && !(mod->props.drmFormatModifierTilingFeatures + & VK_FORMAT_FEATURE_DISJOINT_BIT)) { + wlr_log(WLR_ERROR, "Format/Modifier does not support disjoint images"); + return VK_NULL_HANDLE; + } + + VkExternalMemoryHandleTypeFlagBits htype = + VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; + + VkImageCreateInfo img_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .format = fmt->format.vk, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .extent = (VkExtent3D) { attribs->width, attribs->height, 1 }, + .usage = for_render ? vulkan_render_usage : vulkan_dma_tex_usage, + }; + if (disjoint) { + img_info.flags = VK_IMAGE_CREATE_DISJOINT_BIT; + } + if (mod->has_mutable_srgb) { + img_info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + } + + VkExternalMemoryImageCreateInfo eimg = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + img_info.pNext = &eimg; + + VkSubresourceLayout plane_layouts[WLR_DMABUF_MAX_PLANES] = {0}; + + img_info.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + for (unsigned i = 0u; i < plane_count; ++i) { + plane_layouts[i].offset = attribs->offset[i]; + plane_layouts[i].rowPitch = attribs->stride[i]; + plane_layouts[i].size = 0; + } + + VkImageDrmFormatModifierExplicitCreateInfoEXT mod_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT, + .drmFormatModifierPlaneCount = plane_count, + .drmFormatModifier = mod->props.drmFormatModifier, + .pPlaneLayouts = plane_layouts, + }; + eimg.pNext = &mod_info; + + VkFormat view_formats[2] = { + fmt->format.vk, + fmt->format.vk_srgb, + }; + VkImageFormatListCreateInfoKHR list_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR, + .pViewFormats = view_formats, + .viewFormatCount = 2, + }; + if (mod->has_mutable_srgb) { + mod_info.pNext = &list_info; + } + + VkImage image; + res = vkCreateImage(dev, &img_info, NULL, &image); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateImage", res); + return VK_NULL_HANDLE; + } + + unsigned mem_count = disjoint ? plane_count : 1u; + VkBindImageMemoryInfo bindi[WLR_DMABUF_MAX_PLANES] = {0}; + VkBindImagePlaneMemoryInfo planei[WLR_DMABUF_MAX_PLANES] = {0}; + + for (unsigned i = 0u; i < mem_count; ++i) { + VkMemoryFdPropertiesKHR fdp = { + .sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR, + }; + res = renderer->dev->api.vkGetMemoryFdPropertiesKHR(dev, htype, + attribs->fd[i], &fdp); + if (res != VK_SUCCESS) { + wlr_vk_error("getMemoryFdPropertiesKHR", res); + goto error_image; + } + + VkImageMemoryRequirementsInfo2 memri = { + .image = image, + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2, + }; + + VkImagePlaneMemoryRequirementsInfo planeri; + if (disjoint) { + planeri = (VkImagePlaneMemoryRequirementsInfo){ + .sType = VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO, + .planeAspect = mem_plane_aspect(i), + }; + memri.pNext = &planeri; + } + + VkMemoryRequirements2 memr = { + .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, + }; + + vkGetImageMemoryRequirements2(dev, &memri, &memr); + int mem = vulkan_find_mem_type(renderer->dev, 0, + memr.memoryRequirements.memoryTypeBits & fdp.memoryTypeBits); + if (mem < 0) { + wlr_log(WLR_ERROR, "no valid memory type index"); + goto error_image; + } + + // Since importing transfers ownership of the FD to Vulkan, we have + // to duplicate it since this operation does not transfer ownership + // of the attribs to this texture. Will be closed by Vulkan on + // vkFreeMemory. + int dfd = fcntl(attribs->fd[i], F_DUPFD_CLOEXEC, 0); + if (dfd < 0) { + wlr_log_errno(WLR_ERROR, "fcntl(F_DUPFD_CLOEXEC) failed"); + goto error_image; + } + + VkMemoryAllocateInfo memi = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memr.memoryRequirements.size, + .memoryTypeIndex = mem, + }; + + VkImportMemoryFdInfoKHR importi = { + .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, + .fd = dfd, + .handleType = htype, + }; + memi.pNext = &importi; + + VkMemoryDedicatedAllocateInfo dedi = { + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, + .image = image, + }; + importi.pNext = &dedi; + + res = vkAllocateMemory(dev, &memi, NULL, &mems[i]); + if (res != VK_SUCCESS) { + close(dfd); + wlr_vk_error("vkAllocateMemory failed", res); + goto error_image; + } + + ++(*n_mems); + + // fill bind info + bindi[i].image = image; + bindi[i].memory = mems[i]; + bindi[i].memoryOffset = 0; + bindi[i].sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; + + if (disjoint) { + planei[i].sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO; + planei[i].planeAspect = planeri.planeAspect; + bindi[i].pNext = &planei[i]; + } + } + + res = vkBindImageMemory2(dev, mem_count, bindi); + if (res != VK_SUCCESS) { + wlr_vk_error("vkBindMemory failed", res); + goto error_image; + } + + *using_mutable_srgb = mod->has_mutable_srgb; + return image; + +error_image: + vkDestroyImage(dev, image, NULL); + for (size_t i = 0u; i < *n_mems; ++i) { + vkFreeMemory(dev, mems[i], NULL); + mems[i] = VK_NULL_HANDLE; + } + + return VK_NULL_HANDLE; +} + +static struct wlr_vk_texture *vulkan_texture_from_dmabuf( + struct wlr_vk_renderer *renderer, + struct wlr_dmabuf_attributes *attribs) { + const struct wlr_vk_format_props *fmt = vulkan_format_props_from_drm( + renderer->dev, attribs->format); + if (fmt == NULL) { + char *format_name = drmGetFormatName(attribs->format); + wlr_log(WLR_ERROR, "Unsupported pixel format %s (0x%08"PRIX32")", + format_name, attribs->format); + free(format_name); + return NULL; + } + + struct wlr_vk_texture *texture = vulkan_texture_create(renderer, + attribs->width, attribs->height); + if (texture == NULL) { + return NULL; + } + + bool using_mutable_srgb = false; + texture->image = vulkan_import_dmabuf(renderer, attribs, + texture->memories, &texture->mem_count, false, &using_mutable_srgb); + if (!texture->image) { + goto error; + } + texture_set_format(texture, &fmt->format, using_mutable_srgb); + + texture->dmabuf_imported = true; + + return texture; + +error: + vulkan_texture_destroy(texture); + return NULL; +} + +static void texture_handle_buffer_destroy(struct wlr_addon *addon) { + struct wlr_vk_texture *texture = + wl_container_of(addon, texture, buffer_addon); + // We might keep the texture around, waiting for pending command buffers to + // complete before free'ing descriptor sets. + vulkan_texture_destroy(texture); +} + +static const struct wlr_addon_interface buffer_addon_impl = { + .name = "wlr_vk_texture", + .destroy = texture_handle_buffer_destroy, +}; + +static struct wlr_texture *vulkan_texture_from_dmabuf_buffer( + struct wlr_vk_renderer *renderer, struct wlr_buffer *buffer, + struct wlr_dmabuf_attributes *dmabuf) { + struct wlr_addon *addon = + wlr_addon_find(&buffer->addons, renderer, &buffer_addon_impl); + if (addon != NULL) { + struct wlr_vk_texture *texture = + wl_container_of(addon, texture, buffer_addon); + wlr_buffer_lock(texture->buffer); + return &texture->wlr_texture; + } + + struct wlr_vk_texture *texture = vulkan_texture_from_dmabuf(renderer, dmabuf); + if (texture == NULL) { + return NULL; + } + + texture->buffer = wlr_buffer_lock(buffer); + wlr_addon_init(&texture->buffer_addon, &buffer->addons, renderer, + &buffer_addon_impl); + + return &texture->wlr_texture; +} + +struct wlr_texture *vulkan_texture_from_buffer(struct wlr_renderer *wlr_renderer, + struct wlr_buffer *buffer) { + struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer); + + void *data; + uint32_t format; + size_t stride; + struct wlr_dmabuf_attributes dmabuf; + if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) { + return vulkan_texture_from_dmabuf_buffer(renderer, buffer, &dmabuf); + } else if (wlr_buffer_begin_data_ptr_access(buffer, + WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { + struct wlr_texture *tex = vulkan_texture_from_pixels(renderer, + format, stride, buffer->width, buffer->height, data); + wlr_buffer_end_data_ptr_access(buffer); + return tex; + } else { + return NULL; + } +} + +void wlr_vk_texture_get_image_attribs(struct wlr_texture *texture, + struct wlr_vk_image_attribs *attribs) { + struct wlr_vk_texture *vk_texture = vulkan_get_texture(texture); + attribs->image = vk_texture->image; + attribs->format = vk_texture->format->vk; + attribs->layout = vk_texture->transitioned ? + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED; +} + +bool wlr_vk_texture_has_alpha(struct wlr_texture *texture) { + struct wlr_vk_texture *vk_texture = vulkan_get_texture(texture); + return vk_texture->has_alpha; +} diff --git a/render/vulkan/util.c b/render/vulkan/util.c new file mode 100644 index 0000000..8c31dc7 --- /dev/null +++ b/render/vulkan/util.c @@ -0,0 +1,78 @@ +#include +#include +#include "render/vulkan.h" + +int vulkan_find_mem_type(struct wlr_vk_device *dev, + VkMemoryPropertyFlags flags, uint32_t req_bits) { + VkPhysicalDeviceMemoryProperties props; + vkGetPhysicalDeviceMemoryProperties(dev->phdev, &props); + + for (unsigned i = 0u; i < props.memoryTypeCount; ++i) { + if (req_bits & (1 << i)) { + if ((props.memoryTypes[i].propertyFlags & flags) == flags) { + return i; + } + } + } + + return -1; +} + +const char *vulkan_strerror(VkResult err) { +#define ERR_STR(r) case VK_ ##r: return #r + switch (err) { + ERR_STR(SUCCESS); + ERR_STR(NOT_READY); + ERR_STR(TIMEOUT); + ERR_STR(EVENT_SET); + ERR_STR(EVENT_RESET); + ERR_STR(INCOMPLETE); + ERR_STR(ERROR_OUT_OF_HOST_MEMORY); + ERR_STR(ERROR_OUT_OF_DEVICE_MEMORY); + ERR_STR(ERROR_INITIALIZATION_FAILED); + ERR_STR(ERROR_DEVICE_LOST); + ERR_STR(ERROR_MEMORY_MAP_FAILED); + ERR_STR(ERROR_LAYER_NOT_PRESENT); + ERR_STR(ERROR_EXTENSION_NOT_PRESENT); + ERR_STR(ERROR_FEATURE_NOT_PRESENT); + ERR_STR(ERROR_INCOMPATIBLE_DRIVER); + ERR_STR(ERROR_TOO_MANY_OBJECTS); + ERR_STR(ERROR_FORMAT_NOT_SUPPORTED); + ERR_STR(ERROR_FRAGMENTED_POOL); + ERR_STR(ERROR_UNKNOWN); + ERR_STR(ERROR_OUT_OF_POOL_MEMORY); + ERR_STR(ERROR_INVALID_EXTERNAL_HANDLE); + ERR_STR(ERROR_FRAGMENTATION); + ERR_STR(ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS); + ERR_STR(PIPELINE_COMPILE_REQUIRED); + ERR_STR(ERROR_SURFACE_LOST_KHR); + ERR_STR(ERROR_NATIVE_WINDOW_IN_USE_KHR); + ERR_STR(SUBOPTIMAL_KHR); + ERR_STR(ERROR_OUT_OF_DATE_KHR); + ERR_STR(ERROR_INCOMPATIBLE_DISPLAY_KHR); + ERR_STR(ERROR_VALIDATION_FAILED_EXT); + ERR_STR(ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT); + default: + return ""; + } +#undef ERR_STR +} + +void vulkan_change_layout(VkCommandBuffer cb, VkImage img, + VkImageLayout ol, VkPipelineStageFlags srcs, VkAccessFlags srca, + VkImageLayout nl, VkPipelineStageFlags dsts, VkAccessFlags dsta) { + VkImageMemoryBarrier barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .oldLayout = ol, + .newLayout = nl, + .image = img, + .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .subresourceRange.layerCount = 1, + .subresourceRange.levelCount = 1, + .srcAccessMask = srca, + .dstAccessMask = dsta, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + }; + vkCmdPipelineBarrier(cb, srcs, dsts, 0, 0, NULL, 0, NULL, 1, &barrier); +} diff --git a/render/vulkan/vulkan.c b/render/vulkan/vulkan.c new file mode 100644 index 0000000..54bfa77 --- /dev/null +++ b/render/vulkan/vulkan.c @@ -0,0 +1,668 @@ +#if defined(__FreeBSD__) +#undef _POSIX_C_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "render/dmabuf.h" +#include "render/vulkan.h" + +#if defined(__linux__) +#include +#endif + +static bool check_extension(const VkExtensionProperties *avail, + uint32_t avail_len, const char *name) { + for (size_t i = 0; i < avail_len; i++) { + if (strcmp(avail[i].extensionName, name) == 0) { + return true; + } + } + return false; +} + +static VkBool32 debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT *debug_data, + void *data) { + // we ignore some of the non-helpful warnings + static const char *const ignored[] = { + // notifies us that shader output is not consumed since + // we use the shared vertex buffer with uv output + "UNASSIGNED-CoreValidation-Shader-OutputNotConsumed", + }; + + if (debug_data->pMessageIdName) { + for (unsigned i = 0; i < sizeof(ignored) / sizeof(ignored[0]); ++i) { + if (strcmp(debug_data->pMessageIdName, ignored[i]) == 0) { + return false; + } + } + } + + enum wlr_log_importance importance; + switch (severity) { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + importance = WLR_ERROR; + break; + default: + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + importance = WLR_INFO; + break; + } + + wlr_log(importance, "%s (%s)", debug_data->pMessage, + debug_data->pMessageIdName); + if (debug_data->queueLabelCount > 0) { + const char *name = debug_data->pQueueLabels[0].pLabelName; + if (name) { + wlr_log(importance, " last label '%s'", name); + } + } + + for (unsigned i = 0; i < debug_data->objectCount; ++i) { + if (debug_data->pObjects[i].pObjectName) { + wlr_log(importance, " involving '%s'", debug_data->pMessage); + } + } + + return false; +} + +struct wlr_vk_instance *vulkan_instance_create(bool debug) { + // we require vulkan 1.1 + PFN_vkEnumerateInstanceVersion pfEnumInstanceVersion = + (PFN_vkEnumerateInstanceVersion) + vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceVersion"); + if (!pfEnumInstanceVersion) { + wlr_log(WLR_ERROR, "wlroots requires vulkan 1.1 which is not available"); + return NULL; + } + + uint32_t ini_version; + if (pfEnumInstanceVersion(&ini_version) != VK_SUCCESS || + ini_version < VK_API_VERSION_1_1) { + wlr_log(WLR_ERROR, "wlroots requires vulkan 1.1 which is not available"); + return NULL; + } + + uint32_t avail_extc = 0; + VkResult res; + res = vkEnumerateInstanceExtensionProperties(NULL, &avail_extc, NULL); + if ((res != VK_SUCCESS) || (avail_extc == 0)) { + wlr_vk_error("Could not enumerate instance extensions (1)", res); + return NULL; + } + + VkExtensionProperties avail_ext_props[avail_extc + 1]; + res = vkEnumerateInstanceExtensionProperties(NULL, &avail_extc, + avail_ext_props); + if (res != VK_SUCCESS) { + wlr_vk_error("Could not enumerate instance extensions (2)", res); + return NULL; + } + + for (size_t j = 0; j < avail_extc; ++j) { + wlr_log(WLR_DEBUG, "Vulkan instance extension %s v%"PRIu32, + avail_ext_props[j].extensionName, avail_ext_props[j].specVersion); + } + + struct wlr_vk_instance *ini = calloc(1, sizeof(*ini)); + if (!ini) { + wlr_log_errno(WLR_ERROR, "allocation failed"); + return NULL; + } + + size_t extensions_len = 0; + const char *extensions[1] = {0}; + + bool debug_utils_found = false; + if (debug && check_extension(avail_ext_props, avail_extc, + VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { + debug_utils_found = true; + extensions[extensions_len++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + } + + assert(extensions_len <= sizeof(extensions) / sizeof(extensions[0])); + + VkApplicationInfo application_info = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pEngineName = "wlroots", + .engineVersion = WLR_VERSION_NUM, + .apiVersion = VK_API_VERSION_1_1, + }; + + VkInstanceCreateInfo instance_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &application_info, + .enabledExtensionCount = extensions_len, + .ppEnabledExtensionNames = extensions, + .enabledLayerCount = 0, + .ppEnabledLayerNames = NULL, + }; + + VkDebugUtilsMessageSeverityFlagsEXT severity = + // VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + VkDebugUtilsMessageTypeFlagsEXT types = + // VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + + VkDebugUtilsMessengerCreateInfoEXT debug_info = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .messageSeverity = severity, + .messageType = types, + .pfnUserCallback = &debug_callback, + .pUserData = ini, + }; + + if (debug_utils_found) { + // already adding the debug utils messenger extension to + // instance creation gives us additional information during + // instance creation and destruction, can be useful for debugging + // layers/extensions not being found. + instance_info.pNext = &debug_info; + } + + res = vkCreateInstance(&instance_info, NULL, &ini->instance); + if (res != VK_SUCCESS) { + wlr_vk_error("Could not create instance", res); + goto error; + } + + if (debug_utils_found) { + ini->api.createDebugUtilsMessengerEXT = + (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr( + ini->instance, "vkCreateDebugUtilsMessengerEXT"); + ini->api.destroyDebugUtilsMessengerEXT = + (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr( + ini->instance, "vkDestroyDebugUtilsMessengerEXT"); + + if (ini->api.createDebugUtilsMessengerEXT) { + ini->api.createDebugUtilsMessengerEXT(ini->instance, + &debug_info, NULL, &ini->messenger); + } else { + wlr_log(WLR_ERROR, "vkCreateDebugUtilsMessengerEXT not found"); + } + } + + return ini; + +error: + vulkan_instance_destroy(ini); + return NULL; +} + +void vulkan_instance_destroy(struct wlr_vk_instance *ini) { + if (!ini) { + return; + } + + if (ini->messenger && ini->api.destroyDebugUtilsMessengerEXT) { + ini->api.destroyDebugUtilsMessengerEXT(ini->instance, + ini->messenger, NULL); + } + + if (ini->instance) { + vkDestroyInstance(ini->instance, NULL); + } + + free(ini); +} + +static void log_phdev(const VkPhysicalDeviceProperties *props) { + uint32_t vv_major = VK_VERSION_MAJOR(props->apiVersion); + uint32_t vv_minor = VK_VERSION_MINOR(props->apiVersion); + uint32_t vv_patch = VK_VERSION_PATCH(props->apiVersion); + + uint32_t dv_major = VK_VERSION_MAJOR(props->driverVersion); + uint32_t dv_minor = VK_VERSION_MINOR(props->driverVersion); + uint32_t dv_patch = VK_VERSION_PATCH(props->driverVersion); + + const char *dev_type = "unknown"; + switch (props->deviceType) { + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + dev_type = "integrated"; + break; + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + dev_type = "discrete"; + break; + case VK_PHYSICAL_DEVICE_TYPE_CPU: + dev_type = "cpu"; + break; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + dev_type = "vgpu"; + break; + default: + break; + } + + wlr_log(WLR_INFO, "Vulkan device: '%s'", props->deviceName); + wlr_log(WLR_INFO, " Device type: '%s'", dev_type); + wlr_log(WLR_INFO, " Supported API version: %u.%u.%u", vv_major, vv_minor, vv_patch); + wlr_log(WLR_INFO, " Driver version: %u.%u.%u", dv_major, dv_minor, dv_patch); +} + +VkPhysicalDevice vulkan_find_drm_phdev(struct wlr_vk_instance *ini, int drm_fd) { + VkResult res; + uint32_t num_phdevs; + + res = vkEnumeratePhysicalDevices(ini->instance, &num_phdevs, NULL); + if (res != VK_SUCCESS) { + wlr_vk_error("Could not retrieve physical devices", res); + return VK_NULL_HANDLE; + } + + VkPhysicalDevice phdevs[1 + num_phdevs]; + res = vkEnumeratePhysicalDevices(ini->instance, &num_phdevs, phdevs); + if (res != VK_SUCCESS) { + wlr_vk_error("Could not retrieve physical devices", res); + return VK_NULL_HANDLE; + } + + struct stat drm_stat = {0}; + if (fstat(drm_fd, &drm_stat) != 0) { + wlr_log_errno(WLR_ERROR, "fstat failed"); + return VK_NULL_HANDLE; + } + + for (uint32_t i = 0; i < num_phdevs; ++i) { + VkPhysicalDevice phdev = phdevs[i]; + + // check whether device supports vulkan 1.1, needed for + // vkGetPhysicalDeviceProperties2 + VkPhysicalDeviceProperties phdev_props; + vkGetPhysicalDeviceProperties(phdev, &phdev_props); + + log_phdev(&phdev_props); + + if (phdev_props.apiVersion < VK_API_VERSION_1_1) { + // NOTE: we could additionaly check whether the + // VkPhysicalDeviceProperties2KHR extension is supported but + // implementations not supporting 1.1 are unlikely in future + continue; + } + + // check for extensions + uint32_t avail_extc = 0; + res = vkEnumerateDeviceExtensionProperties(phdev, NULL, + &avail_extc, NULL); + if ((res != VK_SUCCESS) || (avail_extc == 0)) { + wlr_vk_error(" Could not enumerate device extensions", res); + continue; + } + + VkExtensionProperties avail_ext_props[avail_extc + 1]; + res = vkEnumerateDeviceExtensionProperties(phdev, NULL, + &avail_extc, avail_ext_props); + if (res != VK_SUCCESS) { + wlr_vk_error(" Could not enumerate device extensions", res); + continue; + } + + bool has_drm_props = check_extension(avail_ext_props, avail_extc, + VK_EXT_PHYSICAL_DEVICE_DRM_EXTENSION_NAME); + bool has_driver_props = check_extension(avail_ext_props, avail_extc, + VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME); + + VkPhysicalDeviceProperties2 props = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, + }; + + VkPhysicalDeviceDrmPropertiesEXT drm_props = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT, + }; + if (has_drm_props) { + drm_props.pNext = props.pNext; + props.pNext = &drm_props; + } + + VkPhysicalDeviceDriverPropertiesKHR driver_props = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES, + }; + if (has_driver_props) { + driver_props.pNext = props.pNext; + props.pNext = &driver_props; + } + + vkGetPhysicalDeviceProperties2(phdev, &props); + + if (has_driver_props) { + wlr_log(WLR_INFO, " Driver name: %s (%s)", driver_props.driverName, driver_props.driverInfo); + } + + if (!has_drm_props) { + wlr_log(WLR_DEBUG, " Ignoring physical device \"%s\": " + "VK_EXT_physical_device_drm not supported", + phdev_props.deviceName); + continue; + } + + dev_t primary_devid = makedev(drm_props.primaryMajor, drm_props.primaryMinor); + dev_t render_devid = makedev(drm_props.renderMajor, drm_props.renderMinor); + if (primary_devid == drm_stat.st_rdev || + render_devid == drm_stat.st_rdev) { + wlr_log(WLR_INFO, "Found matching Vulkan physical device: %s", + phdev_props.deviceName); + return phdev; + } + } + + return VK_NULL_HANDLE; +} + +int vulkan_open_phdev_drm_fd(VkPhysicalDevice phdev) { + // vulkan_find_drm_phdev() already checks that VK_EXT_physical_device_drm + // is supported + VkPhysicalDeviceDrmPropertiesEXT drm_props = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT, + }; + VkPhysicalDeviceProperties2 props = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, + .pNext = &drm_props, + }; + vkGetPhysicalDeviceProperties2(phdev, &props); + + dev_t devid; + if (drm_props.hasRender) { + devid = makedev(drm_props.renderMajor, drm_props.renderMinor); + } else if (drm_props.hasPrimary) { + devid = makedev(drm_props.primaryMajor, drm_props.primaryMinor); + } else { + wlr_log(WLR_ERROR, "Physical device is missing both render and primary nodes"); + return -1; + } + + drmDevice *device = NULL; + if (drmGetDeviceFromDevId(devid, 0, &device) != 0) { + wlr_log_errno(WLR_ERROR, "drmGetDeviceFromDevId failed"); + return -1; + } + + const char *name = NULL; + if (device->available_nodes & (1 << DRM_NODE_RENDER)) { + name = device->nodes[DRM_NODE_RENDER]; + } else { + assert(device->available_nodes & (1 << DRM_NODE_PRIMARY)); + name = device->nodes[DRM_NODE_PRIMARY]; + wlr_log(WLR_DEBUG, "DRM device %s has no render node, " + "falling back to primary node", name); + } + + int drm_fd = open(name, O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (drm_fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open DRM node %s", name); + } + drmFreeDevice(&device); + return drm_fd; +} + +static void load_device_proc(struct wlr_vk_device *dev, const char *name, + void *proc_ptr) { + void *proc = (void *)vkGetDeviceProcAddr(dev->dev, name); + if (proc == NULL) { + abort(); + } + *(void **)proc_ptr = proc; +} + +struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, + VkPhysicalDevice phdev) { + VkResult res; + + uint32_t avail_extc = 0; + res = vkEnumerateDeviceExtensionProperties(phdev, NULL, + &avail_extc, NULL); + if (res != VK_SUCCESS || avail_extc == 0) { + wlr_vk_error("Could not enumerate device extensions (1)", res); + return NULL; + } + + VkExtensionProperties avail_ext_props[avail_extc + 1]; + res = vkEnumerateDeviceExtensionProperties(phdev, NULL, + &avail_extc, avail_ext_props); + if (res != VK_SUCCESS) { + wlr_vk_error("Could not enumerate device extensions (2)", res); + return NULL; + } + + for (size_t j = 0; j < avail_extc; ++j) { + wlr_log(WLR_DEBUG, "Vulkan device extension %s v%"PRIu32, + avail_ext_props[j].extensionName, avail_ext_props[j].specVersion); + } + + struct wlr_vk_device *dev = calloc(1, sizeof(*dev)); + if (!dev) { + wlr_log_errno(WLR_ERROR, "allocation failed"); + return NULL; + } + + dev->phdev = phdev; + dev->instance = ini; + dev->drm_fd = -1; + + // For dmabuf import we require at least the external_memory_fd, + // external_memory_dma_buf, queue_family_foreign, + // image_drm_format_modifier, and image_format_list extensions. + // The size is set to a large number to allow for other conditional + // extensions before the device is created + const char *extensions[32] = {0}; + size_t extensions_len = 0; + extensions[extensions_len++] = VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME; + extensions[extensions_len++] = VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME; + extensions[extensions_len++] = VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME; // or vulkan 1.2 + extensions[extensions_len++] = VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME; + extensions[extensions_len++] = VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME; + extensions[extensions_len++] = VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME; + extensions[extensions_len++] = VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME; // or vulkan 1.2 + extensions[extensions_len++] = VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME; // or vulkan 1.3 + + for (size_t i = 0; i < extensions_len; i++) { + if (!check_extension(avail_ext_props, avail_extc, extensions[i])) { + wlr_log(WLR_ERROR, "vulkan: required device extension %s not found", + extensions[i]); + goto error; + } + } + + { + uint32_t qfam_count; + vkGetPhysicalDeviceQueueFamilyProperties(phdev, &qfam_count, NULL); + assert(qfam_count > 0); + VkQueueFamilyProperties queue_props[qfam_count]; + vkGetPhysicalDeviceQueueFamilyProperties(phdev, &qfam_count, + queue_props); + + bool graphics_found = false; + for (unsigned i = 0u; i < qfam_count; ++i) { + graphics_found = queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT; + if (graphics_found) { + dev->queue_family = i; + break; + } + } + assert(graphics_found); + } + + const VkPhysicalDeviceExternalSemaphoreInfo ext_semaphore_info = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + }; + VkExternalSemaphoreProperties ext_semaphore_props = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, + }; + vkGetPhysicalDeviceExternalSemaphoreProperties(phdev, + &ext_semaphore_info, &ext_semaphore_props); + bool exportable_semaphore = ext_semaphore_props.externalSemaphoreFeatures & + VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT; + bool importable_semaphore = ext_semaphore_props.externalSemaphoreFeatures & + VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT; + if (!exportable_semaphore) { + wlr_log(WLR_DEBUG, "VkSemaphore is not exportable to a sync_file"); + } + if (!importable_semaphore) { + wlr_log(WLR_DEBUG, "VkSemaphore is not importable from a sync_file"); + } + + bool dmabuf_sync_file_import_export = dmabuf_check_sync_file_import_export(); + if (!dmabuf_sync_file_import_export) { + wlr_log(WLR_DEBUG, "DMA-BUF sync_file import/export not supported"); + } + + dev->implicit_sync_interop = + exportable_semaphore && importable_semaphore && dmabuf_sync_file_import_export; + if (dev->implicit_sync_interop) { + wlr_log(WLR_DEBUG, "Implicit sync interop supported"); + } else { + wlr_log(WLR_INFO, "Implicit sync interop not supported, " + "falling back to blocking"); + } + + VkPhysicalDeviceSamplerYcbcrConversionFeatures phdev_sampler_ycbcr_features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, + }; + VkPhysicalDeviceFeatures2 phdev_features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, + .pNext = &phdev_sampler_ycbcr_features, + }; + vkGetPhysicalDeviceFeatures2(phdev, &phdev_features); + + dev->sampler_ycbcr_conversion = phdev_sampler_ycbcr_features.samplerYcbcrConversion; + wlr_log(WLR_DEBUG, "Sampler YCbCr conversion %s", + dev->sampler_ycbcr_conversion ? "supported" : "not supported"); + + const float prio = 1.f; + VkDeviceQueueCreateInfo qinfo = { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = dev->queue_family, + .queueCount = 1, + .pQueuePriorities = &prio, + }; + + VkDeviceQueueGlobalPriorityCreateInfoKHR global_priority; + bool has_global_priority = check_extension(avail_ext_props, avail_extc, + VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME); + if (has_global_priority) { + // If global priorities are supported, request a high-priority context + global_priority = (VkDeviceQueueGlobalPriorityCreateInfoKHR){ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_KHR, + .globalPriority = VK_QUEUE_GLOBAL_PRIORITY_HIGH_KHR, + }; + qinfo.pNext = &global_priority; + extensions[extensions_len++] = VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME; + wlr_log(WLR_DEBUG, "Requesting a high-priority device queue"); + } else { + wlr_log(WLR_DEBUG, "Global priorities are not supported, " + "falling back to regular queue priority"); + } + + VkPhysicalDeviceSamplerYcbcrConversionFeatures sampler_ycbcr_features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, + .samplerYcbcrConversion = dev->sampler_ycbcr_conversion, + }; + VkPhysicalDeviceSynchronization2FeaturesKHR sync2_features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES_KHR, + .pNext = &sampler_ycbcr_features, + .synchronization2 = VK_TRUE, + }; + VkPhysicalDeviceTimelineSemaphoreFeaturesKHR timeline_features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR, + .pNext = &sync2_features, + .timelineSemaphore = VK_TRUE, + }; + VkDeviceCreateInfo dev_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = &timeline_features, + .queueCreateInfoCount = 1u, + .pQueueCreateInfos = &qinfo, + .enabledExtensionCount = extensions_len, + .ppEnabledExtensionNames = extensions, + }; + + assert(extensions_len < sizeof(extensions) / sizeof(extensions[0])); + + res = vkCreateDevice(phdev, &dev_info, NULL, &dev->dev); + + if (has_global_priority && (res == VK_ERROR_NOT_PERMITTED_EXT || + res == VK_ERROR_INITIALIZATION_FAILED)) { + // Try to recover from the driver denying a global priority queue + wlr_log(WLR_DEBUG, "Failed to obtain a high-priority device queue, " + "falling back to regular queue priority"); + qinfo.pNext = NULL; + res = vkCreateDevice(phdev, &dev_info, NULL, &dev->dev); + } + + if (res != VK_SUCCESS) { + wlr_vk_error("Failed to create vulkan device", res); + goto error; + } + + vkGetDeviceQueue(dev->dev, dev->queue_family, 0, &dev->queue); + + load_device_proc(dev, "vkGetMemoryFdPropertiesKHR", + &dev->api.vkGetMemoryFdPropertiesKHR); + load_device_proc(dev, "vkWaitSemaphoresKHR", &dev->api.vkWaitSemaphoresKHR); + load_device_proc(dev, "vkGetSemaphoreCounterValueKHR", + &dev->api.vkGetSemaphoreCounterValueKHR); + load_device_proc(dev, "vkGetSemaphoreFdKHR", &dev->api.vkGetSemaphoreFdKHR); + load_device_proc(dev, "vkImportSemaphoreFdKHR", &dev->api.vkImportSemaphoreFdKHR); + load_device_proc(dev, "vkQueueSubmit2KHR", &dev->api.vkQueueSubmit2KHR); + + size_t max_fmts; + const struct wlr_vk_format *fmts = vulkan_get_format_list(&max_fmts); + dev->shm_formats = calloc(max_fmts, sizeof(*dev->shm_formats)); + dev->format_props = calloc(max_fmts, sizeof(*dev->format_props)); + if (!dev->shm_formats || !dev->format_props) { + wlr_log_errno(WLR_ERROR, "allocation failed"); + goto error; + } + + wlr_log(WLR_DEBUG, "Supported Vulkan formats:"); + for (unsigned i = 0u; i < max_fmts; ++i) { + vulkan_format_props_query(dev, &fmts[i]); + } + + return dev; + +error: + vulkan_device_destroy(dev); + return NULL; +} + +void vulkan_device_destroy(struct wlr_vk_device *dev) { + if (!dev) { + return; + } + + if (dev->dev) { + vkDestroyDevice(dev->dev, NULL); + } + + if (dev->drm_fd > 0) { + close(dev->drm_fd); + } + + wlr_drm_format_set_finish(&dev->dmabuf_render_formats); + wlr_drm_format_set_finish(&dev->dmabuf_texture_formats); + + for (unsigned i = 0u; i < dev->format_prop_count; ++i) { + vulkan_format_props_finish(&dev->format_props[i]); + } + + free(dev->shm_formats); + free(dev->format_props); + free(dev); +} diff --git a/render/wlr_renderer.c b/render/wlr_renderer.c new file mode 100644 index 0000000..513fecb --- /dev/null +++ b/render/wlr_renderer.c @@ -0,0 +1,337 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if WLR_HAS_GLES2_RENDERER +#include +#include +#endif + +#if WLR_HAS_VULKAN_RENDERER +#include +#endif // WLR_HAS_VULKAN_RENDERER + +#include "backend/backend.h" +#include "render/pixel_format.h" +#include "render/wlr_renderer.h" +#include "util/env.h" + +void wlr_renderer_init(struct wlr_renderer *renderer, + const struct wlr_renderer_impl *impl) { + assert(impl->begin_buffer_pass); + assert(impl->get_shm_texture_formats); + assert(impl->get_render_buffer_caps); + + *renderer = (struct wlr_renderer){ + .impl = impl, + }; + + wl_signal_init(&renderer->events.destroy); + wl_signal_init(&renderer->events.lost); +} + +void wlr_renderer_destroy(struct wlr_renderer *r) { + if (!r) { + return; + } + + wl_signal_emit_mutable(&r->events.destroy, r); + + if (r->impl && r->impl->destroy) { + r->impl->destroy(r); + } else { + free(r); + } +} + +const uint32_t *wlr_renderer_get_shm_texture_formats(struct wlr_renderer *r, + size_t *len) { + return r->impl->get_shm_texture_formats(r, len); +} + +const struct wlr_drm_format_set *wlr_renderer_get_dmabuf_texture_formats( + struct wlr_renderer *r) { + if (!r->impl->get_dmabuf_texture_formats) { + return NULL; + } + return r->impl->get_dmabuf_texture_formats(r); +} + +const struct wlr_drm_format_set *wlr_renderer_get_render_formats( + struct wlr_renderer *r) { + if (!r->impl->get_render_formats) { + return NULL; + } + return r->impl->get_render_formats(r); +} + +uint32_t renderer_get_render_buffer_caps(struct wlr_renderer *r) { + return r->impl->get_render_buffer_caps(r); +} + +bool wlr_renderer_init_wl_shm(struct wlr_renderer *r, + struct wl_display *wl_display) { + return wlr_shm_create_with_renderer(wl_display, 1, r) != NULL; +} + +bool wlr_renderer_init_wl_display(struct wlr_renderer *r, + struct wl_display *wl_display) { + if (!wlr_renderer_init_wl_shm(r, wl_display)) { + return false; + } + + if (wlr_renderer_get_dmabuf_texture_formats(r) != NULL && + wlr_renderer_get_drm_fd(r) >= 0 && + wlr_linux_dmabuf_v1_create_with_renderer(wl_display, 4, r) == NULL) { + return false; + } + + return true; +} + +static int open_drm_render_node(void) { + uint32_t flags = 0; + int devices_len = drmGetDevices2(flags, NULL, 0); + if (devices_len < 0) { + wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len)); + return -1; + } + drmDevice **devices = calloc(devices_len, sizeof(*devices)); + if (devices == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return -1; + } + devices_len = drmGetDevices2(flags, devices, devices_len); + if (devices_len < 0) { + free(devices); + wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len)); + return -1; + } + + int fd = -1; + for (int i = 0; i < devices_len; i++) { + drmDevice *dev = devices[i]; + if (dev->available_nodes & (1 << DRM_NODE_RENDER)) { + const char *name = dev->nodes[DRM_NODE_RENDER]; + wlr_log(WLR_DEBUG, "Opening DRM render node '%s'", name); + fd = open(name, O_RDWR | O_CLOEXEC); + if (fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open '%s'", name); + goto out; + } + break; + } + } + if (fd < 0) { + wlr_log(WLR_ERROR, "Failed to find any DRM render node"); + } + +out: + for (int i = 0; i < devices_len; i++) { + drmFreeDevice(&devices[i]); + } + free(devices); + + return fd; +} + +static bool open_preferred_drm_fd(struct wlr_backend *backend, int *drm_fd_ptr, + bool *own_drm_fd) { + if (*drm_fd_ptr >= 0) { + return true; + } + + // Allow the user to override the render node + const char *render_name = getenv("WLR_RENDER_DRM_DEVICE"); + if (render_name != NULL) { + wlr_log(WLR_INFO, + "Opening DRM render node '%s' from WLR_RENDER_DRM_DEVICE", + render_name); + int drm_fd = open(render_name, O_RDWR | O_CLOEXEC); + if (drm_fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open '%s'", render_name); + return false; + } + if (drmGetNodeTypeFromFd(drm_fd) != DRM_NODE_RENDER) { + wlr_log(WLR_ERROR, "'%s' is not a DRM render node", render_name); + close(drm_fd); + return false; + } + *drm_fd_ptr = drm_fd; + *own_drm_fd = true; + return true; + } + + // Prefer the backend's DRM node, if any + int backend_drm_fd = wlr_backend_get_drm_fd(backend); + if (backend_drm_fd >= 0) { + *drm_fd_ptr = backend_drm_fd; + *own_drm_fd = false; + return true; + } + + // If the backend hasn't picked a DRM FD, but accepts DMA-BUFs, pick an + // arbitrary render node + uint32_t backend_caps = backend_get_buffer_caps(backend); + if (backend_caps & WLR_BUFFER_CAP_DMABUF) { + int drm_fd = open_drm_render_node(); + if (drm_fd < 0) { + return false; + } + *drm_fd_ptr = drm_fd; + *own_drm_fd = true; + return true; + } + + return false; +} + +static void log_creation_failure(bool is_auto, const char *msg) { + wlr_log(is_auto ? WLR_DEBUG : WLR_ERROR, "%s%s", msg, is_auto ? ". Skipping!" : ""); +} + +static bool has_render_node(struct wlr_backend *backend) { + if (!backend) { + return false; + } + + int backend_drm_fd = wlr_backend_get_drm_fd(backend); + if (backend_drm_fd < 0) { + return false; + } + + char *render_node = drmGetRenderDeviceNameFromFd(backend_drm_fd); + bool has_render_node = render_node != NULL; + free(render_node); + + return has_render_node; +} + +static struct wlr_renderer *renderer_autocreate(struct wlr_backend *backend, int drm_fd) { + const char *renderer_options[] = { + "auto", + "gles2", + "vulkan", + "pixman", + NULL + }; + + const char *renderer_name = renderer_options[env_parse_switch("WLR_RENDERER", renderer_options)]; + bool is_auto = strcmp(renderer_name, "auto") == 0; + struct wlr_renderer *renderer = NULL; + + bool own_drm_fd = false; + + if ((is_auto && WLR_HAS_GLES2_RENDERER) || strcmp(renderer_name, "gles2") == 0) { + if (!open_preferred_drm_fd(backend, &drm_fd, &own_drm_fd)) { + log_creation_failure(is_auto, "Cannot create GLES2 renderer: no DRM FD available"); + } else { +#if WLR_HAS_GLES2_RENDERER + renderer = wlr_gles2_renderer_create_with_drm_fd(drm_fd); +#else + wlr_log(WLR_ERROR, "Cannot create GLES renderer: disabled at compile-time"); +#endif + if (renderer) { + goto out; + } else { + log_creation_failure(is_auto, "Failed to create a GLES2 renderer"); + } + } + } + + if (strcmp(renderer_name, "vulkan") == 0) { + if (!open_preferred_drm_fd(backend, &drm_fd, &own_drm_fd)) { + log_creation_failure(is_auto, "Cannot create Vulkan renderer: no DRM FD available"); + } else { +#if WLR_HAS_VULKAN_RENDERER + renderer = wlr_vk_renderer_create_with_drm_fd(drm_fd); +#else + wlr_log(WLR_ERROR, "Cannot create Vulkan renderer: disabled at compile-time"); +#endif + if (renderer) { + goto out; + } else { + log_creation_failure(is_auto, "Failed to create a Vulkan renderer"); + } + } + } + + if ((is_auto && !has_render_node(backend)) || strcmp(renderer_name, "pixman") == 0) { + renderer = wlr_pixman_renderer_create(); + if (renderer) { + goto out; + } else { + log_creation_failure(is_auto, "Failed to create a pixman renderer"); + } + } + +out: + if (renderer == NULL) { + wlr_log(WLR_ERROR, "Could not initialize renderer"); + } + if (own_drm_fd && drm_fd >= 0) { + close(drm_fd); + } + return renderer; +} + +struct wlr_renderer *renderer_autocreate_with_drm_fd(int drm_fd) { + assert(drm_fd >= 0); + + return renderer_autocreate(NULL, drm_fd); +} + +struct wlr_renderer *wlr_renderer_autocreate(struct wlr_backend *backend) { + return renderer_autocreate(backend, -1); +} + +int wlr_renderer_get_drm_fd(struct wlr_renderer *r) { + if (!r->impl->get_drm_fd) { + return -1; + } + return r->impl->get_drm_fd(r); +} + +struct wlr_render_pass *wlr_renderer_begin_buffer_pass(struct wlr_renderer *renderer, + struct wlr_buffer *buffer, const struct wlr_buffer_pass_options *options) { + struct wlr_buffer_pass_options default_options = {0}; + if (!options) { + options = &default_options; + } + + return renderer->impl->begin_buffer_pass(renderer, buffer, options); +} + +struct wlr_render_timer *wlr_render_timer_create(struct wlr_renderer *renderer) { + if (!renderer->impl->render_timer_create) { + return NULL; + } + return renderer->impl->render_timer_create(renderer); +} + +int wlr_render_timer_get_duration_ns(struct wlr_render_timer *timer) { + if (!timer->impl->get_duration_ns) { + return -1; + } + return timer->impl->get_duration_ns(timer); +} + +void wlr_render_timer_destroy(struct wlr_render_timer *timer) { + if (!timer->impl->destroy) { + return; + } + timer->impl->destroy(timer); +} diff --git a/render/wlr_texture.c b/render/wlr_texture.c new file mode 100644 index 0000000..3526ee1 --- /dev/null +++ b/render/wlr_texture.c @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include +#include +#include +#include "render/pixel_format.h" +#include "types/wlr_buffer.h" + +void wlr_texture_init(struct wlr_texture *texture, struct wlr_renderer *renderer, + const struct wlr_texture_impl *impl, uint32_t width, uint32_t height) { + assert(renderer); + + *texture = (struct wlr_texture){ + .renderer = renderer, + .impl = impl, + .width = width, + .height = height, + }; +} + +void wlr_texture_destroy(struct wlr_texture *texture) { + if (texture && texture->impl && texture->impl->destroy) { + texture->impl->destroy(texture); + } else { + free(texture); + } +} + +void wlr_texture_read_pixels_options_get_src_box( + const struct wlr_texture_read_pixels_options *options, + const struct wlr_texture *texture, struct wlr_box *box) { + if (wlr_box_empty(&options->src_box)) { + *box = (struct wlr_box){ + .x = 0, + .y = 0, + .width = texture->width, + .height = texture->height, + }; + return; + } + + *box = options->src_box; +} + +void *wlr_texture_read_pixel_options_get_data( + const struct wlr_texture_read_pixels_options *options) { + const struct wlr_pixel_format_info *fmt = drm_get_pixel_format_info(options->format); + + return (char *)options->data + + pixel_format_info_min_stride(fmt, options->dst_x) + + options->dst_y * options->stride; +} + +bool wlr_texture_read_pixels(struct wlr_texture *texture, + const struct wlr_texture_read_pixels_options *options) { + if (!texture->impl->read_pixels) { + return false; + } + + return texture->impl->read_pixels(texture, options); +} + +uint32_t wlr_texture_preferred_read_format(struct wlr_texture *texture) { + if (!texture->impl->preferred_read_format) { + return DRM_FORMAT_INVALID; + } + + return texture->impl->preferred_read_format(texture); +} + +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) { + assert(width > 0); + assert(height > 0); + assert(stride > 0); + assert(data); + + struct wlr_readonly_data_buffer *buffer = + readonly_data_buffer_create(fmt, stride, width, height, data); + if (buffer == NULL) { + return NULL; + } + + struct wlr_texture *texture = + wlr_texture_from_buffer(renderer, &buffer->base); + + // By this point, the renderer should have locked the buffer if it still + // needs to access it in the future. + readonly_data_buffer_drop(buffer); + + return texture; +} + +struct wlr_texture *wlr_texture_from_dmabuf(struct wlr_renderer *renderer, + struct wlr_dmabuf_attributes *attribs) { + struct wlr_dmabuf_buffer *buffer = dmabuf_buffer_create(attribs); + if (buffer == NULL) { + return NULL; + } + + struct wlr_texture *texture = + wlr_texture_from_buffer(renderer, &buffer->base); + + // By this point, the renderer should have locked the buffer if it still + // needs to access it in the future. + dmabuf_buffer_drop(buffer); + + return texture; +} + +struct wlr_texture *wlr_texture_from_buffer(struct wlr_renderer *renderer, + struct wlr_buffer *buffer) { + if (!renderer->impl->texture_from_buffer) { + return NULL; + } + return renderer->impl->texture_from_buffer(renderer, buffer); +} + +bool wlr_texture_update_from_buffer(struct wlr_texture *texture, + struct wlr_buffer *buffer, const pixman_region32_t *damage) { + if (!texture->impl->update_from_buffer) { + return false; + } + if (texture->width != (uint32_t)buffer->width || + texture->height != (uint32_t)buffer->height) { + return false; + } + const pixman_box32_t *extents = pixman_region32_extents(damage); + if (extents->x1 < 0 || extents->y1 < 0 || extents->x2 > buffer->width || + extents->y2 > buffer->height) { + return false; + } + return texture->impl->update_from_buffer(texture, buffer, damage); +} diff --git a/tinywl/.gitignore b/tinywl/.gitignore new file mode 100644 index 0000000..2b5bb6f --- /dev/null +++ b/tinywl/.gitignore @@ -0,0 +1,3 @@ +tinywl +*-protocol.c +*-protocol.h diff --git a/tinywl/LICENSE b/tinywl/LICENSE new file mode 100644 index 0000000..0153f30 --- /dev/null +++ b/tinywl/LICENSE @@ -0,0 +1,125 @@ +This work is licensed under CC0, which effectively puts it in the public domain. + +--- + +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/tinywl/Makefile b/tinywl/Makefile new file mode 100644 index 0000000..982fc38 --- /dev/null +++ b/tinywl/Makefile @@ -0,0 +1,26 @@ +WAYLAND_PROTOCOLS=$(shell pkg-config --variable=pkgdatadir wayland-protocols) +WAYLAND_SCANNER=$(shell pkg-config --variable=wayland_scanner wayland-scanner) +LIBS=\ + $(shell pkg-config --cflags --libs "wlroots >= 0.18.0-dev") \ + $(shell pkg-config --cflags --libs wayland-server) \ + $(shell pkg-config --cflags --libs xkbcommon) + +# wayland-scanner is a tool which generates C headers and rigging for Wayland +# protocols, which are specified in XML. wlroots requires you to rig these up +# to your build system yourself and provide them in the include path. +xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ + +tinywl: tinywl.c xdg-shell-protocol.h + $(CC) $(CFLAGS) \ + -g -Werror -I. \ + -DWLR_USE_UNSTABLE \ + -o $@ $< \ + $(LIBS) + +clean: + rm -f tinywl xdg-shell-protocol.h xdg-shell-protocol.c + +.DEFAULT_GOAL=tinywl +.PHONY: clean diff --git a/tinywl/README.md b/tinywl/README.md new file mode 100644 index 0000000..7fc83b9 --- /dev/null +++ b/tinywl/README.md @@ -0,0 +1,45 @@ +# TinyWL + +This is the "minimum viable product" Wayland compositor based on wlroots. It +aims to implement a Wayland compositor in the fewest lines of code possible, +while still supporting a reasonable set of features. Reading this code is the +best starting point for anyone looking to build their own Wayland compositor +based on wlroots. + +## Building TinyWL + +TinyWL is disconnected from the main wlroots build system, in order to make it +easier to understand the build requirements for your own Wayland compositors. +Simply install the dependencies: + +- wlroots +- wayland-protocols + +And run `make`. + +## Running TinyWL + +You can run TinyWL with `./tinywl`. In an existing Wayland or X11 session, +tinywl will open a Wayland or X11 window respectively to act as a virtual +display. You can then open Wayland windows by setting `WAYLAND_DISPLAY` to the +value shown in the logs. You can also run `./tinywl` from a TTY. + +In either case, you will likely want to specify `-s [cmd]` to run a command at +startup, such as a terminal emulator. This will be necessary to start any new +programs from within the compositor, as TinyWL does not support any custom +keybindings. TinyWL supports the following keybindings: + +- `Alt+Escape`: Terminate the compositor +- `Alt+F1`: Cycle between windows + +## Limitations + +Notable omissions from TinyWL: + +- HiDPI support +- Any kind of configuration, e.g. output layout +- Any protocol other than xdg-shell (e.g. layer-shell, for + panels/taskbars/etc; or Xwayland, for proxied X11 windows) +- Optional protocols, e.g. screen capture, primary selection, virtual + keyboard, etc. Most of these are plug-and-play with wlroots, but they're + omitted for brevity. diff --git a/tinywl/meson.build b/tinywl/meson.build new file mode 100644 index 0000000..e727145 --- /dev/null +++ b/tinywl/meson.build @@ -0,0 +1,5 @@ +executable( + 'tinywl', + ['tinywl.c', protocols_server_header['xdg-shell']], + dependencies: wlroots, +) diff --git a/tinywl/tinywl.c b/tinywl/tinywl.c new file mode 100644 index 0000000..ac8b11e --- /dev/null +++ b/tinywl/tinywl.c @@ -0,0 +1,1071 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* For brevity's sake, struct members are annotated where they are used. */ +enum tinywl_cursor_mode { + TINYWL_CURSOR_PASSTHROUGH, + TINYWL_CURSOR_MOVE, + TINYWL_CURSOR_RESIZE, +}; + +struct tinywl_server { + struct wl_display *wl_display; + struct wlr_backend *backend; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + struct wlr_scene *scene; + struct wlr_scene_output_layout *scene_layout; + + struct wlr_xdg_shell *xdg_shell; + struct wl_listener new_xdg_toplevel; + struct wl_listener new_xdg_popup; + struct wl_list toplevels; + + struct wlr_cursor *cursor; + struct wlr_xcursor_manager *cursor_mgr; + struct wl_listener cursor_motion; + struct wl_listener cursor_motion_absolute; + struct wl_listener cursor_button; + struct wl_listener cursor_axis; + struct wl_listener cursor_frame; + + struct wlr_seat *seat; + struct wl_listener new_input; + struct wl_listener request_cursor; + struct wl_listener request_set_selection; + struct wl_list keyboards; + enum tinywl_cursor_mode cursor_mode; + struct tinywl_toplevel *grabbed_toplevel; + double grab_x, grab_y; + struct wlr_box grab_geobox; + uint32_t resize_edges; + + struct wlr_output_layout *output_layout; + struct wl_list outputs; + struct wl_listener new_output; +}; + +struct tinywl_output { + struct wl_list link; + struct tinywl_server *server; + struct wlr_output *wlr_output; + struct wl_listener frame; + struct wl_listener request_state; + struct wl_listener destroy; +}; + +struct tinywl_toplevel { + struct wl_list link; + struct tinywl_server *server; + struct wlr_xdg_toplevel *xdg_toplevel; + struct wlr_scene_tree *scene_tree; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener commit; + struct wl_listener destroy; + struct wl_listener request_move; + struct wl_listener request_resize; + struct wl_listener request_maximize; + struct wl_listener request_fullscreen; +}; + +struct tinywl_popup { + struct wlr_xdg_popup *xdg_popup; + struct wl_listener commit; + struct wl_listener destroy; +}; + +struct tinywl_keyboard { + struct wl_list link; + struct tinywl_server *server; + struct wlr_keyboard *wlr_keyboard; + + struct wl_listener modifiers; + struct wl_listener key; + struct wl_listener destroy; +}; + +static void focus_toplevel(struct tinywl_toplevel *toplevel, struct wlr_surface *surface) { + /* Note: this function only deals with keyboard focus. */ + if (toplevel == NULL) { + return; + } + struct tinywl_server *server = toplevel->server; + struct wlr_seat *seat = server->seat; + struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; + if (prev_surface == surface) { + /* Don't re-focus an already focused surface. */ + return; + } + if (prev_surface) { + /* + * Deactivate the previously focused surface. This lets the client know + * it no longer has focus and the client will repaint accordingly, e.g. + * stop displaying a caret. + */ + struct wlr_xdg_toplevel *prev_toplevel = + wlr_xdg_toplevel_try_from_wlr_surface(prev_surface); + if (prev_toplevel != NULL) { + wlr_xdg_toplevel_set_activated(prev_toplevel, false); + } + } + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat); + /* Move the toplevel to the front */ + wlr_scene_node_raise_to_top(&toplevel->scene_tree->node); + wl_list_remove(&toplevel->link); + wl_list_insert(&server->toplevels, &toplevel->link); + /* Activate the new surface */ + wlr_xdg_toplevel_set_activated(toplevel->xdg_toplevel, true); + /* + * Tell the seat to have the keyboard enter this surface. wlroots will keep + * track of this and automatically send key events to the appropriate + * clients without additional work on your part. + */ + if (keyboard != NULL) { + wlr_seat_keyboard_notify_enter(seat, toplevel->xdg_toplevel->base->surface, + keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); + } +} + +static void keyboard_handle_modifiers( + struct wl_listener *listener, void *data) { + /* This event is raised when a modifier key, such as shift or alt, is + * pressed. We simply communicate this to the client. */ + struct tinywl_keyboard *keyboard = + wl_container_of(listener, keyboard, modifiers); + /* + * A seat can only have one keyboard, but this is a limitation of the + * Wayland protocol - not wlroots. We assign all connected keyboards to the + * same seat. You can swap out the underlying wlr_keyboard like this and + * wlr_seat handles this transparently. + */ + wlr_seat_set_keyboard(keyboard->server->seat, keyboard->wlr_keyboard); + /* Send modifiers to the client. */ + wlr_seat_keyboard_notify_modifiers(keyboard->server->seat, + &keyboard->wlr_keyboard->modifiers); +} + +static bool handle_keybinding(struct tinywl_server *server, xkb_keysym_t sym) { + /* + * Here we handle compositor keybindings. This is when the compositor is + * processing keys, rather than passing them on to the client for its own + * processing. + * + * This function assumes Alt is held down. + */ + switch (sym) { + case XKB_KEY_Escape: + wl_display_terminate(server->wl_display); + break; + case XKB_KEY_F1: + /* Cycle to the next toplevel */ + if (wl_list_length(&server->toplevels) < 2) { + break; + } + struct tinywl_toplevel *next_toplevel = + wl_container_of(server->toplevels.prev, next_toplevel, link); + focus_toplevel(next_toplevel, next_toplevel->xdg_toplevel->base->surface); + break; + default: + return false; + } + return true; +} + +static void keyboard_handle_key( + struct wl_listener *listener, void *data) { + /* This event is raised when a key is pressed or released. */ + struct tinywl_keyboard *keyboard = + wl_container_of(listener, keyboard, key); + struct tinywl_server *server = keyboard->server; + struct wlr_keyboard_key_event *event = data; + struct wlr_seat *seat = server->seat; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; + /* Get a list of keysyms based on the keymap for this keyboard */ + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms( + keyboard->wlr_keyboard->xkb_state, keycode, &syms); + + bool handled = false; + uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->wlr_keyboard); + if ((modifiers & WLR_MODIFIER_ALT) && + event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + /* If alt is held down and this button was _pressed_, we attempt to + * process it as a compositor keybinding. */ + for (int i = 0; i < nsyms; i++) { + handled = handle_keybinding(server, syms[i]); + } + } + + if (!handled) { + /* Otherwise, we pass it along to the client. */ + wlr_seat_set_keyboard(seat, keyboard->wlr_keyboard); + wlr_seat_keyboard_notify_key(seat, event->time_msec, + event->keycode, event->state); + } +} + +static void keyboard_handle_destroy(struct wl_listener *listener, void *data) { + /* This event is raised by the keyboard base wlr_input_device to signal + * the destruction of the wlr_keyboard. It will no longer receive events + * and should be destroyed. + */ + struct tinywl_keyboard *keyboard = + wl_container_of(listener, keyboard, destroy); + wl_list_remove(&keyboard->modifiers.link); + wl_list_remove(&keyboard->key.link); + wl_list_remove(&keyboard->destroy.link); + wl_list_remove(&keyboard->link); + free(keyboard); +} + +static void server_new_keyboard(struct tinywl_server *server, + struct wlr_input_device *device) { + struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(device); + + struct tinywl_keyboard *keyboard = calloc(1, sizeof(*keyboard)); + keyboard->server = server; + keyboard->wlr_keyboard = wlr_keyboard; + + /* We need to prepare an XKB keymap and assign it to the keyboard. This + * assumes the defaults (e.g. layout = "us"). */ + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, + XKB_KEYMAP_COMPILE_NO_FLAGS); + + wlr_keyboard_set_keymap(wlr_keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + wlr_keyboard_set_repeat_info(wlr_keyboard, 25, 600); + + /* Here we set up listeners for keyboard events. */ + keyboard->modifiers.notify = keyboard_handle_modifiers; + wl_signal_add(&wlr_keyboard->events.modifiers, &keyboard->modifiers); + keyboard->key.notify = keyboard_handle_key; + wl_signal_add(&wlr_keyboard->events.key, &keyboard->key); + keyboard->destroy.notify = keyboard_handle_destroy; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + + wlr_seat_set_keyboard(server->seat, keyboard->wlr_keyboard); + + /* And add the keyboard to our list of keyboards */ + wl_list_insert(&server->keyboards, &keyboard->link); +} + +static void server_new_pointer(struct tinywl_server *server, + struct wlr_input_device *device) { + /* We don't do anything special with pointers. All of our pointer handling + * is proxied through wlr_cursor. On another compositor, you might take this + * opportunity to do libinput configuration on the device to set + * acceleration, etc. */ + wlr_cursor_attach_input_device(server->cursor, device); +} + +static void server_new_input(struct wl_listener *listener, void *data) { + /* This event is raised by the backend when a new input device becomes + * available. */ + struct tinywl_server *server = + wl_container_of(listener, server, new_input); + struct wlr_input_device *device = data; + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + server_new_keyboard(server, device); + break; + case WLR_INPUT_DEVICE_POINTER: + server_new_pointer(server, device); + break; + default: + break; + } + /* We need to let the wlr_seat know what our capabilities are, which is + * communiciated to the client. In TinyWL we always have a cursor, even if + * there are no pointer devices, so we always include that capability. */ + uint32_t caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&server->keyboards)) { + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + } + wlr_seat_set_capabilities(server->seat, caps); +} + +static void seat_request_cursor(struct wl_listener *listener, void *data) { + struct tinywl_server *server = wl_container_of( + listener, server, request_cursor); + /* This event is raised by the seat when a client provides a cursor image */ + struct wlr_seat_pointer_request_set_cursor_event *event = data; + struct wlr_seat_client *focused_client = + server->seat->pointer_state.focused_client; + /* This can be sent by any client, so we check to make sure this one is + * actually has pointer focus first. */ + if (focused_client == event->seat_client) { + /* Once we've vetted the client, we can tell the cursor to use the + * provided surface as the cursor image. It will set the hardware cursor + * on the output that it's currently on and continue to do so as the + * cursor moves between outputs. */ + wlr_cursor_set_surface(server->cursor, event->surface, + event->hotspot_x, event->hotspot_y); + } +} + +static void seat_request_set_selection(struct wl_listener *listener, void *data) { + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in tinywl we always honor + */ + struct tinywl_server *server = wl_container_of( + listener, server, request_set_selection); + struct wlr_seat_request_set_selection_event *event = data; + wlr_seat_set_selection(server->seat, event->source, event->serial); +} + +static struct tinywl_toplevel *desktop_toplevel_at( + struct tinywl_server *server, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy) { + /* This returns the topmost node in the scene at the given layout coords. + * We only care about surface nodes as we are specifically looking for a + * surface in the surface tree of a tinywl_toplevel. */ + struct wlr_scene_node *node = wlr_scene_node_at( + &server->scene->tree.node, lx, ly, sx, sy); + if (node == NULL || node->type != WLR_SCENE_NODE_BUFFER) { + return NULL; + } + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer(scene_buffer); + if (!scene_surface) { + return NULL; + } + + *surface = scene_surface->surface; + /* Find the node corresponding to the tinywl_toplevel at the root of this + * surface tree, it is the only one for which we set the data field. */ + struct wlr_scene_tree *tree = node->parent; + while (tree != NULL && tree->node.data == NULL) { + tree = tree->node.parent; + } + return tree->node.data; +} + +static void reset_cursor_mode(struct tinywl_server *server) { + /* Reset the cursor mode to passthrough. */ + server->cursor_mode = TINYWL_CURSOR_PASSTHROUGH; + server->grabbed_toplevel = NULL; +} + +static void process_cursor_move(struct tinywl_server *server, uint32_t time) { + /* Move the grabbed toplevel to the new position. */ + struct tinywl_toplevel *toplevel = server->grabbed_toplevel; + wlr_scene_node_set_position(&toplevel->scene_tree->node, + server->cursor->x - server->grab_x, + server->cursor->y - server->grab_y); +} + +static void process_cursor_resize(struct tinywl_server *server, uint32_t time) { + /* + * Resizing the grabbed toplevel can be a little bit complicated, because we + * could be resizing from any corner or edge. This not only resizes the + * toplevel on one or two axes, but can also move the toplevel if you resize + * from the top or left edges (or top-left corner). + * + * Note that some shortcuts are taken here. In a more fleshed-out + * compositor, you'd wait for the client to prepare a buffer at the new + * size, then commit any movement that was prepared. + */ + struct tinywl_toplevel *toplevel = server->grabbed_toplevel; + double border_x = server->cursor->x - server->grab_x; + double border_y = server->cursor->y - server->grab_y; + int new_left = server->grab_geobox.x; + int new_right = server->grab_geobox.x + server->grab_geobox.width; + int new_top = server->grab_geobox.y; + int new_bottom = server->grab_geobox.y + server->grab_geobox.height; + + if (server->resize_edges & WLR_EDGE_TOP) { + new_top = border_y; + if (new_top >= new_bottom) { + new_top = new_bottom - 1; + } + } else if (server->resize_edges & WLR_EDGE_BOTTOM) { + new_bottom = border_y; + if (new_bottom <= new_top) { + new_bottom = new_top + 1; + } + } + if (server->resize_edges & WLR_EDGE_LEFT) { + new_left = border_x; + if (new_left >= new_right) { + new_left = new_right - 1; + } + } else if (server->resize_edges & WLR_EDGE_RIGHT) { + new_right = border_x; + if (new_right <= new_left) { + new_right = new_left + 1; + } + } + + struct wlr_box geo_box; + wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &geo_box); + wlr_scene_node_set_position(&toplevel->scene_tree->node, + new_left - geo_box.x, new_top - geo_box.y); + + int new_width = new_right - new_left; + int new_height = new_bottom - new_top; + wlr_xdg_toplevel_set_size(toplevel->xdg_toplevel, new_width, new_height); +} + +static void process_cursor_motion(struct tinywl_server *server, uint32_t time) { + /* If the mode is non-passthrough, delegate to those functions. */ + if (server->cursor_mode == TINYWL_CURSOR_MOVE) { + process_cursor_move(server, time); + return; + } else if (server->cursor_mode == TINYWL_CURSOR_RESIZE) { + process_cursor_resize(server, time); + return; + } + + /* Otherwise, find the toplevel under the pointer and send the event along. */ + double sx, sy; + struct wlr_seat *seat = server->seat; + struct wlr_surface *surface = NULL; + struct tinywl_toplevel *toplevel = desktop_toplevel_at(server, + server->cursor->x, server->cursor->y, &surface, &sx, &sy); + if (!toplevel) { + /* If there's no toplevel under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * around the screen, not over any toplevels. */ + wlr_cursor_set_xcursor(server->cursor, server->cursor_mgr, "default"); + } + if (surface) { + /* + * Send pointer enter and motion events. + * + * The enter event gives the surface "pointer focus", which is distinct + * from keyboard focus. You get pointer focus by moving the pointer over + * a window. + * + * Note that wlroots will avoid sending duplicate enter/motion events if + * the surface has already has pointer focus or if the client is already + * aware of the coordinates passed. + */ + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } else { + /* Clear pointer focus so future button events and such are not sent to + * the last client to have the cursor over it. */ + wlr_seat_pointer_clear_focus(seat); + } +} + +static void server_cursor_motion(struct wl_listener *listener, void *data) { + /* This event is forwarded by the cursor when a pointer emits a _relative_ + * pointer motion event (i.e. a delta) */ + struct tinywl_server *server = + wl_container_of(listener, server, cursor_motion); + struct wlr_pointer_motion_event *event = data; + /* The cursor doesn't move unless we tell it to. The cursor automatically + * handles constraining the motion to the output layout, as well as any + * special configuration applied for the specific input device which + * generated the event. You can pass NULL for the device if you want to move + * the cursor around without any input. */ + wlr_cursor_move(server->cursor, &event->pointer->base, + event->delta_x, event->delta_y); + process_cursor_motion(server, event->time_msec); +} + +static void server_cursor_motion_absolute( + struct wl_listener *listener, void *data) { + /* This event is forwarded by the cursor when a pointer emits an _absolute_ + * motion event, from 0..1 on each axis. This happens, for example, when + * wlroots is running under a Wayland window rather than KMS+DRM, and you + * move the mouse over the window. You could enter the window from any edge, + * so we have to warp the mouse there. There is also some hardware which + * emits these events. */ + struct tinywl_server *server = + wl_container_of(listener, server, cursor_motion_absolute); + struct wlr_pointer_motion_absolute_event *event = data; + wlr_cursor_warp_absolute(server->cursor, &event->pointer->base, event->x, + event->y); + process_cursor_motion(server, event->time_msec); +} + +static void server_cursor_button(struct wl_listener *listener, void *data) { + /* This event is forwarded by the cursor when a pointer emits a button + * event. */ + struct tinywl_server *server = + wl_container_of(listener, server, cursor_button); + struct wlr_pointer_button_event *event = data; + /* Notify the client with pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button(server->seat, + event->time_msec, event->button, event->state); + double sx, sy; + struct wlr_surface *surface = NULL; + struct tinywl_toplevel *toplevel = desktop_toplevel_at(server, + server->cursor->x, server->cursor->y, &surface, &sx, &sy); + if (event->state == WL_POINTER_BUTTON_STATE_RELEASED) { + /* If you released any buttons, we exit interactive move/resize mode. */ + reset_cursor_mode(server); + } else { + /* Focus that client if the button was _pressed_ */ + focus_toplevel(toplevel, surface); + } +} + +static void server_cursor_axis(struct wl_listener *listener, void *data) { + /* This event is forwarded by the cursor when a pointer emits an axis event, + * for example when you move the scroll wheel. */ + struct tinywl_server *server = + wl_container_of(listener, server, cursor_axis); + struct wlr_pointer_axis_event *event = data; + /* Notify the client with pointer focus of the axis event. */ + wlr_seat_pointer_notify_axis(server->seat, + event->time_msec, event->orientation, event->delta, + event->delta_discrete, event->source, event->relative_direction); +} + +static void server_cursor_frame(struct wl_listener *listener, void *data) { + /* This event is forwarded by the cursor when a pointer emits an frame + * event. Frame events are sent after regular pointer events to group + * multiple events together. For instance, two axis events may happen at the + * same time, in which case a frame event won't be sent in between. */ + struct tinywl_server *server = + wl_container_of(listener, server, cursor_frame); + /* Notify the client with pointer focus of the frame event. */ + wlr_seat_pointer_notify_frame(server->seat); +} + +static void output_frame(struct wl_listener *listener, void *data) { + /* This function is called every time an output is ready to display a frame, + * generally at the output's refresh rate (e.g. 60Hz). */ + struct tinywl_output *output = wl_container_of(listener, output, frame); + struct wlr_scene *scene = output->server->scene; + + struct wlr_scene_output *scene_output = wlr_scene_get_scene_output( + scene, output->wlr_output); + + /* Render the scene if needed and commit the output */ + wlr_scene_output_commit(scene_output, NULL); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(scene_output, &now); +} + +static void output_request_state(struct wl_listener *listener, void *data) { + /* This function is called when the backend requests a new state for + * the output. For example, Wayland and X11 backends request a new mode + * when the output window is resized. */ + struct tinywl_output *output = wl_container_of(listener, output, request_state); + const struct wlr_output_event_request_state *event = data; + wlr_output_commit_state(output->wlr_output, event->state); +} + +static void output_destroy(struct wl_listener *listener, void *data) { + struct tinywl_output *output = wl_container_of(listener, output, destroy); + + wl_list_remove(&output->frame.link); + wl_list_remove(&output->request_state.link); + wl_list_remove(&output->destroy.link); + wl_list_remove(&output->link); + free(output); +} + +static void server_new_output(struct wl_listener *listener, void *data) { + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct tinywl_server *server = + wl_container_of(listener, server, new_output); + struct wlr_output *wlr_output = data; + + /* Configures the output created by the backend to use our allocator + * and our renderer. Must be done once, before commiting the output */ + wlr_output_init_render(wlr_output, server->allocator, server->renderer); + + /* The output may be disabled, switch it on. */ + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + + /* Some backends don't have modes. DRM+KMS does, and we need to set a mode + * before we can use the output. The mode is a tuple of (width, height, + * refresh rate), and each monitor supports only a specific set of modes. We + * just pick the monitor's preferred mode, a more sophisticated compositor + * would let the user configure it. */ + struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + + /* Atomically applies the new output state. */ + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + + /* Allocates and configures our state for this output */ + struct tinywl_output *output = calloc(1, sizeof(*output)); + output->wlr_output = wlr_output; + output->server = server; + + /* Sets up a listener for the frame event. */ + output->frame.notify = output_frame; + wl_signal_add(&wlr_output->events.frame, &output->frame); + + /* Sets up a listener for the state request event. */ + output->request_state.notify = output_request_state; + wl_signal_add(&wlr_output->events.request_state, &output->request_state); + + /* Sets up a listener for the destroy event. */ + output->destroy.notify = output_destroy; + wl_signal_add(&wlr_output->events.destroy, &output->destroy); + + wl_list_insert(&server->outputs, &output->link); + + /* Adds this to the output layout. The add_auto function arranges outputs + * from left-to-right in the order they appear. A more sophisticated + * compositor would let the user configure the arrangement of outputs in the + * layout. + * + * The output layout utility automatically adds a wl_output global to the + * display, which Wayland clients can see to find out information about the + * output (such as DPI, scale factor, manufacturer, etc). + */ + struct wlr_output_layout_output *l_output = wlr_output_layout_add_auto(server->output_layout, + wlr_output); + struct wlr_scene_output *scene_output = wlr_scene_output_create(server->scene, wlr_output); + wlr_scene_output_layout_add_output(server->scene_layout, l_output, scene_output); +} + +static void xdg_toplevel_map(struct wl_listener *listener, void *data) { + /* Called when the surface is mapped, or ready to display on-screen. */ + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, map); + + wl_list_insert(&toplevel->server->toplevels, &toplevel->link); + + focus_toplevel(toplevel, toplevel->xdg_toplevel->base->surface); +} + +static void xdg_toplevel_unmap(struct wl_listener *listener, void *data) { + /* Called when the surface is unmapped, and should no longer be shown. */ + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, unmap); + + /* Reset the cursor mode if the grabbed toplevel was unmapped. */ + if (toplevel == toplevel->server->grabbed_toplevel) { + reset_cursor_mode(toplevel->server); + } + + wl_list_remove(&toplevel->link); +} + +static void xdg_toplevel_commit(struct wl_listener *listener, void *data) { + /* Called when a new surface state is committed. */ + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, commit); + + if (toplevel->xdg_toplevel->base->initial_commit) { + /* When an xdg_surface performs an initial commit, the compositor must + * reply with a configure so the client can map the surface. tinywl + * configures the xdg_toplevel with 0,0 size to let the client pick the + * dimensions itself. */ + wlr_xdg_toplevel_set_size(toplevel->xdg_toplevel, 0, 0); + } +} + +static void xdg_toplevel_destroy(struct wl_listener *listener, void *data) { + /* Called when the xdg_toplevel is destroyed. */ + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, destroy); + + wl_list_remove(&toplevel->map.link); + wl_list_remove(&toplevel->unmap.link); + wl_list_remove(&toplevel->commit.link); + wl_list_remove(&toplevel->destroy.link); + wl_list_remove(&toplevel->request_move.link); + wl_list_remove(&toplevel->request_resize.link); + wl_list_remove(&toplevel->request_maximize.link); + wl_list_remove(&toplevel->request_fullscreen.link); + + free(toplevel); +} + +static void begin_interactive(struct tinywl_toplevel *toplevel, + enum tinywl_cursor_mode mode, uint32_t edges) { + /* This function sets up an interactive move or resize operation, where the + * compositor stops propegating pointer events to clients and instead + * consumes them itself, to move or resize windows. */ + struct tinywl_server *server = toplevel->server; + struct wlr_surface *focused_surface = + server->seat->pointer_state.focused_surface; + if (toplevel->xdg_toplevel->base->surface != + wlr_surface_get_root_surface(focused_surface)) { + /* Deny move/resize requests from unfocused clients. */ + return; + } + server->grabbed_toplevel = toplevel; + server->cursor_mode = mode; + + if (mode == TINYWL_CURSOR_MOVE) { + server->grab_x = server->cursor->x - toplevel->scene_tree->node.x; + server->grab_y = server->cursor->y - toplevel->scene_tree->node.y; + } else { + struct wlr_box geo_box; + wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &geo_box); + + double border_x = (toplevel->scene_tree->node.x + geo_box.x) + + ((edges & WLR_EDGE_RIGHT) ? geo_box.width : 0); + double border_y = (toplevel->scene_tree->node.y + geo_box.y) + + ((edges & WLR_EDGE_BOTTOM) ? geo_box.height : 0); + server->grab_x = server->cursor->x - border_x; + server->grab_y = server->cursor->y - border_y; + + server->grab_geobox = geo_box; + server->grab_geobox.x += toplevel->scene_tree->node.x; + server->grab_geobox.y += toplevel->scene_tree->node.y; + + server->resize_edges = edges; + } +} + +static void xdg_toplevel_request_move( + struct wl_listener *listener, void *data) { + /* This event is raised when a client would like to begin an interactive + * move, typically because the user clicked on their client-side + * decorations. Note that a more sophisticated compositor should check the + * provided serial against a list of button press serials sent to this + * client, to prevent the client from requesting this whenever they want. */ + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, request_move); + begin_interactive(toplevel, TINYWL_CURSOR_MOVE, 0); +} + +static void xdg_toplevel_request_resize( + struct wl_listener *listener, void *data) { + /* This event is raised when a client would like to begin an interactive + * resize, typically because the user clicked on their client-side + * decorations. Note that a more sophisticated compositor should check the + * provided serial against a list of button press serials sent to this + * client, to prevent the client from requesting this whenever they want. */ + struct wlr_xdg_toplevel_resize_event *event = data; + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, request_resize); + begin_interactive(toplevel, TINYWL_CURSOR_RESIZE, event->edges); +} + +static void xdg_toplevel_request_maximize( + struct wl_listener *listener, void *data) { + /* This event is raised when a client would like to maximize itself, + * typically because the user clicked on the maximize button on client-side + * decorations. tinywl doesn't support maximization, but to conform to + * xdg-shell protocol we still must send a configure. + * wlr_xdg_surface_schedule_configure() is used to send an empty reply. + * However, if the request was sent before an initial commit, we don't do + * anything and let the client finish the initial surface setup. */ + struct tinywl_toplevel *toplevel = + wl_container_of(listener, toplevel, request_maximize); + if (toplevel->xdg_toplevel->base->initialized) { + wlr_xdg_surface_schedule_configure(toplevel->xdg_toplevel->base); + } +} + +static void xdg_toplevel_request_fullscreen( + struct wl_listener *listener, void *data) { + /* Just as with request_maximize, we must send a configure here. */ + struct tinywl_toplevel *toplevel = + wl_container_of(listener, toplevel, request_fullscreen); + if (toplevel->xdg_toplevel->base->initialized) { + wlr_xdg_surface_schedule_configure(toplevel->xdg_toplevel->base); + } +} + +static void server_new_xdg_toplevel(struct wl_listener *listener, void *data) { + /* This event is raised when a client creates a new toplevel (application window). */ + struct tinywl_server *server = wl_container_of(listener, server, new_xdg_toplevel); + struct wlr_xdg_toplevel *xdg_toplevel = data; + + /* Allocate a tinywl_toplevel for this surface */ + struct tinywl_toplevel *toplevel = calloc(1, sizeof(*toplevel)); + toplevel->server = server; + toplevel->xdg_toplevel = xdg_toplevel; + toplevel->scene_tree = + wlr_scene_xdg_surface_create(&toplevel->server->scene->tree, xdg_toplevel->base); + toplevel->scene_tree->node.data = toplevel; + xdg_toplevel->base->data = toplevel->scene_tree; + + /* Listen to the various events it can emit */ + toplevel->map.notify = xdg_toplevel_map; + wl_signal_add(&xdg_toplevel->base->surface->events.map, &toplevel->map); + toplevel->unmap.notify = xdg_toplevel_unmap; + wl_signal_add(&xdg_toplevel->base->surface->events.unmap, &toplevel->unmap); + toplevel->commit.notify = xdg_toplevel_commit; + wl_signal_add(&xdg_toplevel->base->surface->events.commit, &toplevel->commit); + + toplevel->destroy.notify = xdg_toplevel_destroy; + wl_signal_add(&xdg_toplevel->events.destroy, &toplevel->destroy); + + /* cotd */ + toplevel->request_move.notify = xdg_toplevel_request_move; + wl_signal_add(&xdg_toplevel->events.request_move, &toplevel->request_move); + toplevel->request_resize.notify = xdg_toplevel_request_resize; + wl_signal_add(&xdg_toplevel->events.request_resize, &toplevel->request_resize); + toplevel->request_maximize.notify = xdg_toplevel_request_maximize; + wl_signal_add(&xdg_toplevel->events.request_maximize, &toplevel->request_maximize); + toplevel->request_fullscreen.notify = xdg_toplevel_request_fullscreen; + wl_signal_add(&xdg_toplevel->events.request_fullscreen, &toplevel->request_fullscreen); +} + +static void xdg_popup_commit(struct wl_listener *listener, void *data) { + /* Called when a new surface state is committed. */ + struct tinywl_popup *popup = wl_container_of(listener, popup, commit); + + if (popup->xdg_popup->base->initial_commit) { + /* When an xdg_surface performs an initial commit, the compositor must + * reply with a configure so the client can map the surface. + * tinywl sends an empty configure. A more sophisticated compositor + * might change an xdg_popup's geometry to ensure it's not positioned + * off-screen, for example. */ + wlr_xdg_surface_schedule_configure(popup->xdg_popup->base); + } +} + +static void xdg_popup_destroy(struct wl_listener *listener, void *data) { + /* Called when the xdg_popup is destroyed. */ + struct tinywl_popup *popup = wl_container_of(listener, popup, destroy); + + wl_list_remove(&popup->commit.link); + wl_list_remove(&popup->destroy.link); + + free(popup); +} + +static void server_new_xdg_popup(struct wl_listener *listener, void *data) { + /* This event is raised when a client creates a new popup. */ + struct wlr_xdg_popup *xdg_popup = data; + + struct tinywl_popup *popup = calloc(1, sizeof(*popup)); + popup->xdg_popup = xdg_popup; + + /* We must add xdg popups to the scene graph so they get rendered. The + * wlroots scene graph provides a helper for this, but to use it we must + * provide the proper parent scene node of the xdg popup. To enable this, + * we always set the user data field of xdg_surfaces to the corresponding + * scene node. */ + struct wlr_xdg_surface *parent = wlr_xdg_surface_try_from_wlr_surface(xdg_popup->parent); + assert(parent != NULL); + struct wlr_scene_tree *parent_tree = parent->data; + xdg_popup->base->data = wlr_scene_xdg_surface_create(parent_tree, xdg_popup->base); + + popup->commit.notify = xdg_popup_commit; + wl_signal_add(&xdg_popup->base->surface->events.commit, &popup->commit); + + popup->destroy.notify = xdg_popup_destroy; + wl_signal_add(&xdg_popup->events.destroy, &popup->destroy); +} + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + char *startup_cmd = NULL; + + int c; + while ((c = getopt(argc, argv, "s:h")) != -1) { + switch (c) { + case 's': + startup_cmd = optarg; + break; + default: + printf("Usage: %s [-s startup command]\n", argv[0]); + return 0; + } + } + if (optind < argc) { + printf("Usage: %s [-s startup command]\n", argv[0]); + return 0; + } + + struct tinywl_server server = {0}; + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, manging Wayland globals, and so on. */ + server.wl_display = wl_display_create(); + /* The backend is a wlroots feature which abstracts the underlying input and + * output hardware. The autocreate option will choose the most suitable + * backend based on the current environment, such as opening an X11 window + * if an X11 server is running. */ + server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.wl_display), NULL); + if (server.backend == NULL) { + wlr_log(WLR_ERROR, "failed to create wlr_backend"); + return 1; + } + + /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user + * can also specify a renderer using the WLR_RENDERER env var. + * The renderer is responsible for defining the various pixel formats it + * supports for shared memory, this configures that for clients. */ + server.renderer = wlr_renderer_autocreate(server.backend); + if (server.renderer == NULL) { + wlr_log(WLR_ERROR, "failed to create wlr_renderer"); + return 1; + } + + wlr_renderer_init_wl_display(server.renderer, server.wl_display); + + /* Autocreates an allocator for us. + * The allocator is the bridge between the renderer and the backend. It + * handles the buffer creation, allowing wlroots to render onto the + * screen */ + server.allocator = wlr_allocator_autocreate(server.backend, + server.renderer); + if (server.allocator == NULL) { + wlr_log(WLR_ERROR, "failed to create wlr_allocator"); + return 1; + } + + /* This creates some hands-off wlroots interfaces. The compositor is + * necessary for clients to allocate surfaces, the subcompositor allows to + * assign the role of subsurfaces to surfaces and the data device manager + * handles the clipboard. Each of these wlroots interfaces has room for you + * to dig your fingers in and play with their behavior if you want. Note that + * the clients cannot set the selection directly without compositor approval, + * see the handling of the request_set_selection event below.*/ + wlr_compositor_create(server.wl_display, 5, server.renderer); + wlr_subcompositor_create(server.wl_display); + wlr_data_device_manager_create(server.wl_display); + + /* Creates an output layout, which a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + server.output_layout = wlr_output_layout_create(server.wl_display); + + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + wl_list_init(&server.outputs); + server.new_output.notify = server_new_output; + wl_signal_add(&server.backend->events.new_output, &server.new_output); + + /* Create a scene graph. This is a wlroots abstraction that handles all + * rendering and damage tracking. All the compositor author needs to do + * is add things that should be rendered to the scene graph at the proper + * positions and then call wlr_scene_output_commit() to render a frame if + * necessary. + */ + server.scene = wlr_scene_create(); + server.scene_layout = wlr_scene_attach_output_layout(server.scene, server.output_layout); + + /* Set up xdg-shell version 3. The xdg-shell is a Wayland protocol which is + * used for application windows. For more detail on shells, refer to + * https://drewdevault.com/2018/07/29/Wayland-shells.html. + */ + wl_list_init(&server.toplevels); + server.xdg_shell = wlr_xdg_shell_create(server.wl_display, 3); + server.new_xdg_toplevel.notify = server_new_xdg_toplevel; + wl_signal_add(&server.xdg_shell->events.new_toplevel, &server.new_xdg_toplevel); + server.new_xdg_popup.notify = server_new_xdg_popup; + wl_signal_add(&server.xdg_shell->events.new_popup, &server.new_xdg_popup); + + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. + */ + server.cursor = wlr_cursor_create(); + wlr_cursor_attach_output_layout(server.cursor, server.output_layout); + + /* Creates an xcursor manager, another wlroots utility which loads up + * Xcursor themes to source cursor images from and makes sure that cursor + * images are available at all scale factors on the screen (necessary for + * HiDPI support). */ + server.cursor_mgr = wlr_xcursor_manager_create(NULL, 24); + + /* + * wlr_cursor *only* displays an image on screen. It does not move around + * when the pointer moves. However, we can attach input devices to it, and + * it will generate aggregate events for all of them. In these events, we + * can choose how we want to process them, forwarding them to clients and + * moving the cursor around. More detail on this process is described in + * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html. + * + * And more comments are sprinkled throughout the notify functions above. + */ + server.cursor_mode = TINYWL_CURSOR_PASSTHROUGH; + server.cursor_motion.notify = server_cursor_motion; + wl_signal_add(&server.cursor->events.motion, &server.cursor_motion); + server.cursor_motion_absolute.notify = server_cursor_motion_absolute; + wl_signal_add(&server.cursor->events.motion_absolute, + &server.cursor_motion_absolute); + server.cursor_button.notify = server_cursor_button; + wl_signal_add(&server.cursor->events.button, &server.cursor_button); + server.cursor_axis.notify = server_cursor_axis; + wl_signal_add(&server.cursor->events.axis, &server.cursor_axis); + server.cursor_frame.notify = server_cursor_frame; + wl_signal_add(&server.cursor->events.frame, &server.cursor_frame); + + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ + wl_list_init(&server.keyboards); + server.new_input.notify = server_new_input; + wl_signal_add(&server.backend->events.new_input, &server.new_input); + server.seat = wlr_seat_create(server.wl_display, "seat0"); + server.request_cursor.notify = seat_request_cursor; + wl_signal_add(&server.seat->events.request_set_cursor, + &server.request_cursor); + server.request_set_selection.notify = seat_request_set_selection; + wl_signal_add(&server.seat->events.request_set_selection, + &server.request_set_selection); + + /* Add a Unix socket to the Wayland display. */ + const char *socket = wl_display_add_socket_auto(server.wl_display); + if (!socket) { + wlr_backend_destroy(server.backend); + return 1; + } + + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ + if (!wlr_backend_start(server.backend)) { + wlr_backend_destroy(server.backend); + wl_display_destroy(server.wl_display); + return 1; + } + + /* Set the WAYLAND_DISPLAY environment variable to our socket and run the + * startup command if requested. */ + setenv("WAYLAND_DISPLAY", socket, true); + if (startup_cmd) { + if (fork() == 0) { + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL); + } + } + /* Run the Wayland event loop. This does not return until you exit the + * compositor. Starting the backend rigged up all of the necessary event + * loop configuration to listen to libinput events, DRM events, generate + * frame events at the refresh rate, and so on. */ + wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", + socket); + wl_display_run(server.wl_display); + + /* Once wl_display_run returns, we destroy all clients then shut down the + * server. */ + wl_display_destroy_clients(server.wl_display); + wlr_scene_node_destroy(&server.scene->tree.node); + wlr_xcursor_manager_destroy(server.cursor_mgr); + wlr_backend_destroy(server.backend); + wl_display_destroy(server.wl_display); + return 0; +} diff --git a/types/buffer/buffer.c b/types/buffer/buffer.c new file mode 100644 index 0000000..953207a --- /dev/null +++ b/types/buffer/buffer.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include "render/pixel_format.h" +#include "types/wlr_buffer.h" + +void wlr_buffer_init(struct wlr_buffer *buffer, + const struct wlr_buffer_impl *impl, int width, int height) { + assert(impl->destroy); + if (impl->begin_data_ptr_access || impl->end_data_ptr_access) { + assert(impl->begin_data_ptr_access && impl->end_data_ptr_access); + } + + *buffer = (struct wlr_buffer){ + .impl = impl, + .width = width, + .height = height, + }; + wl_signal_init(&buffer->events.destroy); + wl_signal_init(&buffer->events.release); + wlr_addon_set_init(&buffer->addons); +} + +static void buffer_consider_destroy(struct wlr_buffer *buffer) { + if (!buffer->dropped || buffer->n_locks > 0) { + return; + } + + assert(!buffer->accessing_data_ptr); + + wl_signal_emit_mutable(&buffer->events.destroy, NULL); + wlr_addon_set_finish(&buffer->addons); + + buffer->impl->destroy(buffer); +} + +void wlr_buffer_drop(struct wlr_buffer *buffer) { + if (buffer == NULL) { + return; + } + + assert(!buffer->dropped); + buffer->dropped = true; + buffer_consider_destroy(buffer); +} + +struct wlr_buffer *wlr_buffer_lock(struct wlr_buffer *buffer) { + buffer->n_locks++; + return buffer; +} + +void wlr_buffer_unlock(struct wlr_buffer *buffer) { + if (buffer == NULL) { + return; + } + + assert(buffer->n_locks > 0); + buffer->n_locks--; + + if (buffer->n_locks == 0) { + wl_signal_emit_mutable(&buffer->events.release, NULL); + } + + buffer_consider_destroy(buffer); +} + +bool wlr_buffer_get_dmabuf(struct wlr_buffer *buffer, + struct wlr_dmabuf_attributes *attribs) { + if (!buffer->impl->get_dmabuf) { + return false; + } + return buffer->impl->get_dmabuf(buffer, attribs); +} + +bool wlr_buffer_begin_data_ptr_access(struct wlr_buffer *buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride) { + assert(!buffer->accessing_data_ptr); + if (!buffer->impl->begin_data_ptr_access) { + return false; + } + if (!buffer->impl->begin_data_ptr_access(buffer, flags, data, format, stride)) { + return false; + } + buffer->accessing_data_ptr = true; + return true; +} + +void wlr_buffer_end_data_ptr_access(struct wlr_buffer *buffer) { + assert(buffer->accessing_data_ptr); + buffer->impl->end_data_ptr_access(buffer); + buffer->accessing_data_ptr = false; +} + +bool wlr_buffer_get_shm(struct wlr_buffer *buffer, + struct wlr_shm_attributes *attribs) { + if (!buffer->impl->get_shm) { + return false; + } + return buffer->impl->get_shm(buffer, attribs); +} + +bool buffer_is_opaque(struct wlr_buffer *buffer) { + void *data; + uint32_t format; + size_t stride; + struct wlr_dmabuf_attributes dmabuf; + struct wlr_shm_attributes shm; + if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) { + format = dmabuf.format; + } else if (wlr_buffer_get_shm(buffer, &shm)) { + format = shm.format; + } else if (wlr_buffer_begin_data_ptr_access(buffer, + WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { + bool opaque = false; + if (buffer->width == 1 && buffer->height == 1 && format == DRM_FORMAT_ARGB8888) { + // Special case for single-pixel-buffer-v1 + const uint8_t *argb8888 = data; // little-endian byte order + opaque = argb8888[3] == 0xFF; + } + wlr_buffer_end_data_ptr_access(buffer); + if (opaque) { + return true; + } + } else { + return false; + } + + return !pixel_format_has_alpha(format); +} diff --git a/types/buffer/client.c b/types/buffer/client.c new file mode 100644 index 0000000..4cfa57a --- /dev/null +++ b/types/buffer/client.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include +#include "types/wlr_buffer.h" + +static const struct wlr_buffer_impl client_buffer_impl; + +struct wlr_client_buffer *wlr_client_buffer_get(struct wlr_buffer *wlr_buffer) { + if (wlr_buffer->impl != &client_buffer_impl) { + return NULL; + } + struct wlr_client_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + return buffer; +} + +static struct wlr_client_buffer *client_buffer_from_buffer( + struct wlr_buffer *buffer) { + struct wlr_client_buffer *client_buffer = wlr_client_buffer_get(buffer); + assert(client_buffer != NULL); + return client_buffer; +} + +static void client_buffer_destroy(struct wlr_buffer *buffer) { + struct wlr_client_buffer *client_buffer = client_buffer_from_buffer(buffer); + wl_list_remove(&client_buffer->source_destroy.link); + wlr_texture_destroy(client_buffer->texture); + free(client_buffer); +} + +static bool client_buffer_get_dmabuf(struct wlr_buffer *buffer, + struct wlr_dmabuf_attributes *attribs) { + struct wlr_client_buffer *client_buffer = client_buffer_from_buffer(buffer); + + if (client_buffer->source == NULL) { + return false; + } + + return wlr_buffer_get_dmabuf(client_buffer->source, attribs); +} + +static const struct wlr_buffer_impl client_buffer_impl = { + .destroy = client_buffer_destroy, + .get_dmabuf = client_buffer_get_dmabuf, +}; + +static void client_buffer_handle_source_destroy(struct wl_listener *listener, + void *data) { + struct wlr_client_buffer *client_buffer = + wl_container_of(listener, client_buffer, source_destroy); + wl_list_remove(&client_buffer->source_destroy.link); + wl_list_init(&client_buffer->source_destroy.link); + client_buffer->source = NULL; +} + +struct wlr_client_buffer *wlr_client_buffer_create(struct wlr_buffer *buffer, + struct wlr_renderer *renderer) { + struct wlr_texture *texture = wlr_texture_from_buffer(renderer, buffer); + if (texture == NULL) { + wlr_log(WLR_ERROR, "Failed to create texture"); + return NULL; + } + + struct wlr_client_buffer *client_buffer = calloc(1, sizeof(*client_buffer)); + if (client_buffer == NULL) { + wlr_texture_destroy(texture); + return NULL; + } + wlr_buffer_init(&client_buffer->base, &client_buffer_impl, + texture->width, texture->height); + client_buffer->source = buffer; + client_buffer->texture = texture; + + wl_signal_add(&buffer->events.destroy, &client_buffer->source_destroy); + client_buffer->source_destroy.notify = client_buffer_handle_source_destroy; + + // Ensure the buffer will be released before being destroyed + wlr_buffer_lock(&client_buffer->base); + wlr_buffer_drop(&client_buffer->base); + + return client_buffer; +} + +bool wlr_client_buffer_apply_damage(struct wlr_client_buffer *client_buffer, + struct wlr_buffer *next, const pixman_region32_t *damage) { + if (client_buffer->base.n_locks - client_buffer->n_ignore_locks > 1) { + // Someone else still has a reference to the buffer + return false; + } + + return wlr_texture_update_from_buffer(client_buffer->texture, next, damage); +} diff --git a/types/buffer/dmabuf.c b/types/buffer/dmabuf.c new file mode 100644 index 0000000..af0c8c5 --- /dev/null +++ b/types/buffer/dmabuf.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include "types/wlr_buffer.h" + +static const struct wlr_buffer_impl dmabuf_buffer_impl; + +static struct wlr_dmabuf_buffer *dmabuf_buffer_from_buffer( + struct wlr_buffer *wlr_buffer) { + assert(wlr_buffer->impl == &dmabuf_buffer_impl); + struct wlr_dmabuf_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + return buffer; +} + +static void dmabuf_buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct wlr_dmabuf_buffer *buffer = dmabuf_buffer_from_buffer(wlr_buffer); + if (buffer->saved) { + wlr_dmabuf_attributes_finish(&buffer->dmabuf); + } + free(buffer); +} + +static bool dmabuf_buffer_get_dmabuf(struct wlr_buffer *wlr_buffer, + struct wlr_dmabuf_attributes *dmabuf) { + struct wlr_dmabuf_buffer *buffer = dmabuf_buffer_from_buffer(wlr_buffer); + if (buffer->dmabuf.n_planes == 0) { + return false; + } + *dmabuf = buffer->dmabuf; + return true; +} + +static const struct wlr_buffer_impl dmabuf_buffer_impl = { + .destroy = dmabuf_buffer_destroy, + .get_dmabuf = dmabuf_buffer_get_dmabuf, +}; + +struct wlr_dmabuf_buffer *dmabuf_buffer_create( + struct wlr_dmabuf_attributes *dmabuf) { + struct wlr_dmabuf_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + return NULL; + } + wlr_buffer_init(&buffer->base, &dmabuf_buffer_impl, + dmabuf->width, dmabuf->height); + + buffer->dmabuf = *dmabuf; + + return buffer; +} + +bool dmabuf_buffer_drop(struct wlr_dmabuf_buffer *buffer) { + bool ok = true; + + if (buffer->base.n_locks > 0) { + struct wlr_dmabuf_attributes saved_dmabuf = {0}; + if (!wlr_dmabuf_attributes_copy(&saved_dmabuf, &buffer->dmabuf)) { + wlr_log(WLR_ERROR, "Failed to save DMA-BUF"); + ok = false; + buffer->dmabuf = (struct wlr_dmabuf_attributes){0}; + } else { + buffer->dmabuf = saved_dmabuf; + buffer->saved = true; + } + } + + wlr_buffer_drop(&buffer->base); + return ok; +} diff --git a/types/buffer/readonly_data.c b/types/buffer/readonly_data.c new file mode 100644 index 0000000..f934ddc --- /dev/null +++ b/types/buffer/readonly_data.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include "types/wlr_buffer.h" + +static const struct wlr_buffer_impl readonly_data_buffer_impl; + +static struct wlr_readonly_data_buffer *readonly_data_buffer_from_buffer( + struct wlr_buffer *wlr_buffer) { + assert(wlr_buffer->impl == &readonly_data_buffer_impl); + struct wlr_readonly_data_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + return buffer; +} + +static void readonly_data_buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct wlr_readonly_data_buffer *buffer = + readonly_data_buffer_from_buffer(wlr_buffer); + free(buffer->saved_data); + free(buffer); +} + +static bool readonly_data_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, + uint32_t flags, void **data, uint32_t *format, size_t *stride) { + struct wlr_readonly_data_buffer *buffer = + readonly_data_buffer_from_buffer(wlr_buffer); + if (buffer->data == NULL) { + return false; + } + if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) { + return false; + } + *data = (void *)buffer->data; + *format = buffer->format; + *stride = buffer->stride; + return true; +} + +static void readonly_data_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { + // This space is intentionally left blank +} + +static const struct wlr_buffer_impl readonly_data_buffer_impl = { + .destroy = readonly_data_buffer_destroy, + .begin_data_ptr_access = readonly_data_buffer_begin_data_ptr_access, + .end_data_ptr_access = readonly_data_buffer_end_data_ptr_access, +}; + +struct wlr_readonly_data_buffer *readonly_data_buffer_create(uint32_t format, + size_t stride, uint32_t width, uint32_t height, const void *data) { + struct wlr_readonly_data_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + return NULL; + } + wlr_buffer_init(&buffer->base, &readonly_data_buffer_impl, width, height); + + buffer->data = data; + buffer->format = format; + buffer->stride = stride; + + return buffer; +} + +bool readonly_data_buffer_drop(struct wlr_readonly_data_buffer *buffer) { + bool ok = true; + + if (buffer->base.n_locks > 0) { + size_t size = buffer->stride * buffer->base.height; + buffer->saved_data = malloc(size); + if (buffer->saved_data == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + ok = false; + buffer->data = NULL; + // We can't destroy the buffer, or we risk use-after-free in the + // consumers. We can't allow accesses to buffer->data anymore, so + // set it to NULL and make subsequent begin_data_ptr_access() + // calls fail. + } else { + memcpy(buffer->saved_data, buffer->data, size); + buffer->data = buffer->saved_data; + } + } + + wlr_buffer_drop(&buffer->base); + return ok; +} + diff --git a/types/buffer/resource.c b/types/buffer/resource.c new file mode 100644 index 0000000..294bac1 --- /dev/null +++ b/types/buffer/resource.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include "types/wlr_buffer.h" + +/* struct wlr_buffer_resource_interface */ +static struct wl_array buffer_resource_interfaces = {0}; + +void wlr_buffer_register_resource_interface( + const struct wlr_buffer_resource_interface *iface) { + assert(iface); + assert(iface->is_instance); + assert(iface->from_resource); + + const struct wlr_buffer_resource_interface **iface_ptr; + wl_array_for_each(iface_ptr, &buffer_resource_interfaces) { + if (*iface_ptr == iface) { + wlr_log(WLR_DEBUG, "wlr_resource_buffer_interface %s has already" + "been registered", iface->name); + return; + } + } + + iface_ptr = wl_array_add(&buffer_resource_interfaces, sizeof(iface)); + *iface_ptr = iface; +} + +static const struct wlr_buffer_resource_interface *get_buffer_resource_iface( + struct wl_resource *resource) { + struct wlr_buffer_resource_interface **iface_ptr; + wl_array_for_each(iface_ptr, &buffer_resource_interfaces) { + if ((*iface_ptr)->is_instance(resource)) { + return *iface_ptr; + } + } + + return NULL; +} + +struct wlr_buffer *wlr_buffer_try_from_resource(struct wl_resource *resource) { + if (strcmp(wl_resource_get_class(resource), wl_buffer_interface.name) != 0) { + return NULL; + } + + const struct wlr_buffer_resource_interface *iface = + get_buffer_resource_iface(resource); + if (!iface) { + wlr_log(WLR_ERROR, "Unknown buffer type"); + return NULL; + } + + struct wlr_buffer *buffer = iface->from_resource(resource); + if (!buffer) { + wlr_log(WLR_ERROR, "Failed to create %s buffer", iface->name); + return NULL; + } + + return wlr_buffer_lock(buffer); +} diff --git a/types/data_device/wlr_data_device.c b/types/data_device/wlr_data_device.c new file mode 100644 index 0000000..3b5b8a6 --- /dev/null +++ b/types/data_device/wlr_data_device.c @@ -0,0 +1,316 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_data_device.h" + +#define DATA_DEVICE_MANAGER_VERSION 3 + +static const struct wl_data_device_interface data_device_impl; + +struct wlr_seat_client *seat_client_from_data_device_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_data_device_interface, + &data_device_impl)); + return wl_resource_get_user_data(resource); +} + +static void data_device_set_selection(struct wl_client *client, + struct wl_resource *device_resource, + struct wl_resource *source_resource, uint32_t serial) { + struct wlr_seat_client *seat_client = + seat_client_from_data_device_resource(device_resource); + if (seat_client == NULL) { + return; + } + + struct wlr_client_data_source *source = NULL; + if (source_resource != NULL) { + source = client_data_source_from_resource(source_resource); + } + + if (source != NULL) { + source->finalized = true; + } + + struct wlr_data_source *wlr_source = + source != NULL ? &source->source : NULL; + wlr_seat_request_set_selection(seat_client->seat, seat_client, wlr_source, serial); +} + +static void data_device_start_drag(struct wl_client *client, + struct wl_resource *device_resource, + struct wl_resource *source_resource, + struct wl_resource *origin_resource, struct wl_resource *icon_resource, + uint32_t serial) { + struct wlr_seat_client *seat_client = + seat_client_from_data_device_resource(device_resource); + if (seat_client == NULL) { + return; + } + + struct wlr_surface *origin = wlr_surface_from_resource(origin_resource); + + struct wlr_client_data_source *source = NULL; + if (source_resource != NULL) { + source = client_data_source_from_resource(source_resource); + } + + struct wlr_surface *icon = NULL; + if (icon_resource) { + icon = wlr_surface_from_resource(icon_resource); + if (!wlr_surface_set_role(icon, &drag_icon_surface_role, + icon_resource, WL_DATA_DEVICE_ERROR_ROLE)) { + return; + } + } + + struct wlr_data_source *wlr_source = + source != NULL ? &source->source : NULL; + struct wlr_drag *drag = wlr_drag_create(seat_client, wlr_source, icon); + if (drag == NULL) { + wl_resource_post_no_memory(device_resource); + return; + } + + if (source != NULL) { + source->finalized = true; + } + + wlr_seat_request_start_drag(seat_client->seat, drag, origin, serial); +} + +static void data_device_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_data_device_interface data_device_impl = { + .start_drag = data_device_start_drag, + .set_selection = data_device_set_selection, + .release = data_device_release, +}; + +static void data_device_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); +} + +static void device_resource_send_selection(struct wl_resource *device_resource) { + struct wlr_seat_client *seat_client = + seat_client_from_data_device_resource(device_resource); + assert(seat_client != NULL); + + struct wlr_data_source *source = seat_client->seat->selection_source; + if (source != NULL) { + struct wlr_data_offer *offer = data_offer_create(device_resource, + source, WLR_DATA_OFFER_SELECTION); + if (offer == NULL) { + wl_client_post_no_memory(seat_client->client); + return; + } + + wl_data_device_send_selection(device_resource, offer->resource); + } else { + wl_data_device_send_selection(device_resource, NULL); + } +} + +void seat_client_send_selection(struct wlr_seat_client *seat_client) { + struct wlr_data_source *source = seat_client->seat->selection_source; + if (source != NULL) { + source->accepted = false; + } + + // Make all current offers inert + struct wlr_data_offer *offer, *tmp; + wl_list_for_each_safe(offer, tmp, + &seat_client->seat->selection_offers, link) { + data_offer_destroy(offer); + } + + struct wl_resource *device_resource; + wl_resource_for_each(device_resource, &seat_client->data_devices) { + device_resource_send_selection(device_resource); + } +} + +void wlr_seat_request_set_selection(struct wlr_seat *seat, + struct wlr_seat_client *client, + struct wlr_data_source *source, uint32_t serial) { + if (client && !wlr_seat_client_validate_event_serial(client, serial)) { + wlr_log(WLR_DEBUG, "Rejecting set_selection request, " + "serial %"PRIu32" was never given to client", serial); + return; + } + + if (seat->selection_source && + serial - seat->selection_serial > UINT32_MAX / 2) { + wlr_log(WLR_DEBUG, "Rejecting set_selection request, serial indicates superseded " + "(%"PRIu32" < %"PRIu32")", serial, seat->selection_serial); + return; + } + + struct wlr_seat_request_set_selection_event event = { + .source = source, + .serial = serial, + }; + wl_signal_emit_mutable(&seat->events.request_set_selection, &event); +} + +static void seat_handle_selection_source_destroy( + struct wl_listener *listener, void *data) { + struct wlr_seat *seat = + wl_container_of(listener, seat, selection_source_destroy); + + wl_list_remove(&seat->selection_source_destroy.link); + seat->selection_source = NULL; + + struct wlr_seat_client *focused_client = + seat->keyboard_state.focused_client; + if (focused_client != NULL) { + seat_client_send_selection(focused_client); + } + + wl_signal_emit_mutable(&seat->events.set_selection, seat); +} + +void wlr_seat_set_selection(struct wlr_seat *seat, + struct wlr_data_source *source, uint32_t serial) { + if (seat->selection_source == source) { + seat->selection_serial = serial; + return; + } + + if (seat->selection_source) { + wl_list_remove(&seat->selection_source_destroy.link); + wlr_data_source_destroy(seat->selection_source); + seat->selection_source = NULL; + } + + seat->selection_source = source; + seat->selection_serial = serial; + + if (source) { + seat->selection_source_destroy.notify = + seat_handle_selection_source_destroy; + wl_signal_add(&source->events.destroy, + &seat->selection_source_destroy); + } + + struct wlr_seat_client *focused_client = + seat->keyboard_state.focused_client; + if (focused_client) { + seat_client_send_selection(focused_client); + } + + wl_signal_emit_mutable(&seat->events.set_selection, seat); +} + + +static const struct wl_data_device_manager_interface data_device_manager_impl; + +static struct wlr_data_device_manager *data_device_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_data_device_manager_interface, + &data_device_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void data_device_manager_get_data_device(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *seat_resource) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &wl_data_device_interface, version, id); + if (resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(resource, &data_device_impl, seat_client, + data_device_handle_resource_destroy); + if (seat_client == NULL) { + wl_list_init(wl_resource_get_link(resource)); + return; + } + wl_list_insert(&seat_client->data_devices, wl_resource_get_link(resource)); + + struct wlr_seat_client *focused_client = + seat_client->seat->keyboard_state.focused_client; + if (focused_client == seat_client) { + device_resource_send_selection(resource); + } +} + +static void data_device_manager_create_data_source(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id) { + struct wlr_data_device_manager *manager = + data_device_manager_from_resource(manager_resource); + + client_data_source_create(client, wl_resource_get_version(manager_resource), + id, &manager->data_sources); +} + +static const struct wl_data_device_manager_interface + data_device_manager_impl = { + .create_data_source = data_device_manager_create_data_source, + .get_data_device = data_device_manager_get_data_device, +}; + +static void data_device_manager_bind(struct wl_client *client, + void *data, uint32_t version, uint32_t id) { + struct wlr_data_device_manager *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &wl_data_device_manager_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &data_device_manager_impl, + manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_data_device_manager *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_data_device_manager *wlr_data_device_manager_create( + struct wl_display *display) { + struct wlr_data_device_manager *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + wlr_log(WLR_ERROR, "could not create data device manager"); + return NULL; + } + + wl_list_init(&manager->data_sources); + wl_signal_init(&manager->events.destroy); + + manager->global = + wl_global_create(display, &wl_data_device_manager_interface, + DATA_DEVICE_MANAGER_VERSION, manager, data_device_manager_bind); + if (!manager->global) { + wlr_log(WLR_ERROR, "could not create data device manager wl_global"); + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/data_device/wlr_data_offer.c b/types/data_device/wlr_data_offer.c new file mode 100644 index 0000000..4bb9d0c --- /dev/null +++ b/types/data_device/wlr_data_offer.c @@ -0,0 +1,284 @@ +#undef _POSIX_C_SOURCE +#define _XOPEN_SOURCE 700 // for ffs() +#include +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_data_device.h" + +static const struct wl_data_offer_interface data_offer_impl; + +static struct wlr_data_offer *data_offer_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_data_offer_interface, + &data_offer_impl)); + return wl_resource_get_user_data(resource); +} + +static uint32_t data_offer_choose_action(struct wlr_data_offer *offer) { + uint32_t offer_actions, preferred_action = 0; + if (wl_resource_get_version(offer->resource) >= + WL_DATA_OFFER_ACTION_SINCE_VERSION) { + offer_actions = offer->actions; + preferred_action = offer->preferred_action; + } else { + offer_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } + + uint32_t source_actions; + if (offer->source->actions >= 0) { + source_actions = offer->source->actions; + } else { + source_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } + + uint32_t available_actions = offer_actions & source_actions; + if (!available_actions) { + return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + } + + if (offer->source->compositor_action & available_actions) { + return offer->source->compositor_action; + } + + // If the dest side has a preferred DnD action, use it + if ((preferred_action & available_actions) != 0) { + return preferred_action; + } + + // Use the first found action, in bit order + return 1 << (ffs(available_actions) - 1); +} + +void data_offer_update_action(struct wlr_data_offer *offer) { + assert(offer->type == WLR_DATA_OFFER_DRAG); + + uint32_t action = data_offer_choose_action(offer); + if (offer->source->current_dnd_action == action) { + return; + } + offer->source->current_dnd_action = action; + + if (offer->in_ask) { + return; + } + + wlr_data_source_dnd_action(offer->source, action); + + if (wl_resource_get_version(offer->resource) >= + WL_DATA_OFFER_ACTION_SINCE_VERSION) { + wl_data_offer_send_action(offer->resource, action); + } +} + +static void data_offer_handle_accept(struct wl_client *client, + struct wl_resource *resource, uint32_t serial, const char *mime_type) { + struct wlr_data_offer *offer = data_offer_from_resource(resource); + if (offer == NULL) { + return; + } + + if (offer->type != WLR_DATA_OFFER_DRAG) { + wlr_log(WLR_DEBUG, "Ignoring wl_data_offer.accept request on a " + "non-drag-and-drop offer"); + return; + } + + wlr_data_source_accept(offer->source, serial, mime_type); +} + +static void data_offer_handle_receive(struct wl_client *client, + struct wl_resource *resource, const char *mime_type, int32_t fd) { + struct wlr_data_offer *offer = data_offer_from_resource(resource); + if (offer == NULL) { + close(fd); + return; + } + + wlr_data_source_send(offer->source, mime_type, fd); +} + +static void data_offer_source_dnd_finish(struct wlr_data_offer *offer) { + struct wlr_data_source *source = offer->source; + if (source->actions < 0) { + return; + } + + if (offer->in_ask) { + wlr_data_source_dnd_action(source, source->current_dnd_action); + } + + wlr_data_source_dnd_finish(source); +} + +static void data_offer_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void data_offer_handle_finish(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_data_offer *offer = data_offer_from_resource(resource); + if (offer == NULL) { + return; + } + + // TODO: also fail while we have a drag-and-drop grab + if (offer->type != WLR_DATA_OFFER_DRAG) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_FINISH, "Offer is not drag-and-drop"); + return; + } + if (!offer->source->accepted) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_FINISH, "Premature finish request"); + return; + } + enum wl_data_device_manager_dnd_action action = + offer->source->current_dnd_action; + if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE || + action == WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_FINISH, + "Offer finished with an invalid action"); + return; + } + + data_offer_source_dnd_finish(offer); + data_offer_destroy(offer); +} + +static void data_offer_handle_set_actions(struct wl_client *client, + struct wl_resource *resource, uint32_t actions, + uint32_t preferred_action) { + struct wlr_data_offer *offer = data_offer_from_resource(resource); + if (offer == NULL) { + return; + } + + if (actions & ~DATA_DEVICE_ALL_ACTIONS) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK, + "invalid action mask %x", actions); + return; + } + + if (preferred_action && (!(preferred_action & actions) || + __builtin_popcount(preferred_action) > 1)) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_ACTION, + "invalid action %x", preferred_action); + return; + } + + if (offer->type != WLR_DATA_OFFER_DRAG) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_OFFER, + "set_action can only be sent to drag-and-drop offers"); + return; + } + + offer->actions = actions; + offer->preferred_action = preferred_action; + + data_offer_update_action(offer); +} + +void data_offer_destroy(struct wlr_data_offer *offer) { + if (offer == NULL) { + return; + } + + wl_list_remove(&offer->source_destroy.link); + wl_list_remove(&offer->link); + + if (offer->type == WLR_DATA_OFFER_DRAG && offer->source) { + // If the drag destination has version < 3, wl_data_offer.finish + // won't be called, so do this here as a safety net, because + // we still want the version >= 3 drag source to be happy. + if (wl_resource_get_version(offer->resource) < + WL_DATA_OFFER_ACTION_SINCE_VERSION) { + data_offer_source_dnd_finish(offer); + } else if (offer->source->impl->dnd_finish) { + wlr_data_source_destroy(offer->source); + } + } + + // Make the resource inert + wl_resource_set_user_data(offer->resource, NULL); + + free(offer); +} + +static const struct wl_data_offer_interface data_offer_impl = { + .accept = data_offer_handle_accept, + .receive = data_offer_handle_receive, + .destroy = data_offer_handle_destroy, + .finish = data_offer_handle_finish, + .set_actions = data_offer_handle_set_actions, +}; + +static void data_offer_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_data_offer *offer = data_offer_from_resource(resource); + data_offer_destroy(offer); +} + +static void data_offer_handle_source_destroy(struct wl_listener *listener, + void *data) { + struct wlr_data_offer *offer = + wl_container_of(listener, offer, source_destroy); + // Prevent data_offer_destroy from destroying the source again + offer->source = NULL; + data_offer_destroy(offer); +} + +struct wlr_data_offer *data_offer_create(struct wl_resource *device_resource, + struct wlr_data_source *source, enum wlr_data_offer_type type) { + struct wlr_seat_client *seat_client = + seat_client_from_data_device_resource(device_resource); + assert(seat_client != NULL); + assert(source != NULL); // a NULL source means no selection + + struct wlr_data_offer *offer = calloc(1, sizeof(*offer)); + if (offer == NULL) { + return NULL; + } + offer->source = source; + offer->type = type; + + struct wl_client *client = wl_resource_get_client(device_resource); + uint32_t version = wl_resource_get_version(device_resource); + offer->resource = + wl_resource_create(client, &wl_data_offer_interface, version, 0); + if (offer->resource == NULL) { + free(offer); + return NULL; + } + wl_resource_set_implementation(offer->resource, &data_offer_impl, offer, + data_offer_handle_resource_destroy); + + switch (type) { + case WLR_DATA_OFFER_SELECTION: + wl_list_insert(&seat_client->seat->selection_offers, &offer->link); + break; + case WLR_DATA_OFFER_DRAG: + wl_list_insert(&seat_client->seat->drag_offers, &offer->link); + break; + } + + offer->source_destroy.notify = data_offer_handle_source_destroy; + wl_signal_add(&source->events.destroy, &offer->source_destroy); + + wl_data_device_send_data_offer(device_resource, offer->resource); + + char **p; + wl_array_for_each(p, &source->mime_types) { + wl_data_offer_send_offer(offer->resource, *p); + } + + return offer; +} diff --git a/types/data_device/wlr_data_source.c b/types/data_device/wlr_data_source.c new file mode 100644 index 0000000..15fb992 --- /dev/null +++ b/types/data_device/wlr_data_source.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_data_device.h" + +void wlr_data_source_init(struct wlr_data_source *source, + const struct wlr_data_source_impl *impl) { + assert(impl->send); + + *source = (struct wlr_data_source){ + .impl = impl, + .actions = -1, + }; + wl_array_init(&source->mime_types); + wl_signal_init(&source->events.destroy); +} + +void wlr_data_source_send(struct wlr_data_source *source, const char *mime_type, + int32_t fd) { + source->impl->send(source, mime_type, fd); +} + +void wlr_data_source_accept(struct wlr_data_source *source, uint32_t serial, + const char *mime_type) { + source->accepted = (mime_type != NULL); + if (source->impl->accept) { + source->impl->accept(source, serial, mime_type); + } +} + +void wlr_data_source_destroy(struct wlr_data_source *source) { + if (source == NULL) { + return; + } + + wl_signal_emit_mutable(&source->events.destroy, source); + + char **p; + wl_array_for_each(p, &source->mime_types) { + free(*p); + } + wl_array_release(&source->mime_types); + + if (source->impl->destroy) { + source->impl->destroy(source); + } else { + free(source); + } +} + +void wlr_data_source_dnd_drop(struct wlr_data_source *source) { + if (source->impl->dnd_drop) { + source->impl->dnd_drop(source); + } +} + +void wlr_data_source_dnd_finish(struct wlr_data_source *source) { + if (source->impl->dnd_finish) { + source->impl->dnd_finish(source); + } +} + +void wlr_data_source_dnd_action(struct wlr_data_source *source, + enum wl_data_device_manager_dnd_action action) { + source->current_dnd_action = action; + if (source->impl->dnd_action) { + source->impl->dnd_action(source, action); + } +} + + +static const struct wl_data_source_interface data_source_impl; + +struct wlr_client_data_source *client_data_source_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_data_source_interface, + &data_source_impl)); + return wl_resource_get_user_data(resource); +} + +static void client_data_source_accept(struct wlr_data_source *wlr_source, + uint32_t serial, const char *mime_type); + +static struct wlr_client_data_source *client_data_source_from_wlr_data_source( + struct wlr_data_source *wlr_source) { + assert(wlr_source->impl->accept == client_data_source_accept); + struct wlr_client_data_source *source = wl_container_of(wlr_source, source, source); + return source; +} + +static void client_data_source_accept(struct wlr_data_source *wlr_source, + uint32_t serial, const char *mime_type) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + wl_data_source_send_target(source->resource, mime_type); +} + +static void client_data_source_send(struct wlr_data_source *wlr_source, + const char *mime_type, int32_t fd) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + wl_data_source_send_send(source->resource, mime_type, fd); + close(fd); +} + +static void client_data_source_destroy(struct wlr_data_source *wlr_source) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + wl_data_source_send_cancelled(source->resource); + wl_resource_set_user_data(source->resource, NULL); + free(source); +} + +static void client_data_source_dnd_drop(struct wlr_data_source *wlr_source) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + assert(wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION); + wl_data_source_send_dnd_drop_performed(source->resource); +} + +static void client_data_source_dnd_finish(struct wlr_data_source *wlr_source) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + assert(wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION); + wl_data_source_send_dnd_finished(source->resource); +} + +static void client_data_source_dnd_action(struct wlr_data_source *wlr_source, + enum wl_data_device_manager_dnd_action action) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + assert(wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_ACTION_SINCE_VERSION); + wl_data_source_send_action(source->resource, action); +} + +static void data_source_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void data_source_set_actions(struct wl_client *client, + struct wl_resource *resource, uint32_t dnd_actions) { + struct wlr_client_data_source *source = + client_data_source_from_resource(resource); + if (source == NULL) { + return; + } + + if (source->source.actions >= 0) { + wl_resource_post_error(source->resource, + WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, + "cannot set actions more than once"); + return; + } + + if (dnd_actions & ~DATA_DEVICE_ALL_ACTIONS) { + wl_resource_post_error(source->resource, + WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, + "invalid action mask %x", dnd_actions); + return; + } + + if (source->finalized) { + wl_resource_post_error(source->resource, + WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, + "invalid action change after wl_data_device.start_drag"); + return; + } + + source->source.actions = dnd_actions; +} + +static void data_source_offer(struct wl_client *client, + struct wl_resource *resource, const char *mime_type) { + struct wlr_client_data_source *source = + client_data_source_from_resource(resource); + if (source == NULL) { + return; + } + if (source->finalized) { + wlr_log(WLR_DEBUG, "Offering additional MIME type after " + "wl_data_device.set_selection"); + } + + const char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, &source->source.mime_types) { + if (strcmp(*mime_type_ptr, mime_type) == 0) { + wlr_log(WLR_DEBUG, "Ignoring duplicate MIME type offer %s", + mime_type); + return; + } + } + + char *dup_mime_type = strdup(mime_type); + if (dup_mime_type == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + char **p = wl_array_add(&source->source.mime_types, sizeof(*p)); + if (p == NULL) { + free(dup_mime_type); + wl_resource_post_no_memory(resource); + return; + } + + *p = dup_mime_type; +} + +static const struct wl_data_source_interface data_source_impl = { + .destroy = data_source_destroy, + .offer = data_source_offer, + .set_actions = data_source_set_actions, +}; + +static void data_source_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_client_data_source *source = + client_data_source_from_resource(resource); + if (source != NULL) { + wlr_data_source_destroy(&source->source); + } + wl_list_remove(wl_resource_get_link(resource)); +} + +struct wlr_client_data_source *client_data_source_create( + struct wl_client *client, uint32_t version, uint32_t id, + struct wl_list *resource_list) { + struct wlr_client_data_source *source = calloc(1, sizeof(*source)); + if (source == NULL) { + return NULL; + } + + source->resource = wl_resource_create(client, &wl_data_source_interface, + version, id); + if (source->resource == NULL) { + wl_resource_post_no_memory(source->resource); + free(source); + return NULL; + } + wl_resource_set_implementation(source->resource, &data_source_impl, + source, data_source_handle_resource_destroy); + wl_list_insert(resource_list, wl_resource_get_link(source->resource)); + + source->impl.accept = client_data_source_accept; + source->impl.send = client_data_source_send; + source->impl.destroy = client_data_source_destroy; + + if (wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION) { + source->impl.dnd_drop = client_data_source_dnd_drop; + } + if (wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION) { + source->impl.dnd_finish = client_data_source_dnd_finish; + } + if (wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_ACTION_SINCE_VERSION) { + source->impl.dnd_action = client_data_source_dnd_action; + } + + wlr_data_source_init(&source->source, &source->impl); + return source; +} diff --git a/types/data_device/wlr_drag.c b/types/data_device/wlr_drag.c new file mode 100644 index 0000000..7161d91 --- /dev/null +++ b/types/data_device/wlr_drag.c @@ -0,0 +1,515 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_data_device.h" + +static void drag_handle_seat_client_destroy(struct wl_listener *listener, + void *data) { + struct wlr_drag *drag = + wl_container_of(listener, drag, seat_client_destroy); + + drag->focus_client = NULL; + wl_list_remove(&drag->seat_client_destroy.link); +} + +static void drag_set_focus(struct wlr_drag *drag, + struct wlr_surface *surface, double sx, double sy) { + if (drag->focus == surface) { + return; + } + + if (drag->focus_client) { + wl_list_remove(&drag->seat_client_destroy.link); + + // If we're switching focus to another client, we want to destroy all + // offers without destroying the source. If the drag operation ends, we + // want to keep the offer around for the data transfer. + struct wlr_data_offer *offer, *tmp; + wl_list_for_each_safe(offer, tmp, + &drag->focus_client->seat->drag_offers, link) { + struct wl_client *client = wl_resource_get_client(offer->resource); + if (!drag->dropped && offer->source == drag->source && + client == drag->focus_client->client) { + offer->source = NULL; + data_offer_destroy(offer); + } + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &drag->focus_client->data_devices) { + wl_data_device_send_leave(resource); + } + + drag->focus_client = NULL; + drag->focus = NULL; + } + + if (!surface) { + goto out; + } + + if (!drag->source && drag->seat_client && + wl_resource_get_client(surface->resource) != + drag->seat_client->client) { + goto out; + } + + struct wlr_seat_client *focus_client = wlr_seat_client_for_wl_client( + drag->seat, wl_resource_get_client(surface->resource)); + if (!focus_client) { + goto out; + } + + if (drag->source != NULL) { + drag->source->accepted = false; + + uint32_t serial = + wl_display_next_serial(drag->seat->display); + + struct wl_resource *device_resource; + wl_resource_for_each(device_resource, &focus_client->data_devices) { + struct wlr_data_offer *offer = data_offer_create(device_resource, + drag->source, WLR_DATA_OFFER_DRAG); + if (offer == NULL) { + wl_resource_post_no_memory(device_resource); + return; + } + + data_offer_update_action(offer); + + if (wl_resource_get_version(offer->resource) >= + WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION) { + wl_data_offer_send_source_actions(offer->resource, + drag->source->actions); + } + + wl_data_device_send_enter(device_resource, serial, + surface->resource, + wl_fixed_from_double(sx), wl_fixed_from_double(sy), + offer->resource); + } + } + + drag->focus = surface; + drag->focus_client = focus_client; + drag->seat_client_destroy.notify = drag_handle_seat_client_destroy; + wl_signal_add(&focus_client->events.destroy, &drag->seat_client_destroy); + +out: + wl_signal_emit_mutable(&drag->events.focus, drag); +} + +static void drag_icon_destroy(struct wlr_drag_icon *icon) { + icon->drag->icon = NULL; + wl_list_remove(&icon->surface_destroy.link); + wl_signal_emit_mutable(&icon->events.destroy, icon); + free(icon); +} + +static void drag_destroy(struct wlr_drag *drag) { + if (drag->cancelling) { + return; + } + drag->cancelling = true; + + if (drag->started) { + wlr_seat_keyboard_end_grab(drag->seat); + switch (drag->grab_type) { + case WLR_DRAG_GRAB_KEYBOARD: + break; + case WLR_DRAG_GRAB_KEYBOARD_POINTER: + wlr_seat_pointer_end_grab(drag->seat); + break; + case WLR_DRAG_GRAB_KEYBOARD_TOUCH: + wlr_seat_touch_end_grab(drag->seat); + break; + } + } + + if (drag->started) { + drag_set_focus(drag, NULL, 0, 0); + + assert(drag->seat->drag == drag); + drag->seat->drag = NULL; + } + + // We issue destroy after ending the grab to allow focus changes. + // Furthermore, we wait until after clearing the drag focus in order + // to ensure that the wl_data_device.leave is sent before emitting the + // signal. This allows e.g. wl_pointer.enter to be sent in the destroy + // signal handler. + wl_signal_emit_mutable(&drag->events.destroy, drag); + + if (drag->source) { + wl_list_remove(&drag->source_destroy.link); + } + + if (drag->icon != NULL) { + drag_icon_destroy(drag->icon); + } + free(drag); +} + +static void drag_handle_pointer_enter(struct wlr_seat_pointer_grab *grab, + struct wlr_surface *surface, double sx, double sy) { + struct wlr_drag *drag = grab->data; + drag_set_focus(drag, surface, sx, sy); +} + +static void drag_handle_pointer_clear_focus(struct wlr_seat_pointer_grab *grab) { + struct wlr_drag *drag = grab->data; + drag_set_focus(drag, NULL, 0, 0); +} + +static void drag_handle_pointer_motion(struct wlr_seat_pointer_grab *grab, + uint32_t time, double sx, double sy) { + struct wlr_drag *drag = grab->data; + if (drag->focus != NULL && drag->focus_client != NULL) { + struct wl_resource *resource; + wl_resource_for_each(resource, &drag->focus_client->data_devices) { + wl_data_device_send_motion(resource, time, wl_fixed_from_double(sx), + wl_fixed_from_double(sy)); + } + + struct wlr_drag_motion_event event = { + .drag = drag, + .time = time, + .sx = sx, + .sy = sy, + }; + wl_signal_emit_mutable(&drag->events.motion, &event); + } +} + +static void drag_drop(struct wlr_drag *drag, uint32_t time) { + assert(drag->focus_client); + + drag->dropped = true; + + struct wl_resource *resource; + wl_resource_for_each(resource, &drag->focus_client->data_devices) { + wl_data_device_send_drop(resource); + } + if (drag->source) { + wlr_data_source_dnd_drop(drag->source); + } + + struct wlr_drag_drop_event event = { + .drag = drag, + .time = time, + }; + wl_signal_emit_mutable(&drag->events.drop, &event); +} + +static uint32_t drag_handle_pointer_button(struct wlr_seat_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state) { + struct wlr_drag *drag = grab->data; + + if (drag->source && + grab->seat->pointer_state.grab_button == button && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + if (drag->focus_client && drag->source->current_dnd_action && + drag->source->accepted) { + drag_drop(drag, time); + } else if (drag->source->impl->dnd_finish) { + // This will end the grab and free `drag` + wlr_data_source_destroy(drag->source); + return 0; + } + } + + if (grab->seat->pointer_state.button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + drag_destroy(drag); + } + + return 0; +} + +static void drag_handle_pointer_axis(struct wlr_seat_pointer_grab *grab, + uint32_t time, enum wl_pointer_axis orientation, double value, + int32_t value_discrete, enum wl_pointer_axis_source source, + enum wl_pointer_axis_relative_direction relative_direction) { + // This space is intentionally left blank +} + +static void drag_handle_pointer_cancel(struct wlr_seat_pointer_grab *grab) { + struct wlr_drag *drag = grab->data; + drag_destroy(drag); +} + +static const struct wlr_pointer_grab_interface + data_device_pointer_drag_interface = { + .enter = drag_handle_pointer_enter, + .clear_focus = drag_handle_pointer_clear_focus, + .motion = drag_handle_pointer_motion, + .button = drag_handle_pointer_button, + .axis = drag_handle_pointer_axis, + .cancel = drag_handle_pointer_cancel, +}; + +static uint32_t drag_handle_touch_down(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + // eat the event + return 0; +} + +static void drag_handle_touch_up(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + struct wlr_drag *drag = grab->data; + if (drag->grab_touch_id != point->touch_id) { + return; + } + + if (drag->focus_client) { + drag_drop(drag, time); + } + + drag_destroy(drag); +} + +static void drag_handle_touch_motion(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + struct wlr_drag *drag = grab->data; + if (drag->focus && drag->focus_client) { + struct wl_resource *resource; + wl_resource_for_each(resource, &drag->focus_client->data_devices) { + wl_data_device_send_motion(resource, time, + wl_fixed_from_double(point->sx), + wl_fixed_from_double(point->sy)); + } + } +} + +static void drag_handle_touch_enter(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + struct wlr_drag *drag = grab->data; + drag_set_focus(drag, point->focus_surface, point->sx, point->sy); +} + +static void drag_handle_touch_cancel(struct wlr_seat_touch_grab *grab) { + struct wlr_drag *drag = grab->data; + drag_destroy(drag); +} + +static const struct wlr_touch_grab_interface + data_device_touch_drag_interface = { + .down = drag_handle_touch_down, + .up = drag_handle_touch_up, + .motion = drag_handle_touch_motion, + .enter = drag_handle_touch_enter, + .cancel = drag_handle_touch_cancel, +}; + +static void drag_handle_keyboard_enter(struct wlr_seat_keyboard_grab *grab, + struct wlr_surface *surface, const uint32_t keycodes[], size_t num_keycodes, + const struct wlr_keyboard_modifiers *modifiers) { + // nothing has keyboard focus during drags +} + +static void drag_handle_keyboard_clear_focus(struct wlr_seat_keyboard_grab *grab) { + // nothing has keyboard focus during drags +} + +static void drag_handle_keyboard_key(struct wlr_seat_keyboard_grab *grab, + uint32_t time, uint32_t key, uint32_t state) { + // no keyboard input during drags +} + +static void drag_handle_keyboard_modifiers(struct wlr_seat_keyboard_grab *grab, + const struct wlr_keyboard_modifiers *modifiers) { + //struct wlr_keyboard *keyboard = grab->seat->keyboard_state.keyboard; + // TODO change the dnd action based on what modifier is pressed on the + // keyboard +} + +static void drag_handle_keyboard_cancel(struct wlr_seat_keyboard_grab *grab) { + struct wlr_drag *drag = grab->data; + drag_destroy(drag); +} + +static const struct wlr_keyboard_grab_interface + data_device_keyboard_drag_interface = { + .enter = drag_handle_keyboard_enter, + .clear_focus = drag_handle_keyboard_clear_focus, + .key = drag_handle_keyboard_key, + .modifiers = drag_handle_keyboard_modifiers, + .cancel = drag_handle_keyboard_cancel, +}; + +static void drag_handle_icon_destroy(struct wl_listener *listener, void *data) { + struct wlr_drag *drag = wl_container_of(listener, drag, icon_destroy); + drag->icon = NULL; +} + +static void drag_handle_drag_source_destroy(struct wl_listener *listener, + void *data) { + struct wlr_drag *drag = wl_container_of(listener, drag, source_destroy); + drag_destroy(drag); +} + +static void drag_icon_surface_role_commit(struct wlr_surface *surface) { + assert(surface->role == &drag_icon_surface_role); + + pixman_region32_clear(&surface->input_region); + if (wlr_surface_has_buffer(surface)) { + wlr_surface_map(surface); + } +} + +const struct wlr_surface_role drag_icon_surface_role = { + .name = "wl_data_device-icon", + .no_object = true, + .commit = drag_icon_surface_role_commit, +}; + +static void drag_icon_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_drag_icon *icon = + wl_container_of(listener, icon, surface_destroy); + drag_icon_destroy(icon); +} + +static struct wlr_drag_icon *drag_icon_create(struct wlr_drag *drag, + struct wlr_surface *surface) { + struct wlr_drag_icon *icon = calloc(1, sizeof(*icon)); + if (!icon) { + return NULL; + } + + icon->drag = drag; + icon->surface = surface; + + wl_signal_init(&icon->events.destroy); + + icon->surface_destroy.notify = drag_icon_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &icon->surface_destroy); + + drag_icon_surface_role_commit(surface); + + return icon; +} + +struct wlr_drag *wlr_drag_create(struct wlr_seat_client *seat_client, + struct wlr_data_source *source, struct wlr_surface *icon_surface) { + struct wlr_drag *drag = calloc(1, sizeof(*drag)); + if (drag == NULL) { + return NULL; + } + + wl_signal_init(&drag->events.focus); + wl_signal_init(&drag->events.motion); + wl_signal_init(&drag->events.drop); + wl_signal_init(&drag->events.destroy); + + drag->seat = seat_client->seat; + drag->seat_client = seat_client; + + if (icon_surface) { + struct wlr_drag_icon *icon = drag_icon_create(drag, icon_surface); + if (icon == NULL) { + free(drag); + return NULL; + } + + drag->icon = icon; + + drag->icon_destroy.notify = drag_handle_icon_destroy; + wl_signal_add(&icon->events.destroy, &drag->icon_destroy); + } + + drag->source = source; + if (source != NULL) { + drag->source_destroy.notify = drag_handle_drag_source_destroy; + wl_signal_add(&source->events.destroy, &drag->source_destroy); + } + + drag->pointer_grab.data = drag; + drag->pointer_grab.interface = &data_device_pointer_drag_interface; + + drag->touch_grab.data = drag; + drag->touch_grab.interface = &data_device_touch_drag_interface; + + drag->keyboard_grab.data = drag; + drag->keyboard_grab.interface = &data_device_keyboard_drag_interface; + + return drag; +} + +void wlr_seat_request_start_drag(struct wlr_seat *seat, struct wlr_drag *drag, + struct wlr_surface *origin, uint32_t serial) { + assert(drag->seat == seat); + + if (seat->drag != NULL) { + wlr_log(WLR_DEBUG, "Rejecting start_drag request, " + "another drag-and-drop operation is already in progress"); + return; + } + + struct wlr_seat_request_start_drag_event event = { + .drag = drag, + .origin = origin, + .serial = serial, + }; + wl_signal_emit_mutable(&seat->events.request_start_drag, &event); +} + +static void seat_handle_drag_source_destroy(struct wl_listener *listener, + void *data) { + struct wlr_seat *seat = + wl_container_of(listener, seat, drag_source_destroy); + wl_list_remove(&seat->drag_source_destroy.link); + seat->drag_source = NULL; +} + +void wlr_seat_start_drag(struct wlr_seat *seat, struct wlr_drag *drag, + uint32_t serial) { + assert(drag->seat == seat); + assert(!drag->started); + drag->started = true; + + wlr_seat_keyboard_start_grab(seat, &drag->keyboard_grab); + + seat->drag = drag; + seat->drag_serial = serial; + + // We need to destroy the previous source, because listeners only expect one + // active drag source at a time. + wlr_data_source_destroy(seat->drag_source); + seat->drag_source = drag->source; + if (drag->source != NULL) { + seat->drag_source_destroy.notify = seat_handle_drag_source_destroy; + wl_signal_add(&drag->source->events.destroy, &seat->drag_source_destroy); + } + + wl_signal_emit_mutable(&seat->events.start_drag, drag); +} + +void wlr_seat_start_pointer_drag(struct wlr_seat *seat, struct wlr_drag *drag, + uint32_t serial) { + drag->grab_type = WLR_DRAG_GRAB_KEYBOARD_POINTER; + + wlr_seat_pointer_clear_focus(seat); + wlr_seat_pointer_start_grab(seat, &drag->pointer_grab); + + wlr_seat_start_drag(seat, drag, serial); +} + +void wlr_seat_start_touch_drag(struct wlr_seat *seat, struct wlr_drag *drag, + uint32_t serial, struct wlr_touch_point *point) { + drag->grab_type = WLR_DRAG_GRAB_KEYBOARD_TOUCH; + drag->grab_touch_id = seat->touch_state.grab_id; + drag->touch_id = point->touch_id; + + wlr_seat_touch_start_grab(seat, &drag->touch_grab); + drag_set_focus(drag, point->surface, point->sx, point->sy); + + wlr_seat_start_drag(seat, drag, serial); +} diff --git a/types/meson.build b/types/meson.build new file mode 100644 index 0000000..8962a39 --- /dev/null +++ b/types/meson.build @@ -0,0 +1,101 @@ +wlr_files += files( + 'data_device/wlr_data_device.c', + 'data_device/wlr_data_offer.c', + 'data_device/wlr_data_source.c', + 'data_device/wlr_drag.c', + 'output/cursor.c', + 'output/output.c', + 'output/render.c', + 'output/state.c', + 'output/swapchain.c', + 'scene/drag_icon.c', + 'scene/subsurface_tree.c', + 'scene/surface.c', + 'scene/wlr_scene.c', + 'scene/output_layout.c', + 'scene/xdg_shell.c', + 'scene/layer_shell_v1.c', + 'seat/wlr_seat_keyboard.c', + 'seat/wlr_seat_pointer.c', + 'seat/wlr_seat_touch.c', + 'seat/wlr_seat.c', + 'tablet_v2/wlr_tablet_v2_pad.c', + 'tablet_v2/wlr_tablet_v2_tablet.c', + 'tablet_v2/wlr_tablet_v2_tool.c', + 'tablet_v2/wlr_tablet_v2.c', + 'xdg_shell/wlr_xdg_popup.c', + 'xdg_shell/wlr_xdg_positioner.c', + 'xdg_shell/wlr_xdg_shell.c', + 'xdg_shell/wlr_xdg_surface.c', + 'xdg_shell/wlr_xdg_toplevel.c', + 'buffer/buffer.c', + 'buffer/client.c', + 'buffer/dmabuf.c', + 'buffer/readonly_data.c', + 'buffer/resource.c', + 'wlr_compositor.c', + 'wlr_content_type_v1.c', + 'wlr_cursor_shape_v1.c', + 'wlr_cursor.c', + 'wlr_damage_ring.c', + 'wlr_data_control_v1.c', + 'wlr_drm.c', + 'wlr_export_dmabuf_v1.c', + 'wlr_foreign_toplevel_management_v1.c', + 'wlr_ext_foreign_toplevel_list_v1.c', + 'wlr_fullscreen_shell_v1.c', + 'wlr_gamma_control_v1.c', + 'wlr_idle_inhibit_v1.c', + 'wlr_idle_notify_v1.c', + 'wlr_input_device.c', + 'wlr_input_method_v2.c', + 'wlr_keyboard.c', + 'wlr_keyboard_group.c', + 'wlr_keyboard_shortcuts_inhibit_v1.c', + 'wlr_layer_shell_v1.c', + 'wlr_linux_dmabuf_v1.c', + 'wlr_matrix.c', + 'wlr_output_layer.c', + 'wlr_output_layout.c', + 'wlr_output_management_v1.c', + 'wlr_output_power_management_v1.c', + 'wlr_pointer_constraints_v1.c', + 'wlr_pointer_gestures_v1.c', + 'wlr_pointer.c', + 'wlr_presentation_time.c', + 'wlr_primary_selection_v1.c', + 'wlr_primary_selection.c', + 'wlr_region.c', + 'wlr_relative_pointer_v1.c', + 'wlr_screencopy_v1.c', + 'wlr_security_context_v1.c', + 'wlr_server_decoration.c', + 'wlr_session_lock_v1.c', + 'wlr_shm.c', + 'wlr_single_pixel_buffer_v1.c', + 'wlr_subcompositor.c', + 'wlr_fractional_scale_v1.c', + 'wlr_switch.c', + 'wlr_tablet_pad.c', + 'wlr_tablet_tool.c', + 'wlr_text_input_v3.c', + 'wlr_touch.c', + 'wlr_transient_seat_v1.c', + 'wlr_viewporter.c', + 'wlr_virtual_keyboard_v1.c', + 'wlr_virtual_pointer_v1.c', + 'wlr_xcursor_manager.c', + 'wlr_xdg_activation_v1.c', + 'wlr_xdg_decoration_v1.c', + 'wlr_xdg_foreign_v1.c', + 'wlr_xdg_foreign_v2.c', + 'wlr_xdg_foreign_registry.c', + 'wlr_xdg_output_v1.c', + 'wlr_tearing_control_v1.c', +) + +if features.get('drm-backend') + wlr_files += files( + 'wlr_drm_lease_v1.c', + ) +endif diff --git a/types/output/cursor.c b/types/output/cursor.c new file mode 100644 index 0000000..6c2ba4c --- /dev/null +++ b/types/output/cursor.c @@ -0,0 +1,436 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "render/allocator/allocator.h" +#include "types/wlr_buffer.h" +#include "types/wlr_output.h" + +static bool output_set_hardware_cursor(struct wlr_output *output, + struct wlr_buffer *buffer, int hotspot_x, int hotspot_y) { + if (!output->impl->set_cursor) { + return false; + } + + if (!output->impl->set_cursor(output, buffer, hotspot_x, hotspot_y)) { + return false; + } + + wlr_buffer_unlock(output->cursor_front_buffer); + output->cursor_front_buffer = NULL; + + if (buffer != NULL) { + output->cursor_front_buffer = wlr_buffer_lock(buffer); + } + + return true; +} + +static void output_cursor_damage_whole(struct wlr_output_cursor *cursor); + +void wlr_output_lock_software_cursors(struct wlr_output *output, bool lock) { + if (lock) { + ++output->software_cursor_locks; + } else { + assert(output->software_cursor_locks > 0); + --output->software_cursor_locks; + } + wlr_log(WLR_DEBUG, "%s hardware cursors on output '%s' (locks: %d)", + lock ? "Disabling" : "Enabling", output->name, + output->software_cursor_locks); + + if (output->software_cursor_locks > 0 && output->hardware_cursor != NULL) { + output_set_hardware_cursor(output, NULL, 0, 0); + output_cursor_damage_whole(output->hardware_cursor); + output->hardware_cursor = NULL; + } + + // If it's possible to use hardware cursors again, don't switch immediately + // since a recorder is likely to lock software cursors for the next frame + // again. +} + +/** + * Returns the cursor box, scaled for its output. + */ +static void output_cursor_get_box(struct wlr_output_cursor *cursor, + struct wlr_box *box) { + box->x = cursor->x - cursor->hotspot_x; + box->y = cursor->y - cursor->hotspot_y; + box->width = cursor->width; + box->height = cursor->height; +} + +void wlr_output_add_software_cursors_to_render_pass(struct wlr_output *output, + struct wlr_render_pass *render_pass, const pixman_region32_t *damage) { + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + pixman_region32_t render_damage; + pixman_region32_init_rect(&render_damage, 0, 0, width, height); + if (damage != NULL) { + pixman_region32_intersect(&render_damage, &render_damage, damage); + } + + struct wlr_output_cursor *cursor; + wl_list_for_each(cursor, &output->cursors, link) { + if (!cursor->enabled || !cursor->visible || + output->hardware_cursor == cursor) { + continue; + } + + struct wlr_texture *texture = cursor->texture; + if (texture == NULL) { + continue; + } + + struct wlr_box box; + output_cursor_get_box(cursor, &box); + + pixman_region32_t cursor_damage; + pixman_region32_init_rect(&cursor_damage, box.x, box.y, box.width, box.height); + pixman_region32_intersect(&cursor_damage, &cursor_damage, &render_damage); + if (!pixman_region32_not_empty(&cursor_damage)) { + pixman_region32_fini(&cursor_damage); + continue; + } + + enum wl_output_transform transform = + wlr_output_transform_invert(output->transform); + wlr_box_transform(&box, &box, transform, width, height); + wlr_region_transform(&cursor_damage, &cursor_damage, transform, width, height); + + wlr_render_pass_add_texture(render_pass, &(struct wlr_render_texture_options) { + .texture = texture, + .src_box = cursor->src_box, + .dst_box = box, + .clip = &cursor_damage, + .transform = output->transform, + }); + + pixman_region32_fini(&cursor_damage); + } + + pixman_region32_fini(&render_damage); +} + +static void output_cursor_damage_whole(struct wlr_output_cursor *cursor) { + struct wlr_box box; + output_cursor_get_box(cursor, &box); + + pixman_region32_t damage; + pixman_region32_init_rect(&damage, box.x, box.y, box.width, box.height); + + struct wlr_output_event_damage event = { + .output = cursor->output, + .damage = &damage, + }; + wl_signal_emit_mutable(&cursor->output->events.damage, &event); + + pixman_region32_fini(&damage); +} + +static void output_cursor_reset(struct wlr_output_cursor *cursor) { + if (cursor->output->hardware_cursor != cursor) { + output_cursor_damage_whole(cursor); + } +} + +static void output_cursor_update_visible(struct wlr_output_cursor *cursor) { + struct wlr_box output_box; + output_box.x = output_box.y = 0; + wlr_output_transformed_resolution(cursor->output, &output_box.width, + &output_box.height); + + struct wlr_box cursor_box; + output_cursor_get_box(cursor, &cursor_box); + + struct wlr_box intersection; + cursor->visible = + wlr_box_intersection(&intersection, &output_box, &cursor_box); +} + +static bool output_pick_cursor_format(struct wlr_output *output, + struct wlr_drm_format *format) { + struct wlr_allocator *allocator = output->allocator; + assert(allocator != NULL); + + const struct wlr_drm_format_set *display_formats = NULL; + if (output->impl->get_cursor_formats) { + display_formats = + output->impl->get_cursor_formats(output, allocator->buffer_caps); + if (display_formats == NULL) { + wlr_log(WLR_DEBUG, "Failed to get cursor display formats"); + return false; + } + } + + return output_pick_format(output, display_formats, format, DRM_FORMAT_ARGB8888); +} + +static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor) { + struct wlr_output *output = cursor->output; + + struct wlr_texture *texture = cursor->texture; + if (texture == NULL) { + return NULL; + } + + struct wlr_allocator *allocator = output->allocator; + struct wlr_renderer *renderer = output->renderer; + assert(allocator != NULL && renderer != NULL); + + int width = cursor->width; + int height = cursor->height; + if (output->impl->get_cursor_size) { + // Apply hardware limitations on buffer size + output->impl->get_cursor_size(cursor->output, &width, &height); + if ((int)texture->width > width || (int)texture->height > height) { + wlr_log(WLR_DEBUG, "Cursor texture too large (%dx%d), " + "exceeds hardware limitations (%dx%d)", texture->width, + texture->height, width, height); + return NULL; + } + } + + if (output->cursor_swapchain == NULL || + output->cursor_swapchain->width != width || + output->cursor_swapchain->height != height) { + struct wlr_drm_format format = {0}; + if (!output_pick_cursor_format(output, &format)) { + wlr_log(WLR_DEBUG, "Failed to pick cursor format"); + return NULL; + } + + wlr_swapchain_destroy(output->cursor_swapchain); + output->cursor_swapchain = wlr_swapchain_create(allocator, + width, height, &format); + wlr_drm_format_finish(&format); + if (output->cursor_swapchain == NULL) { + wlr_log(WLR_ERROR, "Failed to create cursor swapchain"); + return NULL; + } + } + + struct wlr_buffer *buffer = + wlr_swapchain_acquire(output->cursor_swapchain, NULL); + if (buffer == NULL) { + return NULL; + } + + struct wlr_box dst_box = { + .width = cursor->width, + .height = cursor->height, + }; + wlr_box_transform(&dst_box, &dst_box, wlr_output_transform_invert(output->transform), + buffer->width, buffer->height); + + struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, buffer, NULL); + if (pass == NULL) { + wlr_buffer_unlock(buffer); + return NULL; + } + + enum wl_output_transform transform = wlr_output_transform_invert(cursor->transform); + transform = wlr_output_transform_compose(transform, output->transform); + + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .box = { .width = buffer->width, .height = buffer->height }, + .blend_mode = WLR_RENDER_BLEND_MODE_NONE, + }); + wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){ + .texture = texture, + .src_box = cursor->src_box, + .dst_box = dst_box, + .transform = transform, + }); + + if (!wlr_render_pass_submit(pass)) { + wlr_buffer_unlock(buffer); + return NULL; + } + + return buffer; +} + +static bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) { + struct wlr_output *output = cursor->output; + + if (!output->impl->set_cursor || + output->software_cursor_locks > 0) { + return false; + } + + struct wlr_output_cursor *hwcur = output->hardware_cursor; + if (hwcur != NULL && hwcur != cursor) { + return false; + } + + struct wlr_texture *texture = cursor->texture; + + // If the cursor was hidden or was a software cursor, the hardware + // cursor position is outdated + output->impl->move_cursor(cursor->output, + (int)cursor->x, (int)cursor->y); + + struct wlr_buffer *buffer = NULL; + if (texture != NULL) { + buffer = render_cursor_buffer(cursor); + if (buffer == NULL) { + wlr_log(WLR_DEBUG, "Failed to render cursor buffer"); + return false; + } + } + + struct wlr_box hotspot = { + .x = cursor->hotspot_x, + .y = cursor->hotspot_y, + }; + wlr_box_transform(&hotspot, &hotspot, + wlr_output_transform_invert(output->transform), + buffer ? buffer->width : 0, buffer ? buffer->height : 0); + + bool ok = output_set_hardware_cursor(output, buffer, hotspot.x, hotspot.y); + wlr_buffer_unlock(buffer); + if (ok) { + output->hardware_cursor = cursor; + } + return ok; +} + +bool wlr_output_cursor_set_buffer(struct wlr_output_cursor *cursor, + struct wlr_buffer *buffer, int32_t hotspot_x, int32_t hotspot_y) { + struct wlr_renderer *renderer = cursor->output->renderer; + assert(renderer != NULL); + + struct wlr_texture *texture = NULL; + struct wlr_fbox src_box = {0}; + int dst_width = 0, dst_height = 0; + if (buffer != NULL) { + texture = wlr_texture_from_buffer(renderer, buffer); + if (texture == NULL) { + return false; + } + + src_box = (struct wlr_fbox){ + .width = texture->width, + .height = texture->height, + }; + + dst_width = texture->width / cursor->output->scale; + dst_height = texture->height / cursor->output->scale; + } + + hotspot_x /= cursor->output->scale; + hotspot_y /= cursor->output->scale; + + return output_cursor_set_texture(cursor, texture, true, &src_box, + dst_width, dst_height, WL_OUTPUT_TRANSFORM_NORMAL, hotspot_x, hotspot_y); +} + +bool output_cursor_set_texture(struct wlr_output_cursor *cursor, + struct wlr_texture *texture, bool own_texture, const struct wlr_fbox *src_box, + int dst_width, int dst_height, enum wl_output_transform transform, + int32_t hotspot_x, int32_t hotspot_y) { + struct wlr_output *output = cursor->output; + + output_cursor_reset(cursor); + + cursor->enabled = texture != NULL; + if (texture != NULL) { + cursor->width = (int)roundf(dst_width * output->scale); + cursor->height = (int)roundf(dst_height * output->scale); + cursor->src_box = *src_box; + cursor->transform = transform; + } else { + cursor->width = 0; + cursor->height = 0; + } + + cursor->hotspot_x = (int)roundf(hotspot_x * output->scale); + cursor->hotspot_y = (int)roundf(hotspot_y * output->scale); + + output_cursor_update_visible(cursor); + + if (cursor->own_texture) { + wlr_texture_destroy(cursor->texture); + } + cursor->texture = texture; + cursor->own_texture = own_texture; + + if (output_cursor_attempt_hardware(cursor)) { + return true; + } + + wlr_log(WLR_DEBUG, "Falling back to software cursor on output '%s'", + cursor->output->name); + output_cursor_damage_whole(cursor); + return true; +} + +bool wlr_output_cursor_move(struct wlr_output_cursor *cursor, + double x, double y) { + // Scale coordinates for the output + x *= cursor->output->scale; + y *= cursor->output->scale; + + if (cursor->x == x && cursor->y == y) { + return true; + } + + if (cursor->output->hardware_cursor != cursor) { + output_cursor_damage_whole(cursor); + } + + cursor->x = x; + cursor->y = y; + bool was_visible = cursor->visible; + output_cursor_update_visible(cursor); + + if (!was_visible && !cursor->visible) { + // Cursor is still hidden, do nothing + return true; + } + + if (cursor->output->hardware_cursor != cursor) { + output_cursor_damage_whole(cursor); + return true; + } + + assert(cursor->output->impl->move_cursor); + return cursor->output->impl->move_cursor(cursor->output, (int)x, (int)y); +} + +struct wlr_output_cursor *wlr_output_cursor_create(struct wlr_output *output) { + struct wlr_output_cursor *cursor = calloc(1, sizeof(*cursor)); + if (cursor == NULL) { + return NULL; + } + cursor->output = output; + wl_list_insert(&output->cursors, &cursor->link); + cursor->visible = true; // default position is at (0, 0) + return cursor; +} + +void wlr_output_cursor_destroy(struct wlr_output_cursor *cursor) { + if (cursor == NULL) { + return; + } + output_cursor_reset(cursor); + if (cursor->output->hardware_cursor == cursor) { + // If this cursor was the hardware cursor, disable it + output_set_hardware_cursor(cursor->output, NULL, 0, 0); + cursor->output->hardware_cursor = NULL; + } + if (cursor->own_texture) { + wlr_texture_destroy(cursor->texture); + } + wl_list_remove(&cursor->link); + free(cursor); +} diff --git a/types/output/output.c b/types/output/output.c new file mode 100644 index 0000000..da89bb4 --- /dev/null +++ b/types/output/output.c @@ -0,0 +1,887 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "render/allocator/allocator.h" +#include "types/wlr_output.h" +#include "util/env.h" +#include "util/global.h" + +#define OUTPUT_VERSION 4 + +static void send_geometry(struct wl_resource *resource) { + struct wlr_output *output = wlr_output_from_resource(resource); + + const char *make = output->make; + if (make == NULL) { + make = "Unknown"; + } + + const char *model = output->model; + if (model == NULL) { + model = "Unknown"; + } + + wl_output_send_geometry(resource, 0, 0, + output->phys_width, output->phys_height, output->subpixel, + make, model, output->transform); +} + +static void send_current_mode(struct wl_resource *resource) { + struct wlr_output *output = wlr_output_from_resource(resource); + if (output->current_mode != NULL) { + struct wlr_output_mode *mode = output->current_mode; + wl_output_send_mode(resource, WL_OUTPUT_MODE_CURRENT, + mode->width, mode->height, mode->refresh); + } else { + // Output has no mode + wl_output_send_mode(resource, WL_OUTPUT_MODE_CURRENT, output->width, + output->height, output->refresh); + } +} + +static void send_scale(struct wl_resource *resource) { + struct wlr_output *output = wlr_output_from_resource(resource); + uint32_t version = wl_resource_get_version(resource); + if (version >= WL_OUTPUT_SCALE_SINCE_VERSION) { + wl_output_send_scale(resource, (uint32_t)ceil(output->scale)); + } +} + +static void send_name(struct wl_resource *resource) { + struct wlr_output *output = wlr_output_from_resource(resource); + uint32_t version = wl_resource_get_version(resource); + if (version >= WL_OUTPUT_NAME_SINCE_VERSION) { + wl_output_send_name(resource, output->name); + } +} + +static void send_description(struct wl_resource *resource) { + struct wlr_output *output = wlr_output_from_resource(resource); + uint32_t version = wl_resource_get_version(resource); + if (output->description != NULL && + version >= WL_OUTPUT_DESCRIPTION_SINCE_VERSION) { + wl_output_send_description(resource, output->description); + } +} + +static void send_done(struct wl_resource *resource) { + uint32_t version = wl_resource_get_version(resource); + if (version >= WL_OUTPUT_DONE_SINCE_VERSION) { + wl_output_send_done(resource); + } +} + +static void output_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void output_handle_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_output_interface output_impl = { + .release = output_handle_release, +}; + +static void output_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + // `output` can be NULL if the output global is being destroyed + struct wlr_output *output = data; + + struct wl_resource *resource = wl_resource_create(wl_client, + &wl_output_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, &output_impl, output, + output_handle_resource_destroy); + + if (output == NULL) { + wl_list_init(wl_resource_get_link(resource)); + return; + } + + wl_list_insert(&output->resources, wl_resource_get_link(resource)); + + send_geometry(resource); + send_current_mode(resource); + send_scale(resource); + send_name(resource); + send_description(resource); + send_done(resource); + + struct wlr_output_event_bind evt = { + .output = output, + .resource = resource, + }; + + wl_signal_emit_mutable(&output->events.bind, &evt); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_output *output = wl_container_of(listener, output, display_destroy); + wlr_output_destroy_global(output); +} + +void wlr_output_create_global(struct wlr_output *output, struct wl_display *display) { + if (output->global != NULL) { + return; + } + + output->global = wl_global_create(display, + &wl_output_interface, OUTPUT_VERSION, output, output_bind); + if (output->global == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate wl_output global"); + return; + } + + wl_list_remove(&output->display_destroy.link); + wl_display_add_destroy_listener(display, &output->display_destroy); +} + +void wlr_output_destroy_global(struct wlr_output *output) { + if (output->global == NULL) { + return; + } + + // Make all output resources inert + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &output->resources) { + wl_resource_set_user_data(resource, NULL); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } + + wl_list_remove(&output->display_destroy.link); + wl_list_init(&output->display_destroy.link); + + wlr_global_destroy_safe(output->global); + output->global = NULL; +} + +static void schedule_done_handle_idle_timer(void *data) { + struct wlr_output *output = data; + output->idle_done = NULL; + + struct wl_resource *resource; + wl_resource_for_each(resource, &output->resources) { + send_done(resource); + } +} + +void wlr_output_schedule_done(struct wlr_output *output) { + if (output->idle_done != NULL) { + return; // Already scheduled + } + + output->idle_done = wl_event_loop_add_idle(output->event_loop, + schedule_done_handle_idle_timer, output); +} + +struct wlr_output *wlr_output_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_output_interface, + &output_impl)); + return wl_resource_get_user_data(resource); +} + +void wlr_output_set_name(struct wlr_output *output, const char *name) { + assert(output->global == NULL); + + free(output->name); + output->name = strdup(name); +} + +void wlr_output_set_description(struct wlr_output *output, const char *desc) { + if (output->description != NULL && desc != NULL && + strcmp(output->description, desc) == 0) { + return; + } + + free(output->description); + if (desc != NULL) { + output->description = strdup(desc); + } else { + output->description = NULL; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &output->resources) { + send_description(resource); + } + wlr_output_schedule_done(output); + + wl_signal_emit_mutable(&output->events.description, output); +} + +static void output_apply_state(struct wlr_output *output, + const struct wlr_output_state *state) { + if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { + output->render_format = state->render_format; + } + + if (state->committed & WLR_OUTPUT_STATE_SUBPIXEL) { + output->subpixel = state->subpixel; + } + + if (state->committed & WLR_OUTPUT_STATE_ENABLED) { + output->enabled = state->enabled; + } + + bool scale_updated = state->committed & WLR_OUTPUT_STATE_SCALE; + if (scale_updated) { + output->scale = state->scale; + } + + if (state->committed & WLR_OUTPUT_STATE_TRANSFORM) { + output->transform = state->transform; + } + + bool geometry_updated = state->committed & + (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_TRANSFORM | + WLR_OUTPUT_STATE_SUBPIXEL); + + // Destroy the swapchains when an output is disabled + if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) { + wlr_swapchain_destroy(output->swapchain); + output->swapchain = NULL; + wlr_swapchain_destroy(output->cursor_swapchain); + output->cursor_swapchain = NULL; + } + + if (state->committed & WLR_OUTPUT_STATE_LAYERS) { + for (size_t i = 0; i < state->layers_len; i++) { + struct wlr_output_layer_state *layer_state = &state->layers[i]; + struct wlr_output_layer *layer = layer_state->layer; + + // Commit layer ordering + wl_list_remove(&layer->link); + wl_list_insert(output->layers.prev, &layer->link); + + // Commit layer state + layer->src_box = layer_state->src_box; + layer->dst_box = layer_state->dst_box; + } + } + + if ((state->committed & WLR_OUTPUT_STATE_BUFFER) && + output->swapchain != NULL) { + wlr_swapchain_set_buffer_submitted(output->swapchain, state->buffer); + } + + bool mode_updated = false; + if (state->committed & WLR_OUTPUT_STATE_MODE) { + int width = 0, height = 0, refresh = 0; + switch (state->mode_type) { + case WLR_OUTPUT_STATE_MODE_FIXED:; + struct wlr_output_mode *mode = state->mode; + output->current_mode = mode; + if (mode != NULL) { + width = mode->width; + height = mode->height; + refresh = mode->refresh; + } + break; + case WLR_OUTPUT_STATE_MODE_CUSTOM: + output->current_mode = NULL; + width = state->custom_mode.width; + height = state->custom_mode.height; + refresh = state->custom_mode.refresh; + break; + } + + if (output->width != width || output->height != height || + output->refresh != refresh) { + output->width = width; + output->height = height; + + output->refresh = refresh; + + if (output->swapchain != NULL && + (output->swapchain->width != output->width || + output->swapchain->height != output->height)) { + wlr_swapchain_destroy(output->swapchain); + output->swapchain = NULL; + } + + mode_updated = true; + } + } + + if (geometry_updated || scale_updated || mode_updated) { + struct wl_resource *resource; + wl_resource_for_each(resource, &output->resources) { + if (mode_updated) { + send_current_mode(resource); + } + if (geometry_updated) { + send_geometry(resource); + } + if (scale_updated) { + send_scale(resource); + } + } + wlr_output_schedule_done(output); + } +} + +void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, + const struct wlr_output_impl *impl, struct wl_event_loop *event_loop, + const struct wlr_output_state *state) { + assert(impl->commit); + if (impl->set_cursor || impl->move_cursor) { + assert(impl->set_cursor && impl->move_cursor); + } + + *output = (struct wlr_output){ + .backend = backend, + .impl = impl, + .event_loop = event_loop, + .render_format = DRM_FORMAT_XRGB8888, + .transform = WL_OUTPUT_TRANSFORM_NORMAL, + .scale = 1, + .commit_seq = 0, + }; + + wl_list_init(&output->modes); + wl_list_init(&output->cursors); + wl_list_init(&output->layers); + wl_list_init(&output->resources); + wl_signal_init(&output->events.frame); + wl_signal_init(&output->events.damage); + wl_signal_init(&output->events.needs_frame); + wl_signal_init(&output->events.precommit); + wl_signal_init(&output->events.commit); + wl_signal_init(&output->events.present); + wl_signal_init(&output->events.bind); + wl_signal_init(&output->events.description); + wl_signal_init(&output->events.request_state); + wl_signal_init(&output->events.destroy); + + output->software_cursor_locks = env_parse_bool("WLR_NO_HARDWARE_CURSORS"); + if (output->software_cursor_locks) { + wlr_log(WLR_DEBUG, "WLR_NO_HARDWARE_CURSORS set, forcing software cursors"); + } + + wlr_addon_set_init(&output->addons); + + wl_list_init(&output->display_destroy.link); + output->display_destroy.notify = handle_display_destroy; + + if (state) { + output_apply_state(output, state); + } +} + +void wlr_output_destroy(struct wlr_output *output) { + if (!output) { + return; + } + + wl_signal_emit_mutable(&output->events.destroy, output); + + wlr_output_destroy_global(output); + + wl_list_remove(&output->display_destroy.link); + + wlr_addon_set_finish(&output->addons); + + // The backend is responsible for free-ing the list of modes + + struct wlr_output_cursor *cursor, *tmp_cursor; + wl_list_for_each_safe(cursor, tmp_cursor, &output->cursors, link) { + wlr_output_cursor_destroy(cursor); + } + + struct wlr_output_layer *layer, *tmp_layer; + wl_list_for_each_safe(layer, tmp_layer, &output->layers, link) { + wlr_output_layer_destroy(layer); + } + + wlr_swapchain_destroy(output->cursor_swapchain); + wlr_buffer_unlock(output->cursor_front_buffer); + + wlr_swapchain_destroy(output->swapchain); + + if (output->idle_frame != NULL) { + wl_event_source_remove(output->idle_frame); + } + + if (output->idle_done != NULL) { + wl_event_source_remove(output->idle_done); + } + + free(output->name); + free(output->description); + free(output->make); + free(output->model); + free(output->serial); + + if (output->impl && output->impl->destroy) { + output->impl->destroy(output); + } else { + free(output); + } +} + +void wlr_output_transformed_resolution(struct wlr_output *output, + int *width, int *height) { + if (output->transform % 2 == 0) { + *width = output->width; + *height = output->height; + } else { + *width = output->height; + *height = output->width; + } +} + +void wlr_output_effective_resolution(struct wlr_output *output, + int *width, int *height) { + wlr_output_transformed_resolution(output, width, height); + *width /= output->scale; + *height /= output->scale; +} + +struct wlr_output_mode *wlr_output_preferred_mode(struct wlr_output *output) { + if (wl_list_empty(&output->modes)) { + return NULL; + } + + struct wlr_output_mode *mode; + wl_list_for_each(mode, &output->modes, link) { + if (mode->preferred) { + return mode; + } + } + + // No preferred mode, choose the first one + return wl_container_of(output->modes.next, mode, link); +} + +void output_pending_resolution(struct wlr_output *output, + const struct wlr_output_state *state, int *width, int *height) { + if (state->committed & WLR_OUTPUT_STATE_MODE) { + switch (state->mode_type) { + case WLR_OUTPUT_STATE_MODE_FIXED: + *width = state->mode->width; + *height = state->mode->height; + return; + case WLR_OUTPUT_STATE_MODE_CUSTOM: + *width = state->custom_mode.width; + *height = state->custom_mode.height; + return; + } + abort(); + } else { + *width = output->width; + *height = output->height; + } +} + +bool output_pending_enabled(struct wlr_output *output, + const struct wlr_output_state *state) { + if (state->committed & WLR_OUTPUT_STATE_ENABLED) { + return state->enabled; + } + return output->enabled; +} + +/** + * Compare a struct wlr_output_state with the current state of a struct + * wlr_output. + * + * Returns a bitfield of the unchanged fields. + * + * Some fields are not checked: damage always changes in-between frames, the + * gamma LUT is too expensive to check, the contents of the buffer might have + * changed, etc. + */ +static uint32_t output_compare_state(struct wlr_output *output, + const struct wlr_output_state *state) { + uint32_t fields = 0; + if (state->committed & WLR_OUTPUT_STATE_MODE) { + bool unchanged = false; + switch (state->mode_type) { + case WLR_OUTPUT_STATE_MODE_FIXED: + unchanged = output->current_mode == state->mode; + break; + case WLR_OUTPUT_STATE_MODE_CUSTOM: + unchanged = output->width == state->custom_mode.width && + output->height == state->custom_mode.height && + output->refresh == state->custom_mode.refresh; + break; + } + if (unchanged) { + fields |= WLR_OUTPUT_STATE_MODE; + } + } + if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && output->enabled == state->enabled) { + fields |= WLR_OUTPUT_STATE_ENABLED; + } + if ((state->committed & WLR_OUTPUT_STATE_SCALE) && output->scale == state->scale) { + fields |= WLR_OUTPUT_STATE_SCALE; + } + if ((state->committed & WLR_OUTPUT_STATE_TRANSFORM) && + output->transform == state->transform) { + fields |= WLR_OUTPUT_STATE_TRANSFORM; + } + if (state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) { + bool enabled = + output->adaptive_sync_status != WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED; + if (enabled == state->adaptive_sync_enabled) { + fields |= WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED; + } + } + if ((state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) && + output->render_format == state->render_format) { + fields |= WLR_OUTPUT_STATE_RENDER_FORMAT; + } + if ((state->committed & WLR_OUTPUT_STATE_SUBPIXEL) && + output->subpixel == state->subpixel) { + fields |= WLR_OUTPUT_STATE_SUBPIXEL; + } + return fields; +} + +static bool output_basic_test(struct wlr_output *output, + const struct wlr_output_state *state) { + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + // If the size doesn't match, reject buffer (scaling is not + // supported) + int pending_width, pending_height; + output_pending_resolution(output, state, + &pending_width, &pending_height); + if (state->buffer->width != pending_width || + state->buffer->height != pending_height) { + wlr_log(WLR_DEBUG, "Primary buffer size mismatch"); + return false; + } + } else if (state->tearing_page_flip) { + wlr_log(WLR_ERROR, "Trying to commit a tearing page flip without a buffer?"); + return false; + } + + if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { + struct wlr_allocator *allocator = output->allocator; + assert(allocator != NULL); + + const struct wlr_drm_format_set *display_formats = + wlr_output_get_primary_formats(output, allocator->buffer_caps); + struct wlr_drm_format format = {0}; + if (!output_pick_format(output, display_formats, &format, state->render_format)) { + wlr_log(WLR_ERROR, "Failed to pick primary buffer format for output"); + return false; + } + + wlr_drm_format_finish(&format); + } + + bool enabled = output_pending_enabled(output, state); + + if (enabled && (state->committed & (WLR_OUTPUT_STATE_ENABLED | + WLR_OUTPUT_STATE_MODE))) { + int pending_width, pending_height; + output_pending_resolution(output, state, + &pending_width, &pending_height); + if (pending_width == 0 || pending_height == 0) { + wlr_log(WLR_DEBUG, "Tried to enable an output with a zero mode"); + return false; + } + } + + if (!enabled && state->committed & WLR_OUTPUT_STATE_BUFFER) { + wlr_log(WLR_DEBUG, "Tried to commit a buffer on a disabled output"); + return false; + } + if (!enabled && state->committed & WLR_OUTPUT_STATE_MODE) { + wlr_log(WLR_DEBUG, "Tried to modeset a disabled output"); + return false; + } + if (!enabled && state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) { + wlr_log(WLR_DEBUG, "Tried to enable adaptive sync on a disabled output"); + return false; + } + if (!enabled && state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { + wlr_log(WLR_DEBUG, "Tried to set format for a disabled output"); + return false; + } + if (!enabled && state->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { + wlr_log(WLR_DEBUG, "Tried to set the gamma lut on a disabled output"); + return false; + } + if (!enabled && state->committed & WLR_OUTPUT_STATE_SUBPIXEL) { + wlr_log(WLR_DEBUG, "Tried to set the subpixel layout on a disabled output"); + return false; + } + + if (state->committed & WLR_OUTPUT_STATE_LAYERS) { + if (state->layers_len != (size_t)wl_list_length(&output->layers)) { + wlr_log(WLR_DEBUG, "All output layers must be specified in wlr_output_state.layers"); + return false; + } + + for (size_t i = 0; i < state->layers_len; i++) { + state->layers[i].accepted = false; + } + } + + return true; +} + +bool wlr_output_test_state(struct wlr_output *output, + const struct wlr_output_state *state) { + uint32_t unchanged = output_compare_state(output, state); + + // Create a shallow copy of the state with only the fields which have been + // changed and potentially a new buffer. + struct wlr_output_state copy = *state; + copy.committed &= ~unchanged; + + if (!output_basic_test(output, ©)) { + return false; + } + if (!output->impl->test) { + return true; + } + + bool new_back_buffer = false; + if (!output_ensure_buffer(output, ©, &new_back_buffer)) { + return false; + } + + bool success = output->impl->test(output, ©); + if (new_back_buffer) { + wlr_buffer_unlock(copy.buffer); + } + return success; +} + +bool wlr_output_commit_state(struct wlr_output *output, + const struct wlr_output_state *state) { + uint32_t unchanged = output_compare_state(output, state); + + // Create a shallow copy of the state with only the fields which have been + // changed and potentially a new buffer. + struct wlr_output_state pending = *state; + pending.committed &= ~unchanged; + + if (!output_basic_test(output, &pending)) { + wlr_log(WLR_ERROR, "Basic output test failed for %s", output->name); + return false; + } + + bool new_back_buffer = false; + if (!output_ensure_buffer(output, &pending, &new_back_buffer)) { + return false; + } + + if ((pending.committed & WLR_OUTPUT_STATE_BUFFER) && + output->idle_frame != NULL) { + wl_event_source_remove(output->idle_frame); + output->idle_frame = NULL; + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + struct wlr_output_event_precommit pre_event = { + .output = output, + .when = &now, + .state = &pending, + }; + wl_signal_emit_mutable(&output->events.precommit, &pre_event); + + if (!output->impl->commit(output, &pending)) { + if (new_back_buffer) { + wlr_buffer_unlock(pending.buffer); + } + return false; + } + + output->commit_seq++; + + if (output_pending_enabled(output, state)) { + output->frame_pending = true; + output->needs_frame = false; + } + + output_apply_state(output, &pending); + + struct wlr_output_event_commit event = { + .output = output, + .when = &now, + .state = &pending, + }; + wl_signal_emit_mutable(&output->events.commit, &event); + + if (new_back_buffer) { + wlr_buffer_unlock(pending.buffer); + } + + return true; +} + +void wlr_output_send_frame(struct wlr_output *output) { + output->frame_pending = false; + if (output->enabled) { + wl_signal_emit_mutable(&output->events.frame, output); + } +} + +static void schedule_frame_handle_idle_timer(void *data) { + struct wlr_output *output = data; + output->idle_frame = NULL; + if (!output->frame_pending) { + wlr_output_send_frame(output); + } +} + +void wlr_output_schedule_frame(struct wlr_output *output) { + // Make sure the compositor commits a new frame. This is necessary to make + // clients which ask for frame callbacks without submitting a new buffer + // work. + wlr_output_update_needs_frame(output); + + if (output->frame_pending || output->idle_frame != NULL) { + return; + } + + // We're using an idle timer here in case a buffer swap happens right after + // this function is called + output->idle_frame = wl_event_loop_add_idle(output->event_loop, + schedule_frame_handle_idle_timer, output); +} + +void wlr_output_send_present(struct wlr_output *output, + struct wlr_output_event_present *event) { + assert(event); + event->output = output; + + struct timespec now; + if (event->presented && event->when == NULL) { + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { + wlr_log_errno(WLR_ERROR, "failed to send output present event: " + "failed to read clock"); + return; + } + event->when = &now; + } + + wl_signal_emit_mutable(&output->events.present, event); +} + +struct deferred_present_event { + struct wlr_output *output; + struct wl_event_source *idle_source; + struct wlr_output_event_present event; + struct wl_listener output_destroy; +}; + +static void deferred_present_event_destroy(struct deferred_present_event *deferred) { + wl_list_remove(&deferred->output_destroy.link); + free(deferred); +} + +static void deferred_present_event_handle_idle(void *data) { + struct deferred_present_event *deferred = data; + wlr_output_send_present(deferred->output, &deferred->event); + deferred_present_event_destroy(deferred); +} + +static void deferred_present_event_handle_output_destroy(struct wl_listener *listener, void *data) { + struct deferred_present_event *deferred = wl_container_of(listener, deferred, output_destroy); + wl_event_source_remove(deferred->idle_source); + deferred_present_event_destroy(deferred); +} + +void output_defer_present(struct wlr_output *output, struct wlr_output_event_present event) { + struct deferred_present_event *deferred = calloc(1, sizeof(*deferred)); + if (!deferred) { + return; + } + *deferred = (struct deferred_present_event){ + .output = output, + .event = event, + }; + deferred->output_destroy.notify = deferred_present_event_handle_output_destroy; + wl_signal_add(&output->events.destroy, &deferred->output_destroy); + + deferred->idle_source = wl_event_loop_add_idle(output->event_loop, + deferred_present_event_handle_idle, deferred); +} + +void wlr_output_send_request_state(struct wlr_output *output, + const struct wlr_output_state *state) { + uint32_t unchanged = output_compare_state(output, state); + struct wlr_output_state copy = *state; + copy.committed &= ~unchanged; + if (copy.committed == 0) { + return; + } + + struct wlr_output_event_request_state event = { + .output = output, + .state = ©, + }; + wl_signal_emit_mutable(&output->events.request_state, &event); +} + +size_t wlr_output_get_gamma_size(struct wlr_output *output) { + if (!output->impl->get_gamma_size) { + return 0; + } + return output->impl->get_gamma_size(output); +} + +void wlr_output_update_needs_frame(struct wlr_output *output) { + if (output->needs_frame) { + return; + } + output->needs_frame = true; + wl_signal_emit_mutable(&output->events.needs_frame, output); +} + +const struct wlr_drm_format_set *wlr_output_get_primary_formats( + struct wlr_output *output, uint32_t buffer_caps) { + if (!output->impl->get_primary_formats) { + return NULL; + } + + const struct wlr_drm_format_set *formats = + output->impl->get_primary_formats(output, buffer_caps); + if (formats == NULL) { + wlr_log(WLR_ERROR, "Failed to get primary display formats"); + + static const struct wlr_drm_format_set empty_format_set = {0}; + return &empty_format_set; + } + + return formats; +} + +bool wlr_output_is_direct_scanout_allowed(struct wlr_output *output) { + if (output->attach_render_locks > 0) { + wlr_log(WLR_DEBUG, "Direct scan-out disabled by lock"); + return false; + } + + // If the output has at least one software cursor, reject direct scan-out + struct wlr_output_cursor *cursor; + wl_list_for_each(cursor, &output->cursors, link) { + if (cursor->enabled && cursor->visible && + cursor != output->hardware_cursor) { + wlr_log(WLR_DEBUG, + "Direct scan-out disabled by software cursor"); + return false; + } + } + + return true; +} diff --git a/types/output/render.c b/types/output/render.c new file mode 100644 index 0000000..c5e86e1 --- /dev/null +++ b/types/output/render.c @@ -0,0 +1,223 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/backend.h" +#include "render/allocator/allocator.h" +#include "render/drm_format_set.h" +#include "render/wlr_renderer.h" +#include "render/pixel_format.h" +#include "types/wlr_output.h" + +bool wlr_output_init_render(struct wlr_output *output, + struct wlr_allocator *allocator, struct wlr_renderer *renderer) { + assert(allocator != NULL && renderer != NULL); + + uint32_t backend_caps = backend_get_buffer_caps(output->backend); + uint32_t renderer_caps = renderer_get_render_buffer_caps(renderer); + + if (!(backend_caps & allocator->buffer_caps)) { + wlr_log(WLR_ERROR, "output backend and allocator buffer capabilities " + "don't match"); + return false; + } else if (!(renderer_caps & allocator->buffer_caps)) { + wlr_log(WLR_ERROR, "renderer and allocator buffer capabilities " + "don't match"); + return false; + } + + wlr_swapchain_destroy(output->swapchain); + output->swapchain = NULL; + + wlr_swapchain_destroy(output->cursor_swapchain); + output->cursor_swapchain = NULL; + + output->allocator = allocator; + output->renderer = renderer; + + return true; +} + +static struct wlr_buffer *output_acquire_empty_buffer(struct wlr_output *output, + const struct wlr_output_state *state) { + assert(!(state->committed & WLR_OUTPUT_STATE_BUFFER)); + + // wlr_output_configure_primary_swapchain() function will call + // wlr_output_test_state(), which can call us again. This is dangerous: we + // risk infinite recursion. However, a buffer will always be supplied in + // wlr_output_test_state(), which will prevent us from being called. + if (!wlr_output_configure_primary_swapchain(output, state, + &output->swapchain)) { + return NULL; + } + + struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain, NULL); + if (buffer == NULL) { + return NULL; + } + + struct wlr_render_pass *pass = + wlr_renderer_begin_buffer_pass(output->renderer, buffer, NULL); + if (pass == NULL) { + wlr_buffer_unlock(buffer); + return NULL; + } + + wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ + .color = { 0, 0, 0, 0 }, + .blend_mode = WLR_RENDER_BLEND_MODE_NONE, + }); + + if (!wlr_render_pass_submit(pass)) { + wlr_buffer_unlock(buffer); + return NULL; + } + + return buffer; +} + +// This function may attach a new, empty buffer if necessary. +// If so, the new_back_buffer out parameter will be set to true. +bool output_ensure_buffer(struct wlr_output *output, + struct wlr_output_state *state, bool *new_buffer) { + assert(*new_buffer == false); + + // If we already have a buffer, we don't need to allocate a new one + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + return true; + } + + // If the compositor hasn't called wlr_output_init_render(), they will use + // their own logic to attach buffers + if (output->renderer == NULL) { + return true; + } + + bool enabled = output->enabled; + if (state->committed & WLR_OUTPUT_STATE_ENABLED) { + enabled = state->enabled; + } + + // If we're lighting up an output or changing its mode, make sure to + // provide a new buffer + bool needs_new_buffer = false; + if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && state->enabled) { + needs_new_buffer = true; + } + if (state->committed & WLR_OUTPUT_STATE_MODE) { + needs_new_buffer = true; + } + if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { + needs_new_buffer = true; + } + if (state->allow_reconfiguration && output->commit_seq == 0 && enabled) { + // On first commit, require a new buffer if the compositor called a + // mode-setting function, even if the mode won't change. This makes it + // so the swapchain is created now. + needs_new_buffer = true; + } + if (!needs_new_buffer) { + return true; + } + + wlr_log(WLR_DEBUG, "Attaching empty buffer to output for modeset"); + struct wlr_buffer *buffer = output_acquire_empty_buffer(output, state); + if (buffer == NULL) { + return false; + } + + *new_buffer = true; + wlr_output_state_set_buffer(state, buffer); + wlr_buffer_unlock(buffer); + return true; +} + +void wlr_output_lock_attach_render(struct wlr_output *output, bool lock) { + if (lock) { + ++output->attach_render_locks; + } else { + assert(output->attach_render_locks > 0); + --output->attach_render_locks; + } + wlr_log(WLR_DEBUG, "%s direct scan-out on output '%s' (locks: %d)", + lock ? "Disabling" : "Enabling", output->name, + output->attach_render_locks); +} + +bool output_pick_format(struct wlr_output *output, + const struct wlr_drm_format_set *display_formats, + struct wlr_drm_format *format, uint32_t fmt) { + struct wlr_renderer *renderer = output->renderer; + struct wlr_allocator *allocator = output->allocator; + assert(renderer != NULL && allocator != NULL); + + const struct wlr_drm_format_set *render_formats = + wlr_renderer_get_render_formats(renderer); + if (render_formats == NULL) { + wlr_log(WLR_ERROR, "Failed to get render formats"); + return false; + } + + const struct wlr_drm_format *render_format = + wlr_drm_format_set_get(render_formats, fmt); + if (render_format == NULL) { + wlr_log(WLR_DEBUG, "Renderer doesn't support format 0x%"PRIX32, fmt); + return false; + } + + if (display_formats != NULL) { + const struct wlr_drm_format *display_format = + wlr_drm_format_set_get(display_formats, fmt); + if (display_format == NULL) { + wlr_log(WLR_DEBUG, "Output doesn't support format 0x%"PRIX32, fmt); + return false; + } + if (!wlr_drm_format_intersect(format, display_format, render_format)) { + wlr_log(WLR_DEBUG, "Failed to intersect display and render " + "modifiers for format 0x%"PRIX32 " on output %s", + fmt, output->name); + return false; + } + } else { + // The output can display any format + if (!wlr_drm_format_copy(format, render_format)) { + return false; + } + } + + if (format->len == 0) { + wlr_drm_format_finish(format); + wlr_log(WLR_DEBUG, "Failed to pick output format"); + return false; + } + + return true; +} + +struct wlr_render_pass *wlr_output_begin_render_pass(struct wlr_output *output, + struct wlr_output_state *state, int *buffer_age, + struct wlr_buffer_pass_options *render_options) { + if (!wlr_output_configure_primary_swapchain(output, state, &output->swapchain)) { + return NULL; + } + + struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain, buffer_age); + if (buffer == NULL) { + return NULL; + } + + struct wlr_renderer *renderer = output->renderer; + assert(renderer != NULL); + struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, buffer, render_options); + if (pass == NULL) { + return NULL; + } + + wlr_output_state_set_buffer(state, buffer); + wlr_buffer_unlock(buffer); + return pass; +} diff --git a/types/output/state.c b/types/output/state.c new file mode 100644 index 0000000..0909b3e --- /dev/null +++ b/types/output/state.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include "types/wlr_output.h" + +void wlr_output_state_init(struct wlr_output_state *state) { + *state = (struct wlr_output_state){0}; + pixman_region32_init(&state->damage); +} + +void wlr_output_state_finish(struct wlr_output_state *state) { + wlr_buffer_unlock(state->buffer); + // struct wlr_buffer is ref'counted, so the pointer may remain valid after + // wlr_buffer_unlock(). Reset the field to NULL to ensure nobody mistakenly + // reads it after output_state_finish(). + state->buffer = NULL; + pixman_region32_fini(&state->damage); + free(state->gamma_lut); +} + +void wlr_output_state_set_enabled(struct wlr_output_state *state, + bool enabled) { + state->committed |= WLR_OUTPUT_STATE_ENABLED; + state->enabled = enabled; + state->allow_reconfiguration = true; +} + +void wlr_output_state_set_mode(struct wlr_output_state *state, + struct wlr_output_mode *mode) { + state->committed |= WLR_OUTPUT_STATE_MODE; + state->mode_type = WLR_OUTPUT_STATE_MODE_FIXED; + state->mode = mode; + state->allow_reconfiguration = true; +} + +void wlr_output_state_set_custom_mode(struct wlr_output_state *state, + int32_t width, int32_t height, int32_t refresh) { + state->committed |= WLR_OUTPUT_STATE_MODE; + state->mode_type = WLR_OUTPUT_STATE_MODE_CUSTOM; + state->custom_mode.width = width; + state->custom_mode.height = height; + state->custom_mode.refresh = refresh; + state->allow_reconfiguration = true; +} + +void wlr_output_state_set_scale(struct wlr_output_state *state, float scale) { + state->committed |= WLR_OUTPUT_STATE_SCALE; + state->scale = scale; +} + +void wlr_output_state_set_transform(struct wlr_output_state *state, + enum wl_output_transform transform) { + state->committed |= WLR_OUTPUT_STATE_TRANSFORM; + state->transform = transform; +} + +void wlr_output_state_set_adaptive_sync_enabled(struct wlr_output_state *state, + bool enabled) { + state->committed |= WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED; + state->adaptive_sync_enabled = enabled; +} + +void wlr_output_state_set_render_format(struct wlr_output_state *state, + uint32_t format) { + state->committed |= WLR_OUTPUT_STATE_RENDER_FORMAT; + state->render_format = format; +} + +void wlr_output_state_set_subpixel(struct wlr_output_state *state, + enum wl_output_subpixel subpixel) { + state->committed |= WLR_OUTPUT_STATE_SUBPIXEL; + state->subpixel = subpixel; +} + +void wlr_output_state_set_buffer(struct wlr_output_state *state, + struct wlr_buffer *buffer) { + state->committed |= WLR_OUTPUT_STATE_BUFFER; + wlr_buffer_unlock(state->buffer); + state->buffer = wlr_buffer_lock(buffer); +} + +void wlr_output_state_set_damage(struct wlr_output_state *state, + const pixman_region32_t *damage) { + state->committed |= WLR_OUTPUT_STATE_DAMAGE; + pixman_region32_copy(&state->damage, damage); +} + +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) { + uint16_t *gamma_lut = NULL; + if (ramp_size > 0) { + gamma_lut = realloc(state->gamma_lut, 3 * ramp_size * sizeof(uint16_t)); + if (gamma_lut == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return false; + } + memcpy(gamma_lut, r, ramp_size * sizeof(uint16_t)); + memcpy(gamma_lut + ramp_size, g, ramp_size * sizeof(uint16_t)); + memcpy(gamma_lut + 2 * ramp_size, b, ramp_size * sizeof(uint16_t)); + } else { + free(state->gamma_lut); + } + + state->committed |= WLR_OUTPUT_STATE_GAMMA_LUT; + state->gamma_lut_size = ramp_size; + state->gamma_lut = gamma_lut; + return true; +} + +void wlr_output_state_set_layers(struct wlr_output_state *state, + struct wlr_output_layer_state *layers, size_t layers_len) { + state->committed |= WLR_OUTPUT_STATE_LAYERS; + state->layers = layers; + state->layers_len = layers_len; +} + +bool wlr_output_state_copy(struct wlr_output_state *dst, + const struct wlr_output_state *src) { + struct wlr_output_state copy = *src; + copy.committed &= ~(WLR_OUTPUT_STATE_BUFFER | + WLR_OUTPUT_STATE_DAMAGE | + WLR_OUTPUT_STATE_GAMMA_LUT); + copy.buffer = NULL; + pixman_region32_init(©.damage); + copy.gamma_lut = NULL; + copy.gamma_lut_size = 0; + + if (src->committed & WLR_OUTPUT_STATE_BUFFER) { + wlr_output_state_set_buffer(©, src->buffer); + } + + if (src->committed & WLR_OUTPUT_STATE_DAMAGE) { + wlr_output_state_set_damage(©, &src->damage); + } + + if (src->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { + const uint16_t *r = src->gamma_lut; + const uint16_t *g = src->gamma_lut + src->gamma_lut_size; + const uint16_t *b = src->gamma_lut + 2 * src->gamma_lut_size; + if (!wlr_output_state_set_gamma_lut(©, src->gamma_lut_size, r, g, b)) { + goto err; + } + } + + wlr_output_state_finish(dst); + *dst = copy; + return true; + +err: + wlr_output_state_finish(©); + return false; +} diff --git a/types/output/swapchain.c b/types/output/swapchain.c new file mode 100644 index 0000000..5a16610 --- /dev/null +++ b/types/output/swapchain.c @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "render/drm_format_set.h" +#include "types/wlr_output.h" + +static struct wlr_swapchain *create_swapchain(struct wlr_output *output, + int width, int height, uint32_t render_format, bool allow_modifiers) { + struct wlr_allocator *allocator = output->allocator; + assert(output->allocator != NULL); + + const struct wlr_drm_format_set *display_formats = + wlr_output_get_primary_formats(output, allocator->buffer_caps); + struct wlr_drm_format format = {0}; + if (!output_pick_format(output, display_formats, &format, render_format)) { + wlr_log(WLR_ERROR, "Failed to pick primary buffer format for output '%s'", + output->name); + return NULL; + } + + char *format_name = drmGetFormatName(format.format); + wlr_log(WLR_DEBUG, "Choosing primary buffer format %s (0x%08"PRIX32") for output '%s'", + format_name ? format_name : "", format.format, output->name); + free(format_name); + + if (!allow_modifiers && (format.len != 1 || format.modifiers[0] != DRM_FORMAT_MOD_LINEAR)) { + if (!wlr_drm_format_has(&format, DRM_FORMAT_MOD_INVALID)) { + wlr_log(WLR_DEBUG, "Implicit modifiers not supported"); + wlr_drm_format_finish(&format); + return NULL; + } + + format.len = 0; + if (!wlr_drm_format_add(&format, DRM_FORMAT_MOD_INVALID)) { + wlr_log(WLR_DEBUG, "Failed to add implicit modifier to format"); + wlr_drm_format_finish(&format); + return NULL; + } + } + + struct wlr_swapchain *swapchain = wlr_swapchain_create(allocator, width, height, &format); + wlr_drm_format_finish(&format); + return swapchain; +} + +static bool test_swapchain(struct wlr_output *output, + struct wlr_swapchain *swapchain, const struct wlr_output_state *state) { + struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain, NULL); + if (buffer == NULL) { + return false; + } + + struct wlr_output_state copy = *state; + copy.committed |= WLR_OUTPUT_STATE_BUFFER; + copy.buffer = buffer; + bool ok = wlr_output_test_state(output, ©); + wlr_buffer_unlock(buffer); + return ok; +} + +bool wlr_output_configure_primary_swapchain(struct wlr_output *output, + const struct wlr_output_state *state, struct wlr_swapchain **swapchain_ptr) { + struct wlr_output_state empty_state; + if (state == NULL) { + wlr_output_state_init(&empty_state); + state = &empty_state; + } + + int width, height; + output_pending_resolution(output, state, &width, &height); + + uint32_t format = output->render_format; + if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { + format = state->render_format; + } + + // Re-use the existing swapchain if possible + struct wlr_swapchain *old_swapchain = *swapchain_ptr; + if (old_swapchain != NULL && + old_swapchain->width == width && old_swapchain->height == height && + old_swapchain->format.format == format) { + return true; + } + + struct wlr_swapchain *swapchain = create_swapchain(output, width, height, format, true); + if (swapchain == NULL) { + wlr_log(WLR_ERROR, "Failed to create swapchain for output '%s'", output->name); + return false; + } + + wlr_log(WLR_DEBUG, "Testing swapchain for output '%s'", output->name); + if (!test_swapchain(output, swapchain, state)) { + wlr_log(WLR_DEBUG, "Output test failed on '%s', retrying without modifiers", + output->name); + wlr_swapchain_destroy(swapchain); + swapchain = create_swapchain(output, width, height, format, false); + if (swapchain == NULL) { + wlr_log(WLR_ERROR, "Failed to create modifier-less swapchain for output '%s'", + output->name); + return false; + } + wlr_log(WLR_DEBUG, "Testing modifier-less swapchain for output '%s'", output->name); + if (!test_swapchain(output, swapchain, state)) { + wlr_log(WLR_ERROR, "Swapchain for output '%s' failed test", output->name); + wlr_swapchain_destroy(swapchain); + return false; + } + } + + wlr_swapchain_destroy(*swapchain_ptr); + *swapchain_ptr = swapchain; + return true; +} diff --git a/types/scene/drag_icon.c b/types/scene/drag_icon.c new file mode 100644 index 0000000..b508487 --- /dev/null +++ b/types/scene/drag_icon.c @@ -0,0 +1,93 @@ +#include +#include "wlr/types/wlr_compositor.h" +#include +#include + +struct wlr_scene_drag_icon { + struct wlr_scene_tree *tree; + struct wlr_scene_tree *surface_tree; + struct wlr_drag_icon *drag_icon; + + struct wl_listener tree_destroy; + struct wl_listener drag_icon_surface_commit; + struct wl_listener drag_icon_surface_map; + struct wl_listener drag_icon_surface_unmap; + struct wl_listener drag_icon_destroy; +}; + +static void drag_icon_handle_surface_commit(struct wl_listener *listener, void *data) { + struct wlr_scene_drag_icon *icon = + wl_container_of(listener, icon, drag_icon_surface_commit); + struct wlr_surface *surface = icon->drag_icon->surface; + struct wlr_scene_node *node = &icon->surface_tree->node; + wlr_scene_node_set_position(node, + node->x + surface->current.dx, node->y + surface->current.dy); +} + +static void drag_icon_handle_surface_map(struct wl_listener *listener, void *data) { + struct wlr_scene_drag_icon *icon = + wl_container_of(listener, icon, drag_icon_surface_map); + wlr_scene_node_set_enabled(&icon->tree->node, true); +} + +static void drag_icon_handle_surface_unmap(struct wl_listener *listener, void *data) { + struct wlr_scene_drag_icon *icon = + wl_container_of(listener, icon, drag_icon_surface_unmap); + wlr_scene_node_set_enabled(&icon->tree->node, false); +} + +static void drag_icon_handle_tree_destroy(struct wl_listener *listener, void *data) { + struct wlr_scene_drag_icon *icon = + wl_container_of(listener, icon, tree_destroy); + wl_list_remove(&icon->tree_destroy.link); + wl_list_remove(&icon->drag_icon_surface_commit.link); + wl_list_remove(&icon->drag_icon_surface_map.link); + wl_list_remove(&icon->drag_icon_surface_unmap.link); + wl_list_remove(&icon->drag_icon_destroy.link); + free(icon); +} + +static void drag_icon_handle_destroy(struct wl_listener *listener, void *data) { + struct wlr_scene_drag_icon *icon = + wl_container_of(listener, icon, drag_icon_destroy); + wlr_scene_node_destroy(&icon->tree->node); +} + +struct wlr_scene_tree *wlr_scene_drag_icon_create( + struct wlr_scene_tree *parent, struct wlr_drag_icon *drag_icon) { + struct wlr_scene_drag_icon *icon = calloc(1, sizeof(*icon)); + if (!icon) { + return NULL; + } + + icon->drag_icon = drag_icon; + + icon->tree = wlr_scene_tree_create(parent); + if (!icon->tree) { + free(icon); + return NULL; + } + + icon->surface_tree = wlr_scene_subsurface_tree_create( + icon->tree, drag_icon->surface); + if (!icon->surface_tree) { + wlr_scene_node_destroy(&icon->tree->node); + free(icon); + return NULL; + } + + wlr_scene_node_set_enabled(&icon->tree->node, drag_icon->surface->mapped); + + icon->tree_destroy.notify = drag_icon_handle_tree_destroy; + wl_signal_add(&icon->tree->node.events.destroy, &icon->tree_destroy); + icon->drag_icon_surface_commit.notify = drag_icon_handle_surface_commit; + wl_signal_add(&drag_icon->surface->events.commit, &icon->drag_icon_surface_commit); + icon->drag_icon_surface_map.notify = drag_icon_handle_surface_map; + wl_signal_add(&drag_icon->surface->events.map, &icon->drag_icon_surface_map); + icon->drag_icon_surface_unmap.notify = drag_icon_handle_surface_unmap; + wl_signal_add(&drag_icon->surface->events.unmap, &icon->drag_icon_surface_unmap); + icon->drag_icon_destroy.notify = drag_icon_handle_destroy; + wl_signal_add(&drag_icon->events.destroy, &icon->drag_icon_destroy); + + return icon->tree; +} diff --git a/types/scene/layer_shell_v1.c b/types/scene/layer_shell_v1.c new file mode 100644 index 0000000..67fdda2 --- /dev/null +++ b/types/scene/layer_shell_v1.c @@ -0,0 +1,186 @@ +#include +#include +#include + +static void scene_layer_surface_handle_tree_destroy( + struct wl_listener *listener, void *data) { + struct wlr_scene_layer_surface_v1 *scene_layer_surface = + wl_container_of(listener, scene_layer_surface, tree_destroy); + // tree and surface_node will be cleaned up by scene_node_finish + wl_list_remove(&scene_layer_surface->tree_destroy.link); + wl_list_remove(&scene_layer_surface->layer_surface_destroy.link); + wl_list_remove(&scene_layer_surface->layer_surface_map.link); + wl_list_remove(&scene_layer_surface->layer_surface_unmap.link); + free(scene_layer_surface); +} + +static void scene_layer_surface_handle_layer_surface_destroy( + struct wl_listener *listener, void *data) { + struct wlr_scene_layer_surface_v1 *scene_layer_surface = + wl_container_of(listener, scene_layer_surface, layer_surface_destroy); + wlr_scene_node_destroy(&scene_layer_surface->tree->node); +} + +static void scene_layer_surface_handle_layer_surface_map( + struct wl_listener *listener, void *data) { + struct wlr_scene_layer_surface_v1 *scene_layer_surface = + wl_container_of(listener, scene_layer_surface, layer_surface_map); + wlr_scene_node_set_enabled(&scene_layer_surface->tree->node, true); +} + +static void scene_layer_surface_handle_layer_surface_unmap( + struct wl_listener *listener, void *data) { + struct wlr_scene_layer_surface_v1 *scene_layer_surface = + wl_container_of(listener, scene_layer_surface, layer_surface_unmap); + wlr_scene_node_set_enabled(&scene_layer_surface->tree->node, false); +} + +static void layer_surface_exclusive_zone( + struct wlr_layer_surface_v1_state *state, + struct wlr_box *usable_area) { + switch (state->anchor) { + case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP: + case (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT): + // Anchor top + usable_area->y += state->exclusive_zone + state->margin.top; + usable_area->height -= state->exclusive_zone + state->margin.top; + break; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM: + case (ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT): + // Anchor bottom + usable_area->height -= state->exclusive_zone + state->margin.bottom; + break; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT: + case (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT): + // Anchor left + usable_area->x += state->exclusive_zone + state->margin.left; + usable_area->width -= state->exclusive_zone + state->margin.left; + break; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT: + case (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT): + // Anchor right + usable_area->width -= state->exclusive_zone + state->margin.right; + break; + } +} + +void wlr_scene_layer_surface_v1_configure( + struct wlr_scene_layer_surface_v1 *scene_layer_surface, + const struct wlr_box *full_area, struct wlr_box *usable_area) { + struct wlr_layer_surface_v1 *layer_surface = + scene_layer_surface->layer_surface; + struct wlr_layer_surface_v1_state *state = &layer_surface->current; + + // If the exclusive zone is set to -1, the layer surface will use the + // full area of the output, otherwise it is constrained to the + // remaining usable area. + struct wlr_box bounds; + if (state->exclusive_zone == -1) { + bounds = *full_area; + } else { + bounds = *usable_area; + } + + struct wlr_box box = { + .width = state->desired_width, + .height = state->desired_height, + }; + + // Horizontal positioning + if (box.width == 0) { + box.x = bounds.x + state->margin.left; + box.width = bounds.width - + (state->margin.left + state->margin.right); + } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT && + state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) { + box.x = bounds.x + bounds.width/2 -box.width/2; + } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) { + box.x = bounds.x + state->margin.left; + } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) { + box.x = bounds.x + bounds.width - box.width - state->margin.right; + } else { + box.x = bounds.x + bounds.width/2 - box.width/2; + } + + // Vertical positioning + if (box.height == 0) { + box.y = bounds.y + state->margin.top; + box.height = bounds.height - + (state->margin.top + state->margin.bottom); + } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP && + state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) { + box.y = bounds.y + bounds.height/2 - box.height/2; + } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) { + box.y = bounds.y + state->margin.top; + } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) { + box.y = bounds.y + bounds.height - box.height - state->margin.bottom; + } else { + box.y = bounds.y + bounds.height/2 - box.height/2; + } + + wlr_scene_node_set_position(&scene_layer_surface->tree->node, box.x, box.y); + wlr_layer_surface_v1_configure(layer_surface, box.width, box.height); + + if (layer_surface->surface->mapped && state->exclusive_zone > 0) { + layer_surface_exclusive_zone(state, usable_area); + } +} + +struct wlr_scene_layer_surface_v1 *wlr_scene_layer_surface_v1_create( + struct wlr_scene_tree *parent, + struct wlr_layer_surface_v1 *layer_surface) { + struct wlr_scene_layer_surface_v1 *scene_layer_surface = + calloc(1, sizeof(*scene_layer_surface)); + if (scene_layer_surface == NULL) { + return NULL; + } + + scene_layer_surface->layer_surface = layer_surface; + + scene_layer_surface->tree = wlr_scene_tree_create(parent); + if (scene_layer_surface->tree == NULL) { + free(scene_layer_surface); + return NULL; + } + + struct wlr_scene_tree *surface_tree = wlr_scene_subsurface_tree_create( + scene_layer_surface->tree, layer_surface->surface); + if (surface_tree == NULL) { + wlr_scene_node_destroy(&scene_layer_surface->tree->node); + free(scene_layer_surface); + return NULL; + } + + scene_layer_surface->tree_destroy.notify = + scene_layer_surface_handle_tree_destroy; + wl_signal_add(&scene_layer_surface->tree->node.events.destroy, + &scene_layer_surface->tree_destroy); + + scene_layer_surface->layer_surface_destroy.notify = + scene_layer_surface_handle_layer_surface_destroy; + wl_signal_add(&layer_surface->events.destroy, + &scene_layer_surface->layer_surface_destroy); + + scene_layer_surface->layer_surface_map.notify = + scene_layer_surface_handle_layer_surface_map; + wl_signal_add(&layer_surface->surface->events.map, + &scene_layer_surface->layer_surface_map); + + scene_layer_surface->layer_surface_unmap.notify = + scene_layer_surface_handle_layer_surface_unmap; + wl_signal_add(&layer_surface->surface->events.unmap, + &scene_layer_surface->layer_surface_unmap); + + wlr_scene_node_set_enabled(&scene_layer_surface->tree->node, + layer_surface->surface->mapped); + + return scene_layer_surface; +} diff --git a/types/scene/output_layout.c b/types/scene/output_layout.c new file mode 100644 index 0000000..8818590 --- /dev/null +++ b/types/scene/output_layout.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include + +struct wlr_scene_output_layout { + struct wlr_output_layout *layout; + struct wlr_scene *scene; + + struct wl_list outputs; // wlr_scene_output_layout_output.link + + struct wl_listener layout_change; + struct wl_listener layout_destroy; + struct wl_listener scene_destroy; +}; + +struct wlr_scene_output_layout_output { + struct wlr_output_layout_output *layout_output; + struct wlr_scene_output *scene_output; + + struct wl_list link; // wlr_scene_output_layout.outputs + + struct wl_listener layout_output_destroy; + struct wl_listener scene_output_destroy; +}; + +static void scene_output_layout_output_destroy( + struct wlr_scene_output_layout_output *solo) { + wl_list_remove(&solo->layout_output_destroy.link); + wl_list_remove(&solo->scene_output_destroy.link); + wl_list_remove(&solo->link); + free(solo); +} + +static void scene_output_layout_output_handle_layout_output_destroy( + struct wl_listener *listener, void *data) { + struct wlr_scene_output_layout_output *solo = + wl_container_of(listener, solo, layout_output_destroy); + scene_output_layout_output_destroy(solo); +} + +static void scene_output_layout_output_handle_scene_output_destroy( + struct wl_listener *listener, void *data) { + struct wlr_scene_output_layout_output *solo = + wl_container_of(listener, solo, scene_output_destroy); + scene_output_layout_output_destroy(solo); +} + +static void scene_output_layout_destroy(struct wlr_scene_output_layout *sol) { + struct wlr_scene_output_layout_output *solo, *tmp; + wl_list_for_each_safe(solo, tmp, &sol->outputs, link) { + scene_output_layout_output_destroy(solo); + } + wl_list_remove(&sol->layout_change.link); + wl_list_remove(&sol->layout_destroy.link); + wl_list_remove(&sol->scene_destroy.link); + free(sol); +} + +static void scene_output_layout_handle_layout_change( + struct wl_listener *listener, void *data) { + struct wlr_scene_output_layout *sol = + wl_container_of(listener, sol, layout_change); + + struct wlr_scene_output_layout_output *solo; + wl_list_for_each(solo, &sol->outputs, link) { + wlr_scene_output_set_position(solo->scene_output, + solo->layout_output->x, solo->layout_output->y); + } +} + +void wlr_scene_output_layout_add_output(struct wlr_scene_output_layout *sol, + struct wlr_output_layout_output *lo, struct wlr_scene_output *so) { + assert(lo->output == so->output); + + struct wlr_scene_output_layout_output *solo; + wl_list_for_each(solo, &sol->outputs, link) { + if (solo->scene_output == so) { + return; + } + } + + solo = calloc(1, sizeof(*solo)); + if (solo == NULL) { + return; + } + + solo->scene_output = so; + solo->layout_output = lo; + + solo->layout_output_destroy.notify = + scene_output_layout_output_handle_layout_output_destroy; + wl_signal_add(&lo->events.destroy, &solo->layout_output_destroy); + + solo->scene_output_destroy.notify = + scene_output_layout_output_handle_scene_output_destroy; + wl_signal_add(&solo->scene_output->events.destroy, + &solo->scene_output_destroy); + + wl_list_insert(&sol->outputs, &solo->link); + + wlr_scene_output_set_position(solo->scene_output, lo->x, lo->y); +} + +static void scene_output_layout_handle_layout_destroy( + struct wl_listener *listener, void *data) { + struct wlr_scene_output_layout *sol = + wl_container_of(listener, sol, layout_destroy); + scene_output_layout_destroy(sol); +} + +static void scene_output_layout_handle_scene_destroy( + struct wl_listener *listener, void *data) { + struct wlr_scene_output_layout *sol = + wl_container_of(listener, sol, scene_destroy); + scene_output_layout_destroy(sol); +} + +struct wlr_scene_output_layout *wlr_scene_attach_output_layout(struct wlr_scene *scene, + struct wlr_output_layout *output_layout) { + struct wlr_scene_output_layout *sol = calloc(1, sizeof(*sol)); + if (sol == NULL) { + return NULL; + } + + sol->scene = scene; + sol->layout = output_layout; + + wl_list_init(&sol->outputs); + + sol->layout_destroy.notify = scene_output_layout_handle_layout_destroy; + wl_signal_add(&output_layout->events.destroy, &sol->layout_destroy); + + sol->layout_change.notify = scene_output_layout_handle_layout_change; + wl_signal_add(&output_layout->events.change, &sol->layout_change); + + sol->scene_destroy.notify = scene_output_layout_handle_scene_destroy; + wl_signal_add(&scene->tree.node.events.destroy, &sol->scene_destroy); + + return sol; +} diff --git a/types/scene/subsurface_tree.c b/types/scene/subsurface_tree.c new file mode 100644 index 0000000..a8d301f --- /dev/null +++ b/types/scene/subsurface_tree.c @@ -0,0 +1,370 @@ +#include +#include +#include +#include +#include +#include +#include "types/wlr_scene.h" + +/** + * A tree for a surface and all of its child sub-surfaces. + * + * `tree` contains `scene_surface` and one node per sub-surface. + */ +struct wlr_scene_subsurface_tree { + struct wlr_scene_tree *tree; + struct wlr_surface *surface; + struct wlr_scene_surface *scene_surface; + + struct wl_listener surface_destroy; + struct wl_listener surface_commit; + struct wl_listener surface_map; + struct wl_listener surface_unmap; + struct wl_listener surface_new_subsurface; + + struct wlr_scene_subsurface_tree *parent; // NULL for the top-level surface + + struct wlr_addon scene_addon; + + struct wlr_box clip; + + // Only valid if the surface is a sub-surface + + struct wlr_addon surface_addon; + + struct wl_listener subsurface_destroy; +}; + +static void subsurface_tree_addon_destroy(struct wlr_addon *addon) { + struct wlr_scene_subsurface_tree *subsurface_tree = + wl_container_of(addon, subsurface_tree, scene_addon); + // tree and scene_surface will be cleaned up by scene_node_finish + if (subsurface_tree->parent) { + wlr_addon_finish(&subsurface_tree->surface_addon); + wl_list_remove(&subsurface_tree->subsurface_destroy.link); + } + wlr_addon_finish(&subsurface_tree->scene_addon); + wl_list_remove(&subsurface_tree->surface_destroy.link); + wl_list_remove(&subsurface_tree->surface_commit.link); + wl_list_remove(&subsurface_tree->surface_map.link); + wl_list_remove(&subsurface_tree->surface_unmap.link); + wl_list_remove(&subsurface_tree->surface_new_subsurface.link); + free(subsurface_tree); +} + +static const struct wlr_addon_interface subsurface_tree_surface_addon_impl; + +static struct wlr_scene_subsurface_tree *subsurface_tree_from_subsurface( + struct wlr_scene_subsurface_tree *parent, + struct wlr_subsurface *subsurface) { + struct wlr_addon *addon = wlr_addon_find(&subsurface->surface->addons, + parent, &subsurface_tree_surface_addon_impl); + assert(addon != NULL); + struct wlr_scene_subsurface_tree *subsurface_tree = + wl_container_of(addon, subsurface_tree, surface_addon); + return subsurface_tree; +} + +static bool subsurface_tree_reconfigure_clip( + struct wlr_scene_subsurface_tree *subsurface_tree) { + if (subsurface_tree->parent) { + subsurface_tree->clip = (struct wlr_box){ + .x = subsurface_tree->parent->clip.x - subsurface_tree->tree->node.x, + .y = subsurface_tree->parent->clip.y - subsurface_tree->tree->node.y, + .width = subsurface_tree->parent->clip.width, + .height = subsurface_tree->parent->clip.height, + }; + } + + if (wlr_box_empty(&subsurface_tree->clip)) { + scene_surface_set_clip(subsurface_tree->scene_surface, NULL); + wlr_scene_node_set_enabled(&subsurface_tree->scene_surface->buffer->node, true); + wlr_scene_node_set_position(&subsurface_tree->scene_surface->buffer->node, 0, 0); + + return false; + } else { + struct wlr_box clip = subsurface_tree->clip; + struct wlr_box surface_box = { + .width = subsurface_tree->surface->current.width, + .height = subsurface_tree->surface->current.height, + }; + + bool intersects = wlr_box_intersection(&clip, &clip, &surface_box); + wlr_scene_node_set_enabled(&subsurface_tree->scene_surface->buffer->node, intersects); + + if (intersects) { + wlr_scene_node_set_position(&subsurface_tree->scene_surface->buffer->node, clip.x, clip.y); + scene_surface_set_clip(subsurface_tree->scene_surface, &clip); + } + + return true; + } +} + +static void subsurface_tree_reconfigure( + struct wlr_scene_subsurface_tree *subsurface_tree) { + bool has_clip = subsurface_tree_reconfigure_clip(subsurface_tree); + + struct wlr_surface *surface = subsurface_tree->surface; + + struct wlr_scene_node *prev = NULL; + struct wlr_subsurface *subsurface; + wl_list_for_each(subsurface, &surface->current.subsurfaces_below, + current.link) { + struct wlr_scene_subsurface_tree *child = + subsurface_tree_from_subsurface(subsurface_tree, subsurface); + if (prev != NULL) { + wlr_scene_node_place_above(&child->tree->node, prev); + } + prev = &child->tree->node; + + wlr_scene_node_set_position(&child->tree->node, + subsurface->current.x, subsurface->current.y); + + if (has_clip) { + subsurface_tree_reconfigure_clip(child); + } + } + + if (prev != NULL) { + wlr_scene_node_place_above(&subsurface_tree->scene_surface->buffer->node, prev); + } + prev = &subsurface_tree->scene_surface->buffer->node; + + wl_list_for_each(subsurface, &surface->current.subsurfaces_above, + current.link) { + struct wlr_scene_subsurface_tree *child = + subsurface_tree_from_subsurface(subsurface_tree, subsurface); + wlr_scene_node_place_above(&child->tree->node, prev); + prev = &child->tree->node; + + wlr_scene_node_set_position(&child->tree->node, + subsurface->current.x, subsurface->current.y); + + if (has_clip) { + subsurface_tree_reconfigure_clip(child); + } + } +} + +static void subsurface_tree_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene_subsurface_tree *subsurface_tree = + wl_container_of(listener, subsurface_tree, surface_destroy); + wlr_scene_node_destroy(&subsurface_tree->tree->node); +} + +static void subsurface_tree_handle_surface_commit(struct wl_listener *listener, + void *data) { + struct wlr_scene_subsurface_tree *subsurface_tree = + wl_container_of(listener, subsurface_tree, surface_commit); + + // TODO: only do this on subsurface order or position change + subsurface_tree_reconfigure(subsurface_tree); +} + +static void subsurface_tree_handle_subsurface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene_subsurface_tree *subsurface_tree = + wl_container_of(listener, subsurface_tree, subsurface_destroy); + wlr_scene_node_destroy(&subsurface_tree->tree->node); +} + +static void subsurface_tree_handle_surface_map(struct wl_listener *listener, + void *data) { + struct wlr_scene_subsurface_tree *subsurface_tree = + wl_container_of(listener, subsurface_tree, surface_map); + + wlr_scene_node_set_enabled(&subsurface_tree->tree->node, true); +} + +static void subsurface_tree_handle_surface_unmap(struct wl_listener *listener, + void *data) { + struct wlr_scene_subsurface_tree *subsurface_tree = + wl_container_of(listener, subsurface_tree, surface_unmap); + + wlr_scene_node_set_enabled(&subsurface_tree->tree->node, false); +} + +static void subsurface_tree_surface_addon_destroy(struct wlr_addon *addon) { + struct wlr_scene_subsurface_tree *subsurface_tree = + wl_container_of(addon, subsurface_tree, surface_addon); + wlr_scene_node_destroy(&subsurface_tree->tree->node); +} + +static const struct wlr_addon_interface subsurface_tree_surface_addon_impl = { + .name = "wlr_scene_subsurface_tree", + .destroy = subsurface_tree_surface_addon_destroy, +}; + +static struct wlr_scene_subsurface_tree *scene_surface_tree_create( + struct wlr_scene_tree *parent, struct wlr_surface *surface); + +static bool subsurface_tree_create_subsurface( + struct wlr_scene_subsurface_tree *parent, + struct wlr_subsurface *subsurface) { + struct wlr_scene_subsurface_tree *child = scene_surface_tree_create( + parent->tree, subsurface->surface); + if (child == NULL) { + return false; + } + + child->parent = parent; + + wlr_addon_init(&child->surface_addon, &subsurface->surface->addons, + parent, &subsurface_tree_surface_addon_impl); + + child->subsurface_destroy.notify = subsurface_tree_handle_subsurface_destroy; + wl_signal_add(&subsurface->events.destroy, &child->subsurface_destroy); + + return true; +} + +static void subsurface_tree_handle_surface_new_subsurface( + struct wl_listener *listener, void *data) { + struct wlr_scene_subsurface_tree *subsurface_tree = + wl_container_of(listener, subsurface_tree, surface_new_subsurface); + struct wlr_subsurface *subsurface = data; + if (!subsurface_tree_create_subsurface(subsurface_tree, subsurface)) { + wl_resource_post_no_memory(subsurface->resource); + } +} + +static const struct wlr_addon_interface subsurface_tree_addon_impl = { + .name = "wlr_scene_subsurface_tree", + .destroy = subsurface_tree_addon_destroy, +}; + +static struct wlr_scene_subsurface_tree *scene_surface_tree_create( + struct wlr_scene_tree *parent, struct wlr_surface *surface) { + struct wlr_scene_subsurface_tree *subsurface_tree = + calloc(1, sizeof(*subsurface_tree)); + if (subsurface_tree == NULL) { + return NULL; + } + + subsurface_tree->tree = wlr_scene_tree_create(parent); + if (subsurface_tree->tree == NULL) { + goto error_surface_tree; + } + + subsurface_tree->scene_surface = + wlr_scene_surface_create(subsurface_tree->tree, surface); + if (subsurface_tree->scene_surface == NULL) { + goto error_scene_surface; + } + + subsurface_tree->surface = surface; + + struct wlr_subsurface *subsurface; + wl_list_for_each(subsurface, &surface->current.subsurfaces_below, + current.link) { + if (!subsurface_tree_create_subsurface(subsurface_tree, subsurface)) { + goto error_scene_surface; + } + } + wl_list_for_each(subsurface, &surface->current.subsurfaces_above, + current.link) { + if (!subsurface_tree_create_subsurface(subsurface_tree, subsurface)) { + goto error_scene_surface; + } + } + + subsurface_tree_reconfigure(subsurface_tree); + + wlr_addon_init(&subsurface_tree->scene_addon, &subsurface_tree->tree->node.addons, + NULL, &subsurface_tree_addon_impl); + + subsurface_tree->surface_destroy.notify = subsurface_tree_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &subsurface_tree->surface_destroy); + + subsurface_tree->surface_commit.notify = subsurface_tree_handle_surface_commit; + wl_signal_add(&surface->events.commit, &subsurface_tree->surface_commit); + + subsurface_tree->surface_map.notify = subsurface_tree_handle_surface_map; + wl_signal_add(&surface->events.map, &subsurface_tree->surface_map); + + subsurface_tree->surface_unmap.notify = subsurface_tree_handle_surface_unmap; + wl_signal_add(&surface->events.unmap, &subsurface_tree->surface_unmap); + + subsurface_tree->surface_new_subsurface.notify = + subsurface_tree_handle_surface_new_subsurface; + wl_signal_add(&surface->events.new_subsurface, + &subsurface_tree->surface_new_subsurface); + + wlr_scene_node_set_enabled(&subsurface_tree->tree->node, surface->mapped); + + return subsurface_tree; + +error_scene_surface: + wlr_scene_node_destroy(&subsurface_tree->tree->node); +error_surface_tree: + free(subsurface_tree); + return NULL; +} + +struct wlr_scene_tree *wlr_scene_subsurface_tree_create( + struct wlr_scene_tree *parent, struct wlr_surface *surface) { + struct wlr_scene_subsurface_tree *subsurface_tree = + scene_surface_tree_create(parent, surface); + if (subsurface_tree == NULL) { + return NULL; + } + return subsurface_tree->tree; +} + +static struct wlr_scene_subsurface_tree *get_subsurface_tree_from_node( + struct wlr_scene_node *node) { + struct wlr_addon *addon = wlr_addon_find(&node->addons, NULL, &subsurface_tree_addon_impl); + if (!addon) { + return NULL; + } + + struct wlr_scene_subsurface_tree *tree = + wl_container_of(addon, tree, scene_addon); + return tree; +} + +static bool subsurface_tree_set_clip(struct wlr_scene_node *node, + struct wlr_box *clip) { + if (node->type != WLR_SCENE_NODE_TREE) { + return false; + } + + bool discovered_subsurface_tree = false; + struct wlr_scene_subsurface_tree *tree = get_subsurface_tree_from_node(node); + if (tree) { + if (tree->parent == NULL) { + if (wlr_box_equal(&tree->clip, clip)) { + return true; + } + + if (clip) { + tree->clip = *clip; + } else { + tree->clip = (struct wlr_box){0}; + } + } + + discovered_subsurface_tree = true; + subsurface_tree_reconfigure_clip(tree); + } + + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + discovered_subsurface_tree |= subsurface_tree_set_clip(child, clip); + } + + return discovered_subsurface_tree; +} + +void wlr_scene_subsurface_tree_set_clip(struct wlr_scene_node *node, + struct wlr_box *clip) { +#ifndef NDEBUG + bool found = +#endif + subsurface_tree_set_clip(node, clip); + + assert(found); +} diff --git a/types/scene/surface.c b/types/scene/surface.c new file mode 100644 index 0000000..11396e2 --- /dev/null +++ b/types/scene/surface.c @@ -0,0 +1,291 @@ +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_scene.h" + +static void handle_scene_buffer_outputs_update( + struct wl_listener *listener, void *data) { + struct wlr_scene_surface *surface = + wl_container_of(listener, surface, outputs_update); + + if (surface->buffer->primary_output == NULL) { + return; + } + double scale = surface->buffer->primary_output->output->scale; + wlr_fractional_scale_v1_notify_scale(surface->surface, scale); + wlr_surface_set_preferred_buffer_scale(surface->surface, ceil(scale)); +} + +static void handle_scene_buffer_output_enter( + struct wl_listener *listener, void *data) { + struct wlr_scene_surface *surface = + wl_container_of(listener, surface, output_enter); + struct wlr_scene_output *output = data; + + wlr_surface_send_enter(surface->surface, output->output); +} + +static void handle_scene_buffer_output_leave( + struct wl_listener *listener, void *data) { + struct wlr_scene_surface *surface = + wl_container_of(listener, surface, output_leave); + struct wlr_scene_output *output = data; + + wlr_surface_send_leave(surface->surface, output->output); +} + +static void handle_scene_buffer_output_sample( + struct wl_listener *listener, void *data) { + struct wlr_scene_surface *surface = + wl_container_of(listener, surface, output_sample); + const struct wlr_scene_output_sample_event *event = data; + struct wlr_scene_output *scene_output = event->output; + if (surface->buffer->primary_output != scene_output) { + return; + } + + if (event->direct_scanout) { + wlr_presentation_surface_scanned_out_on_output(surface->surface, scene_output->output); + } else { + wlr_presentation_surface_textured_on_output(surface->surface, scene_output->output); + } +} + +static void handle_scene_buffer_frame_done( + struct wl_listener *listener, void *data) { + struct wlr_scene_surface *surface = + wl_container_of(listener, surface, frame_done); + struct timespec *now = data; + + wlr_surface_send_frame_done(surface->surface, now); +} + +static void scene_surface_handle_surface_destroy( + struct wl_listener *listener, void *data) { + struct wlr_scene_surface *surface = + wl_container_of(listener, surface, surface_destroy); + + wlr_scene_node_destroy(&surface->buffer->node); +} + +// This is used for wlr_scene where it unconditionally locks buffers preventing +// reuse of the existing texture for shm clients. With the usage pattern of +// wlr_scene surface handling, we can mark its locked buffer as safe +// for mutation. +static void client_buffer_mark_next_can_damage(struct wlr_client_buffer *buffer) { + buffer->n_ignore_locks++; +} + +static void scene_buffer_unmark_client_buffer(struct wlr_scene_buffer *scene_buffer) { + if (!scene_buffer->buffer) { + return; + } + + struct wlr_client_buffer *buffer = wlr_client_buffer_get(scene_buffer->buffer); + if (!buffer) { + return; + } + + assert(buffer->n_ignore_locks > 0); + buffer->n_ignore_locks--; +} + +static int min(int a, int b) { + return a < b ? a : b; +} + +static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { + struct wlr_scene_buffer *scene_buffer = scene_surface->buffer; + struct wlr_surface *surface = scene_surface->surface; + struct wlr_surface_state *state = &surface->current; + + struct wlr_fbox src_box; + wlr_surface_get_buffer_source_box(surface, &src_box); + + pixman_region32_t opaque; + pixman_region32_init(&opaque); + pixman_region32_copy(&opaque, &surface->opaque_region); + + int width = state->width; + int height = state->height; + + if (!wlr_box_empty(&scene_surface->clip)) { + struct wlr_box *clip = &scene_surface->clip; + + int buffer_width = state->buffer_width; + int buffer_height = state->buffer_height; + width = min(clip->width, width - clip->x); + height = min(clip->height, height - clip->y); + + wlr_fbox_transform(&src_box, &src_box, state->transform, + buffer_width, buffer_height); + wlr_output_transform_coords(state->transform, &buffer_width, &buffer_height); + + src_box.x += (double)(clip->x * buffer_width) / state->width; + src_box.y += (double)(clip->y * buffer_height) / state->height; + src_box.width *= (double)width / state->width; + src_box.height *= (double)height / state->height; + + wlr_fbox_transform(&src_box, &src_box, wlr_output_transform_invert(state->transform), + buffer_width, buffer_height); + + pixman_region32_translate(&opaque, -clip->x, -clip->y); + pixman_region32_intersect_rect(&opaque, &opaque, 0, 0, width, height); + } + + if (width <= 0 || height <= 0) { + wlr_scene_buffer_set_buffer(scene_buffer, NULL); + pixman_region32_fini(&opaque); + return; + } + + wlr_scene_buffer_set_opaque_region(scene_buffer, &opaque); + wlr_scene_buffer_set_source_box(scene_buffer, &src_box); + wlr_scene_buffer_set_dest_size(scene_buffer, width, height); + wlr_scene_buffer_set_transform(scene_buffer, state->transform); + + scene_buffer_unmark_client_buffer(scene_buffer); + + if (surface->buffer) { + client_buffer_mark_next_can_damage(surface->buffer); + + wlr_scene_buffer_set_buffer_with_damage(scene_buffer, + &surface->buffer->base, &surface->buffer_damage); + } else { + wlr_scene_buffer_set_buffer(scene_buffer, NULL); + } + + pixman_region32_fini(&opaque); +} + +static void handle_scene_surface_surface_commit( + struct wl_listener *listener, void *data) { + struct wlr_scene_surface *surface = + wl_container_of(listener, surface, surface_commit); + struct wlr_scene_buffer *scene_buffer = surface->buffer; + + surface_reconfigure(surface); + + // If the surface has requested a frame done event, honour that. The + // frame_callback_list will be populated in this case. We should only + // schedule the frame however if the node is enabled and there is an + // output intersecting, otherwise the frame done events would never reach + // the surface anyway. + int lx, ly; + bool enabled = wlr_scene_node_coords(&scene_buffer->node, &lx, &ly); + + if (!wl_list_empty(&surface->surface->current.frame_callback_list) && + surface->buffer->primary_output != NULL && enabled) { + wlr_output_schedule_frame(surface->buffer->primary_output->output); + } +} + +static bool scene_buffer_point_accepts_input(struct wlr_scene_buffer *scene_buffer, + double *sx, double *sy) { + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer(scene_buffer); + + *sx += scene_surface->clip.x; + *sy += scene_surface->clip.y; + + return wlr_surface_point_accepts_input(scene_surface->surface, *sx, *sy); +} + +static void surface_addon_destroy(struct wlr_addon *addon) { + struct wlr_scene_surface *surface = wl_container_of(addon, surface, addon); + + scene_buffer_unmark_client_buffer(surface->buffer); + + wlr_addon_finish(&surface->addon); + + wl_list_remove(&surface->outputs_update.link); + wl_list_remove(&surface->output_enter.link); + wl_list_remove(&surface->output_leave.link); + wl_list_remove(&surface->output_sample.link); + wl_list_remove(&surface->frame_done.link); + wl_list_remove(&surface->surface_destroy.link); + wl_list_remove(&surface->surface_commit.link); + + free(surface); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "wlr_scene_surface", + .destroy = surface_addon_destroy, +}; + +struct wlr_scene_surface *wlr_scene_surface_try_from_buffer( + struct wlr_scene_buffer *scene_buffer) { + struct wlr_addon *addon = wlr_addon_find(&scene_buffer->node.addons, + scene_buffer, &surface_addon_impl); + if (!addon) { + return NULL; + } + + struct wlr_scene_surface *surface = wl_container_of(addon, surface, addon); + return surface; +} + +struct wlr_scene_surface *wlr_scene_surface_create(struct wlr_scene_tree *parent, + struct wlr_surface *wlr_surface) { + struct wlr_scene_surface *surface = calloc(1, sizeof(*surface)); + if (surface == NULL) { + return NULL; + } + + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_create(parent, NULL); + if (!scene_buffer) { + free(surface); + return NULL; + } + + surface->buffer = scene_buffer; + surface->surface = wlr_surface; + scene_buffer->point_accepts_input = scene_buffer_point_accepts_input; + + surface->outputs_update.notify = handle_scene_buffer_outputs_update; + wl_signal_add(&scene_buffer->events.outputs_update, &surface->outputs_update); + + surface->output_enter.notify = handle_scene_buffer_output_enter; + wl_signal_add(&scene_buffer->events.output_enter, &surface->output_enter); + + surface->output_leave.notify = handle_scene_buffer_output_leave; + wl_signal_add(&scene_buffer->events.output_leave, &surface->output_leave); + + surface->output_sample.notify = handle_scene_buffer_output_sample; + wl_signal_add(&scene_buffer->events.output_sample, &surface->output_sample); + + surface->frame_done.notify = handle_scene_buffer_frame_done; + wl_signal_add(&scene_buffer->events.frame_done, &surface->frame_done); + + surface->surface_destroy.notify = scene_surface_handle_surface_destroy; + wl_signal_add(&wlr_surface->events.destroy, &surface->surface_destroy); + + surface->surface_commit.notify = handle_scene_surface_surface_commit; + wl_signal_add(&wlr_surface->events.commit, &surface->surface_commit); + + wlr_addon_init(&surface->addon, &scene_buffer->node.addons, + scene_buffer, &surface_addon_impl); + + surface_reconfigure(surface); + + return surface; +} + +void scene_surface_set_clip(struct wlr_scene_surface *surface, struct wlr_box *clip) { + if (wlr_box_equal(clip, &surface->clip)) { + return; + } + + if (clip) { + surface->clip = *clip; + } else { + surface->clip = (struct wlr_box){0}; + } + + surface_reconfigure(surface); +} diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c new file mode 100644 index 0000000..7e5b313 --- /dev/null +++ b/types/scene/wlr_scene.c @@ -0,0 +1,2010 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_buffer.h" +#include "types/wlr_output.h" +#include "types/wlr_scene.h" +#include "util/array.h" +#include "util/env.h" +#include "util/time.h" + +#define HIGHLIGHT_DAMAGE_FADEOUT_TIME 250 + +struct wlr_scene_tree *wlr_scene_tree_from_node(struct wlr_scene_node *node) { + assert(node->type == WLR_SCENE_NODE_TREE); + struct wlr_scene_tree *tree = wl_container_of(node, tree, node); + return tree; +} + +struct wlr_scene_rect *wlr_scene_rect_from_node(struct wlr_scene_node *node) { + assert(node->type == WLR_SCENE_NODE_RECT); + struct wlr_scene_rect *rect = wl_container_of(node, rect, node); + return rect; +} + +struct wlr_scene_buffer *wlr_scene_buffer_from_node( + struct wlr_scene_node *node) { + assert(node->type == WLR_SCENE_NODE_BUFFER); + struct wlr_scene_buffer *buffer = wl_container_of(node, buffer, node); + return buffer; +} + +struct wlr_scene *scene_node_get_root(struct wlr_scene_node *node) { + struct wlr_scene_tree *tree; + if (node->type == WLR_SCENE_NODE_TREE) { + tree = wlr_scene_tree_from_node(node); + } else { + tree = node->parent; + } + + while (tree->node.parent != NULL) { + tree = tree->node.parent; + } + struct wlr_scene *scene = wl_container_of(tree, scene, tree); + return scene; +} + +static void scene_node_init(struct wlr_scene_node *node, + enum wlr_scene_node_type type, struct wlr_scene_tree *parent) { + *node = (struct wlr_scene_node){ + .type = type, + .parent = parent, + .enabled = true, + }; + + wl_list_init(&node->link); + + wl_signal_init(&node->events.destroy); + pixman_region32_init(&node->visible); + + if (parent != NULL) { + wl_list_insert(parent->children.prev, &node->link); + } + + wlr_addon_set_init(&node->addons); +} + +struct highlight_region { + pixman_region32_t region; + struct timespec when; + struct wl_list link; +}; + +static void scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, + struct wlr_buffer *buffer); + +void wlr_scene_node_destroy(struct wlr_scene_node *node) { + if (node == NULL) { + return; + } + + // We want to call the destroy listeners before we do anything else + // in case the destroy signal would like to remove children before they + // are recursively destroyed. + wl_signal_emit_mutable(&node->events.destroy, NULL); + wlr_addon_set_finish(&node->addons); + + wlr_scene_node_set_enabled(node, false); + + struct wlr_scene *scene = scene_node_get_root(node); + if (node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + + uint64_t active = scene_buffer->active_outputs; + if (active) { + struct wlr_scene_output *scene_output; + wl_list_for_each(scene_output, &scene->outputs, link) { + if (active & (1ull << scene_output->index)) { + wl_signal_emit_mutable(&scene_buffer->events.output_leave, + scene_output); + } + } + } + + wlr_texture_destroy(scene_buffer->texture); + scene_buffer_set_buffer(scene_buffer, NULL); + pixman_region32_fini(&scene_buffer->opaque_region); + } else if (node->type == WLR_SCENE_NODE_TREE) { + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + + if (scene_tree == &scene->tree) { + assert(!node->parent); + struct wlr_scene_output *scene_output, *scene_output_tmp; + wl_list_for_each_safe(scene_output, scene_output_tmp, &scene->outputs, link) { + wlr_scene_output_destroy(scene_output); + } + + wl_list_remove(&scene->linux_dmabuf_v1_destroy.link); + } else { + assert(node->parent); + } + + struct wlr_scene_node *child, *child_tmp; + wl_list_for_each_safe(child, child_tmp, + &scene_tree->children, link) { + wlr_scene_node_destroy(child); + } + } + + wl_list_remove(&node->link); + pixman_region32_fini(&node->visible); + free(node); +} + +static void scene_tree_init(struct wlr_scene_tree *tree, + struct wlr_scene_tree *parent) { + *tree = (struct wlr_scene_tree){0}; + scene_node_init(&tree->node, WLR_SCENE_NODE_TREE, parent); + wl_list_init(&tree->children); +} + +struct wlr_scene *wlr_scene_create(void) { + struct wlr_scene *scene = calloc(1, sizeof(*scene)); + if (scene == NULL) { + return NULL; + } + + scene_tree_init(&scene->tree, NULL); + + wl_list_init(&scene->outputs); + wl_list_init(&scene->linux_dmabuf_v1_destroy.link); + + const char *debug_damage_options[] = { + "none", + "rerender", + "highlight", + NULL + }; + + scene->debug_damage_option = env_parse_switch("WLR_SCENE_DEBUG_DAMAGE", debug_damage_options); + scene->direct_scanout = !env_parse_bool("WLR_SCENE_DISABLE_DIRECT_SCANOUT"); + scene->calculate_visibility = !env_parse_bool("WLR_SCENE_DISABLE_VISIBILITY"); + + return scene; +} + +struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent) { + assert(parent); + + struct wlr_scene_tree *tree = calloc(1, sizeof(*tree)); + if (tree == NULL) { + return NULL; + } + + scene_tree_init(tree, parent); + return tree; +} + +static void scene_node_get_size(struct wlr_scene_node *node, int *lx, int *ly); + +typedef bool (*scene_node_box_iterator_func_t)(struct wlr_scene_node *node, + int sx, int sy, void *data); + +static bool _scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box, + scene_node_box_iterator_func_t iterator, void *user_data, int lx, int ly) { + if (!node->enabled) { + return false; + } + + switch (node->type) { + case WLR_SCENE_NODE_TREE:; + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each_reverse(child, &scene_tree->children, link) { + if (_scene_nodes_in_box(child, box, iterator, user_data, lx + child->x, ly + child->y)) { + return true; + } + } + break; + case WLR_SCENE_NODE_RECT: + case WLR_SCENE_NODE_BUFFER:; + struct wlr_box node_box = { .x = lx, .y = ly }; + scene_node_get_size(node, &node_box.width, &node_box.height); + + if (wlr_box_intersection(&node_box, &node_box, box) && + iterator(node, lx, ly, user_data)) { + return true; + } + break; + } + + return false; +} + +static bool scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box, + scene_node_box_iterator_func_t iterator, void *user_data) { + int x, y; + wlr_scene_node_coords(node, &x, &y); + + return _scene_nodes_in_box(node, box, iterator, user_data, x, y); +} + +static void scene_node_opaque_region(struct wlr_scene_node *node, int x, int y, + pixman_region32_t *opaque) { + int width, height; + scene_node_get_size(node, &width, &height); + + if (node->type == WLR_SCENE_NODE_RECT) { + struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node); + if (scene_rect->color[3] != 1) { + return; + } + } else if (node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + + if (!scene_buffer->buffer) { + return; + } + + if (scene_buffer->opacity != 1) { + return; + } + + if (!scene_buffer->buffer_is_opaque) { + pixman_region32_copy(opaque, &scene_buffer->opaque_region); + pixman_region32_intersect_rect(opaque, opaque, 0, 0, width, height); + pixman_region32_translate(opaque, x, y); + return; + } + } + + pixman_region32_fini(opaque); + pixman_region32_init_rect(opaque, x, y, width, height); +} + +struct scene_update_data { + pixman_region32_t *visible; + pixman_region32_t *update_region; + struct wl_list *outputs; + bool calculate_visibility; +}; + +static uint32_t region_area(pixman_region32_t *region) { + uint32_t area = 0; + + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(region, &nrects); + for (int i = 0; i < nrects; ++i) { + area += (rects[i].x2 - rects[i].x1) * (rects[i].y2 - rects[i].y1); + } + + return area; +} + +static void scale_output_damage(pixman_region32_t *damage, float scale) { + wlr_region_scale(damage, damage, scale); + + if (floor(scale) != scale) { + wlr_region_expand(damage, damage, 1); + } +} + +struct render_data { + enum wl_output_transform transform; + float scale; + struct wlr_box logical; + int trans_width, trans_height; + + struct wlr_scene_output *output; + + struct wlr_render_pass *render_pass; + pixman_region32_t damage; +}; + +static void transform_output_damage(pixman_region32_t *damage, const struct render_data *data) { + enum wl_output_transform transform = wlr_output_transform_invert(data->transform); + wlr_region_transform(damage, damage, transform, data->trans_width, data->trans_height); +} + +static void transform_output_box(struct wlr_box *box, const struct render_data *data) { + enum wl_output_transform transform = wlr_output_transform_invert(data->transform); + wlr_box_transform(box, box, transform, data->trans_width, data->trans_height); +} + +static void scene_damage_outputs(struct wlr_scene *scene, pixman_region32_t *damage) { + if (!pixman_region32_not_empty(damage)) { + return; + } + + struct wlr_scene_output *scene_output; + wl_list_for_each(scene_output, &scene->outputs, link) { + pixman_region32_t output_damage; + pixman_region32_init(&output_damage); + pixman_region32_copy(&output_damage, damage); + pixman_region32_translate(&output_damage, + -scene_output->x, -scene_output->y); + scale_output_damage(&output_damage, scene_output->output->scale); + if (wlr_damage_ring_add(&scene_output->damage_ring, &output_damage)) { + wlr_output_schedule_frame(scene_output->output); + } + pixman_region32_fini(&output_damage); + } +} + +static void update_node_update_outputs(struct wlr_scene_node *node, + struct wl_list *outputs, struct wlr_scene_output *ignore, + struct wlr_scene_output *force) { + if (node->type != WLR_SCENE_NODE_BUFFER) { + return; + } + + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + + uint32_t largest_overlap = 0; + struct wlr_scene_output *old_primary_output = scene_buffer->primary_output; + scene_buffer->primary_output = NULL; + + size_t count = 0; + uint64_t active_outputs = 0; + + // let's update the outputs in two steps: + // - the primary outputs + // - the enter/leave signals + // This ensures that the enter/leave signals can rely on the primary output + // to have a reasonable value. Otherwise, they may get a value that's in + // the middle of a calculation. + struct wlr_scene_output *scene_output; + wl_list_for_each(scene_output, outputs, link) { + if (scene_output == ignore) { + continue; + } + + if (!scene_output->output->enabled) { + continue; + } + + struct wlr_box output_box = { + .x = scene_output->x, + .y = scene_output->y, + }; + wlr_output_effective_resolution(scene_output->output, + &output_box.width, &output_box.height); + + pixman_region32_t intersection; + pixman_region32_init(&intersection); + pixman_region32_intersect_rect(&intersection, &node->visible, + output_box.x, output_box.y, output_box.width, output_box.height); + + if (pixman_region32_not_empty(&intersection)) { + uint32_t overlap = region_area(&intersection); + if (overlap >= largest_overlap) { + largest_overlap = overlap; + scene_buffer->primary_output = scene_output; + } + + active_outputs |= 1ull << scene_output->index; + count++; + } + + pixman_region32_fini(&intersection); + } + + if (old_primary_output != scene_buffer->primary_output) { + scene_buffer->prev_feedback_options = + (struct wlr_linux_dmabuf_feedback_v1_init_options){0}; + } + + uint64_t old_active = scene_buffer->active_outputs; + scene_buffer->active_outputs = active_outputs; + + wl_list_for_each(scene_output, outputs, link) { + uint64_t mask = 1ull << scene_output->index; + bool intersects = active_outputs & mask; + bool intersects_before = old_active & mask; + + if (intersects && !intersects_before) { + wl_signal_emit_mutable(&scene_buffer->events.output_enter, scene_output); + } else if (!intersects && intersects_before) { + wl_signal_emit_mutable(&scene_buffer->events.output_leave, scene_output); + } + } + + // if there are active outputs on this node, we should always have a primary + // output + assert(!scene_buffer->active_outputs || scene_buffer->primary_output); + + // Skip output update event if nothing was updated + if (old_active == active_outputs && + (!force || ((1ull << force->index) & ~active_outputs)) && + old_primary_output == scene_buffer->primary_output) { + return; + } + + struct wlr_scene_output *outputs_array[64]; + struct wlr_scene_outputs_update_event event = { + .active = outputs_array, + .size = count, + }; + + size_t i = 0; + wl_list_for_each(scene_output, outputs, link) { + if (~active_outputs & (1ull << scene_output->index)) { + continue; + } + + assert(i < count); + outputs_array[i++] = scene_output; + } + + wl_signal_emit_mutable(&scene_buffer->events.outputs_update, &event); +} + +static bool scene_node_update_iterator(struct wlr_scene_node *node, + int lx, int ly, void *_data) { + struct scene_update_data *data = _data; + + struct wlr_box box = { .x = lx, .y = ly }; + scene_node_get_size(node, &box.width, &box.height); + + pixman_region32_subtract(&node->visible, &node->visible, data->update_region); + pixman_region32_union(&node->visible, &node->visible, data->visible); + pixman_region32_intersect_rect(&node->visible, &node->visible, + lx, ly, box.width, box.height); + + if (data->calculate_visibility) { + pixman_region32_t opaque; + pixman_region32_init(&opaque); + scene_node_opaque_region(node, lx, ly, &opaque); + pixman_region32_subtract(data->visible, data->visible, &opaque); + pixman_region32_fini(&opaque); + } + + update_node_update_outputs(node, data->outputs, NULL, NULL); + + return false; +} + +static void scene_node_visibility(struct wlr_scene_node *node, + pixman_region32_t *visible) { + if (!node->enabled) { + return; + } + + if (node->type == WLR_SCENE_NODE_TREE) { + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + scene_node_visibility(child, visible); + } + return; + } + + pixman_region32_union(visible, visible, &node->visible); +} + +static void scene_node_bounds(struct wlr_scene_node *node, + int x, int y, pixman_region32_t *visible) { + if (!node->enabled) { + return; + } + + if (node->type == WLR_SCENE_NODE_TREE) { + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + scene_node_bounds(child, x + child->x, y + child->y, visible); + } + return; + } + + int width, height; + scene_node_get_size(node, &width, &height); + pixman_region32_union_rect(visible, visible, x, y, width, height); +} + +static void scene_update_region(struct wlr_scene *scene, + pixman_region32_t *update_region) { + pixman_region32_t visible; + pixman_region32_init(&visible); + pixman_region32_copy(&visible, update_region); + + struct scene_update_data data = { + .visible = &visible, + .update_region = update_region, + .outputs = &scene->outputs, + .calculate_visibility = scene->calculate_visibility, + }; + + struct pixman_box32 *region_box = pixman_region32_extents(update_region); + struct wlr_box box = { + .x = region_box->x1, + .y = region_box->y1, + .width = region_box->x2 - region_box->x1, + .height = region_box->y2 - region_box->y1, + }; + + // update node visibility and output enter/leave events + scene_nodes_in_box(&scene->tree.node, &box, scene_node_update_iterator, &data); + + pixman_region32_fini(&visible); +} + +static void scene_node_update(struct wlr_scene_node *node, + pixman_region32_t *damage) { + struct wlr_scene *scene = scene_node_get_root(node); + + int x, y; + if (!wlr_scene_node_coords(node, &x, &y)) { + if (damage) { + scene_update_region(scene, damage); + scene_damage_outputs(scene, damage); + pixman_region32_fini(damage); + } + + return; + } + + pixman_region32_t visible; + if (!damage) { + pixman_region32_init(&visible); + scene_node_visibility(node, &visible); + damage = &visible; + } + + pixman_region32_t update_region; + pixman_region32_init(&update_region); + pixman_region32_copy(&update_region, damage); + scene_node_bounds(node, x, y, &update_region); + + scene_update_region(scene, &update_region); + pixman_region32_fini(&update_region); + + scene_node_visibility(node, damage); + scene_damage_outputs(scene, damage); + pixman_region32_fini(damage); +} + +struct wlr_scene_rect *wlr_scene_rect_create(struct wlr_scene_tree *parent, + int width, int height, const float color[static 4]) { + struct wlr_scene_rect *scene_rect = calloc(1, sizeof(*scene_rect)); + if (scene_rect == NULL) { + return NULL; + } + assert(parent); + scene_node_init(&scene_rect->node, WLR_SCENE_NODE_RECT, parent); + + scene_rect->width = width; + scene_rect->height = height; + memcpy(scene_rect->color, color, sizeof(scene_rect->color)); + + scene_node_update(&scene_rect->node, NULL); + + return scene_rect; +} + +void wlr_scene_rect_set_size(struct wlr_scene_rect *rect, int width, int height) { + if (rect->width == width && rect->height == height) { + return; + } + + rect->width = width; + rect->height = height; + scene_node_update(&rect->node, NULL); +} + +void wlr_scene_rect_set_color(struct wlr_scene_rect *rect, const float color[static 4]) { + if (memcmp(rect->color, color, sizeof(rect->color)) == 0) { + return; + } + + memcpy(rect->color, color, sizeof(rect->color)); + scene_node_update(&rect->node, NULL); +} + +static void scene_buffer_handle_buffer_release(struct wl_listener *listener, + void *data) { + struct wlr_scene_buffer *scene_buffer = + wl_container_of(listener, scene_buffer, buffer_release); + scene_buffer_set_buffer(scene_buffer, NULL); +} + +static void scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, + struct wlr_buffer *buffer) { + wl_list_remove(&scene_buffer->buffer_release.link); + wl_list_init(&scene_buffer->buffer_release.link); + if (scene_buffer->own_buffer) { + wlr_buffer_unlock(scene_buffer->buffer); + } + scene_buffer->buffer = NULL; + scene_buffer->own_buffer = false; + scene_buffer->buffer_width = scene_buffer->buffer_height = 0; + scene_buffer->buffer_is_opaque = false; + + if (!buffer) { + return; + } + + scene_buffer->own_buffer = true; + scene_buffer->buffer = wlr_buffer_lock(buffer); + scene_buffer->buffer_width = buffer->width; + scene_buffer->buffer_height = buffer->height; + scene_buffer->buffer_is_opaque = buffer_is_opaque(buffer); + + scene_buffer->buffer_release.notify = scene_buffer_handle_buffer_release; + wl_signal_add(&buffer->events.release, &scene_buffer->buffer_release); +} + +struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, + struct wlr_buffer *buffer) { + struct wlr_scene_buffer *scene_buffer = calloc(1, sizeof(*scene_buffer)); + if (scene_buffer == NULL) { + return NULL; + } + assert(parent); + scene_node_init(&scene_buffer->node, WLR_SCENE_NODE_BUFFER, parent); + + wl_signal_init(&scene_buffer->events.outputs_update); + wl_signal_init(&scene_buffer->events.output_enter); + wl_signal_init(&scene_buffer->events.output_leave); + wl_signal_init(&scene_buffer->events.output_sample); + wl_signal_init(&scene_buffer->events.frame_done); + pixman_region32_init(&scene_buffer->opaque_region); + wl_list_init(&scene_buffer->buffer_release.link); + scene_buffer->opacity = 1; + + scene_buffer_set_buffer(scene_buffer, buffer); + scene_node_update(&scene_buffer->node, NULL); + + return scene_buffer; +} + +void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer, + struct wlr_buffer *buffer, const pixman_region32_t *damage) { + // specifying a region for a NULL buffer doesn't make sense. We need to know + // about the buffer to scale the buffer local coordinates down to scene + // coordinates. + assert(buffer || !damage); + + bool update = false; + + wlr_texture_destroy(scene_buffer->texture); + scene_buffer->texture = NULL; + + if (buffer) { + // if this node used to not be mapped or its previous displayed + // buffer region will be different from what the new buffer would + // produce we need to update the node. + update = scene_buffer->dst_width == 0 && scene_buffer->dst_height == 0 && + (scene_buffer->buffer_width != buffer->width || + scene_buffer->buffer_height != buffer->height); + } else { + update = true; + } + + scene_buffer_set_buffer(scene_buffer, buffer); + + if (update) { + scene_node_update(&scene_buffer->node, NULL); + // updating the node will already damage the whole node for us. Return + // early to not damage again + return; + } + + int lx, ly; + if (!wlr_scene_node_coords(&scene_buffer->node, &lx, &ly)) { + return; + } + + pixman_region32_t fallback_damage; + pixman_region32_init_rect(&fallback_damage, 0, 0, buffer->width, buffer->height); + if (!damage) { + damage = &fallback_damage; + } + + struct wlr_fbox box = scene_buffer->src_box; + if (wlr_fbox_empty(&box)) { + box.x = 0; + box.y = 0; + box.width = buffer->width; + box.height = buffer->height; + } + + wlr_fbox_transform(&box, &box, scene_buffer->transform, + buffer->width, buffer->height); + + float scale_x, scale_y; + if (scene_buffer->dst_width || scene_buffer->dst_height) { + scale_x = scene_buffer->dst_width / box.width; + scale_y = scene_buffer->dst_height / box.height; + } else { + scale_x = buffer->width / box.width; + scale_y = buffer->height / box.height; + } + + pixman_region32_t trans_damage; + pixman_region32_init(&trans_damage); + wlr_region_transform(&trans_damage, damage, + scene_buffer->transform, buffer->width, buffer->height); + pixman_region32_intersect_rect(&trans_damage, &trans_damage, + box.x, box.y, box.width, box.height); + pixman_region32_translate(&trans_damage, -box.x, -box.y); + + struct wlr_scene *scene = scene_node_get_root(&scene_buffer->node); + struct wlr_scene_output *scene_output; + wl_list_for_each(scene_output, &scene->outputs, link) { + float output_scale = scene_output->output->scale; + float output_scale_x = output_scale * scale_x; + float output_scale_y = output_scale * scale_y; + pixman_region32_t output_damage; + pixman_region32_init(&output_damage); + wlr_region_scale_xy(&output_damage, &trans_damage, + output_scale_x, output_scale_y); + + // One output pixel will match (buffer_scale_x)x(buffer_scale_y) buffer pixels. + // If the buffer is upscaled on the given axis (output_scale_* > 1.0, + // buffer_scale_* < 1.0), its contents will bleed into adjacent + // (ceil(output_scale_* / 2)) output pixels because of linear filtering. + // Additionally, if the buffer is downscaled (output_scale_* < 1.0, + // buffer_scale_* > 1.0), and one output pixel matches a non-integer number of + // buffer pixels, its contents will bleed into neighboring output pixels. + // Handle both cases by computing buffer_scale_{x,y} and checking if they are + // integer numbers; ceilf() is used to ensure that the distance is at least 1. + float buffer_scale_x = 1.0f / output_scale_x; + float buffer_scale_y = 1.0f / output_scale_y; + int dist_x = floor(buffer_scale_x) != buffer_scale_x ? + (int)ceilf(output_scale_x / 2.0f) : 0; + int dist_y = floor(buffer_scale_y) != buffer_scale_y ? + (int)ceilf(output_scale_y / 2.0f) : 0; + // TODO: expand with per-axis distances + wlr_region_expand(&output_damage, &output_damage, + dist_x >= dist_y ? dist_x : dist_y); + + pixman_region32_t cull_region; + pixman_region32_init(&cull_region); + pixman_region32_copy(&cull_region, &scene_buffer->node.visible); + scale_output_damage(&cull_region, output_scale); + pixman_region32_translate(&cull_region, -lx * output_scale, -ly * output_scale); + pixman_region32_intersect(&output_damage, &output_damage, &cull_region); + pixman_region32_fini(&cull_region); + + pixman_region32_translate(&output_damage, + (int)round((lx - scene_output->x) * output_scale), + (int)round((ly - scene_output->y) * output_scale)); + if (wlr_damage_ring_add(&scene_output->damage_ring, &output_damage)) { + wlr_output_schedule_frame(scene_output->output); + } + pixman_region32_fini(&output_damage); + } + + pixman_region32_fini(&trans_damage); + pixman_region32_fini(&fallback_damage); +} + +void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, + struct wlr_buffer *buffer) { + wlr_scene_buffer_set_buffer_with_damage(scene_buffer, buffer, NULL); +} + +void wlr_scene_buffer_set_opaque_region(struct wlr_scene_buffer *scene_buffer, + const pixman_region32_t *region) { + if (pixman_region32_equal(&scene_buffer->opaque_region, region)) { + return; + } + + pixman_region32_copy(&scene_buffer->opaque_region, region); + + int x, y; + if (!wlr_scene_node_coords(&scene_buffer->node, &x, &y)) { + return; + } + + pixman_region32_t update_region; + pixman_region32_init(&update_region); + scene_node_bounds(&scene_buffer->node, x, y, &update_region); + scene_update_region(scene_node_get_root(&scene_buffer->node), &update_region); + pixman_region32_fini(&update_region); +} + +void wlr_scene_buffer_set_source_box(struct wlr_scene_buffer *scene_buffer, + const struct wlr_fbox *box) { + if (wlr_fbox_equal(&scene_buffer->src_box, box)) { + return; + } + + if (box != NULL) { + scene_buffer->src_box = *box; + } else { + scene_buffer->src_box = (struct wlr_fbox){0}; + } + + scene_node_update(&scene_buffer->node, NULL); +} + +void wlr_scene_buffer_set_dest_size(struct wlr_scene_buffer *scene_buffer, + int width, int height) { + if (scene_buffer->dst_width == width && scene_buffer->dst_height == height) { + return; + } + + scene_buffer->dst_width = width; + scene_buffer->dst_height = height; + scene_node_update(&scene_buffer->node, NULL); +} + +void wlr_scene_buffer_set_transform(struct wlr_scene_buffer *scene_buffer, + enum wl_output_transform transform) { + if (scene_buffer->transform == transform) { + return; + } + + scene_buffer->transform = transform; + scene_node_update(&scene_buffer->node, NULL); +} + +void wlr_scene_buffer_send_frame_done(struct wlr_scene_buffer *scene_buffer, + struct timespec *now) { + if (pixman_region32_not_empty(&scene_buffer->node.visible)) { + wl_signal_emit_mutable(&scene_buffer->events.frame_done, now); + } +} + +void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer, + float opacity) { + if (scene_buffer->opacity == opacity) { + return; + } + + scene_buffer->opacity = opacity; + scene_node_update(&scene_buffer->node, NULL); +} + +void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer, + enum wlr_scale_filter_mode filter_mode) { + if (scene_buffer->filter_mode == filter_mode) { + return; + } + + scene_buffer->filter_mode = filter_mode; + scene_node_update(&scene_buffer->node, NULL); +} + +static struct wlr_texture *scene_buffer_get_texture( + struct wlr_scene_buffer *scene_buffer, struct wlr_renderer *renderer) { + if (scene_buffer->buffer == NULL || scene_buffer->texture != NULL) { + return scene_buffer->texture; + } + + struct wlr_client_buffer *client_buffer = + wlr_client_buffer_get(scene_buffer->buffer); + if (client_buffer != NULL) { + return client_buffer->texture; + } + + scene_buffer->texture = + wlr_texture_from_buffer(renderer, scene_buffer->buffer); + if (scene_buffer->texture != NULL && scene_buffer->own_buffer) { + scene_buffer->own_buffer = false; + wlr_buffer_unlock(scene_buffer->buffer); + } + return scene_buffer->texture; +} + +static void scene_node_get_size(struct wlr_scene_node *node, + int *width, int *height) { + *width = 0; + *height = 0; + + switch (node->type) { + case WLR_SCENE_NODE_TREE: + return; + case WLR_SCENE_NODE_RECT:; + struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node); + *width = scene_rect->width; + *height = scene_rect->height; + break; + case WLR_SCENE_NODE_BUFFER:; + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + if (scene_buffer->dst_width > 0 && scene_buffer->dst_height > 0) { + *width = scene_buffer->dst_width; + *height = scene_buffer->dst_height; + } else { + *width = scene_buffer->buffer_width; + *height = scene_buffer->buffer_height; + wlr_output_transform_coords(scene_buffer->transform, width, height); + } + break; + } +} + +static int scale_length(int length, int offset, float scale) { + return round((offset + length) * scale) - round(offset * scale); +} + +static void scale_box(struct wlr_box *box, float scale) { + box->width = scale_length(box->width, box->x, scale); + box->height = scale_length(box->height, box->y, scale); + box->x = round(box->x * scale); + box->y = round(box->y * scale); +} + +void wlr_scene_node_set_enabled(struct wlr_scene_node *node, bool enabled) { + if (node->enabled == enabled) { + return; + } + + int x, y; + pixman_region32_t visible; + pixman_region32_init(&visible); + if (wlr_scene_node_coords(node, &x, &y)) { + scene_node_visibility(node, &visible); + } + + node->enabled = enabled; + + scene_node_update(node, &visible); +} + +void wlr_scene_node_set_position(struct wlr_scene_node *node, int x, int y) { + if (node->x == x && node->y == y) { + return; + } + + node->x = x; + node->y = y; + scene_node_update(node, NULL); +} + +void wlr_scene_node_place_above(struct wlr_scene_node *node, + struct wlr_scene_node *sibling) { + assert(node != sibling); + assert(node->parent == sibling->parent); + + if (node->link.prev == &sibling->link) { + return; + } + + wl_list_remove(&node->link); + wl_list_insert(&sibling->link, &node->link); + scene_node_update(node, NULL); +} + +void wlr_scene_node_place_below(struct wlr_scene_node *node, + struct wlr_scene_node *sibling) { + assert(node != sibling); + assert(node->parent == sibling->parent); + + if (node->link.next == &sibling->link) { + return; + } + + wl_list_remove(&node->link); + wl_list_insert(sibling->link.prev, &node->link); + scene_node_update(node, NULL); +} + +void wlr_scene_node_raise_to_top(struct wlr_scene_node *node) { + struct wlr_scene_node *current_top = wl_container_of( + node->parent->children.prev, current_top, link); + if (node == current_top) { + return; + } + wlr_scene_node_place_above(node, current_top); +} + +void wlr_scene_node_lower_to_bottom(struct wlr_scene_node *node) { + struct wlr_scene_node *current_bottom = wl_container_of( + node->parent->children.next, current_bottom, link); + if (node == current_bottom) { + return; + } + wlr_scene_node_place_below(node, current_bottom); +} + +void wlr_scene_node_reparent(struct wlr_scene_node *node, + struct wlr_scene_tree *new_parent) { + assert(new_parent != NULL); + + if (node->parent == new_parent) { + return; + } + + /* Ensure that a node cannot become its own ancestor */ + for (struct wlr_scene_tree *ancestor = new_parent; ancestor != NULL; + ancestor = ancestor->node.parent) { + assert(&ancestor->node != node); + } + + int x, y; + pixman_region32_t visible; + pixman_region32_init(&visible); + if (wlr_scene_node_coords(node, &x, &y)) { + scene_node_visibility(node, &visible); + } + + wl_list_remove(&node->link); + node->parent = new_parent; + wl_list_insert(new_parent->children.prev, &node->link); + scene_node_update(node, &visible); +} + +bool wlr_scene_node_coords(struct wlr_scene_node *node, + int *lx_ptr, int *ly_ptr) { + assert(node); + + int lx = 0, ly = 0; + bool enabled = true; + while (true) { + lx += node->x; + ly += node->y; + enabled = enabled && node->enabled; + if (node->parent == NULL) { + break; + } + + node = &node->parent->node; + } + + *lx_ptr = lx; + *ly_ptr = ly; + return enabled; +} + +static void scene_node_for_each_scene_buffer(struct wlr_scene_node *node, + int lx, int ly, wlr_scene_buffer_iterator_func_t user_iterator, + void *user_data) { + if (!node->enabled) { + return; + } + + lx += node->x; + ly += node->y; + + if (node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + user_iterator(scene_buffer, lx, ly, user_data); + } else if (node->type == WLR_SCENE_NODE_TREE) { + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + scene_node_for_each_scene_buffer(child, lx, ly, user_iterator, user_data); + } + } +} + +void wlr_scene_node_for_each_buffer(struct wlr_scene_node *node, + wlr_scene_buffer_iterator_func_t user_iterator, void *user_data) { + scene_node_for_each_scene_buffer(node, 0, 0, user_iterator, user_data); +} + +struct node_at_data { + double lx, ly; + double rx, ry; + struct wlr_scene_node *node; +}; + +static bool scene_node_at_iterator(struct wlr_scene_node *node, + int lx, int ly, void *data) { + struct node_at_data *at_data = data; + + double rx = at_data->lx - lx; + double ry = at_data->ly - ly; + + if (node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + + if (scene_buffer->point_accepts_input && + !scene_buffer->point_accepts_input(scene_buffer, &rx, &ry)) { + return false; + } + } + + at_data->rx = rx; + at_data->ry = ry; + at_data->node = node; + return true; +} + +struct wlr_scene_node *wlr_scene_node_at(struct wlr_scene_node *node, + double lx, double ly, double *nx, double *ny) { + struct wlr_box box = { + .x = floor(lx), + .y = floor(ly), + .width = 1, + .height = 1 + }; + + struct node_at_data data = { + .lx = lx, + .ly = ly + }; + + if (scene_nodes_in_box(node, &box, scene_node_at_iterator, &data)) { + if (nx) { + *nx = data.rx; + } + if (ny) { + *ny = data.ry; + } + return data.node; + } + + return NULL; +} + +struct render_list_entry { + struct wlr_scene_node *node; + bool sent_dmabuf_feedback; + int x, y; +}; + +static void scene_entry_render(struct render_list_entry *entry, const struct render_data *data) { + struct wlr_scene_node *node = entry->node; + + pixman_region32_t render_region; + pixman_region32_init(&render_region); + pixman_region32_copy(&render_region, &node->visible); + pixman_region32_translate(&render_region, -data->logical.x, -data->logical.y); + scale_output_damage(&render_region, data->scale); + pixman_region32_intersect(&render_region, &render_region, &data->damage); + if (!pixman_region32_not_empty(&render_region)) { + pixman_region32_fini(&render_region); + return; + } + + struct wlr_box dst_box = { + .x = entry->x - data->logical.x, + .y = entry->y - data->logical.y, + }; + scene_node_get_size(node, &dst_box.width, &dst_box.height); + scale_box(&dst_box, data->scale); + + pixman_region32_t opaque; + pixman_region32_init(&opaque); + scene_node_opaque_region(node, dst_box.x, dst_box.y, &opaque); + scale_output_damage(&opaque, data->scale); + pixman_region32_subtract(&opaque, &render_region, &opaque); + + transform_output_box(&dst_box, data); + transform_output_damage(&render_region, data); + + switch (node->type) { + case WLR_SCENE_NODE_TREE: + assert(false); + break; + case WLR_SCENE_NODE_RECT:; + struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node); + + wlr_render_pass_add_rect(data->render_pass, &(struct wlr_render_rect_options){ + .box = dst_box, + .color = { + .r = scene_rect->color[0], + .g = scene_rect->color[1], + .b = scene_rect->color[2], + .a = scene_rect->color[3], + }, + .clip = &render_region, + }); + break; + case WLR_SCENE_NODE_BUFFER:; + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + + struct wlr_texture *texture = scene_buffer_get_texture(scene_buffer, + data->output->output->renderer); + if (texture == NULL) { + break; + } + + enum wl_output_transform transform = + wlr_output_transform_invert(scene_buffer->transform); + transform = wlr_output_transform_compose(transform, data->transform); + + wlr_render_pass_add_texture(data->render_pass, &(struct wlr_render_texture_options) { + .texture = texture, + .src_box = scene_buffer->src_box, + .dst_box = dst_box, + .transform = transform, + .clip = &render_region, + .alpha = &scene_buffer->opacity, + .filter_mode = scene_buffer->filter_mode, + .blend_mode = pixman_region32_not_empty(&opaque) ? + WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, + }); + + struct wlr_scene_output_sample_event sample_event = { + .output = data->output, + .direct_scanout = false, + }; + wl_signal_emit_mutable(&scene_buffer->events.output_sample, &sample_event); + break; + } + + pixman_region32_fini(&opaque); + pixman_region32_fini(&render_region); +} + +static void scene_handle_linux_dmabuf_v1_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene *scene = + wl_container_of(listener, scene, linux_dmabuf_v1_destroy); + wl_list_remove(&scene->linux_dmabuf_v1_destroy.link); + wl_list_init(&scene->linux_dmabuf_v1_destroy.link); + scene->linux_dmabuf_v1 = NULL; +} + +void wlr_scene_set_linux_dmabuf_v1(struct wlr_scene *scene, + struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1) { + assert(scene->linux_dmabuf_v1 == NULL); + scene->linux_dmabuf_v1 = linux_dmabuf_v1; + scene->linux_dmabuf_v1_destroy.notify = scene_handle_linux_dmabuf_v1_destroy; + wl_signal_add(&linux_dmabuf_v1->events.destroy, &scene->linux_dmabuf_v1_destroy); +} + +static void scene_output_handle_destroy(struct wlr_addon *addon) { + struct wlr_scene_output *scene_output = + wl_container_of(addon, scene_output, addon); + wlr_scene_output_destroy(scene_output); +} + +static const struct wlr_addon_interface output_addon_impl = { + .name = "wlr_scene_output", + .destroy = scene_output_handle_destroy, +}; + +static void scene_node_output_update(struct wlr_scene_node *node, + struct wl_list *outputs, struct wlr_scene_output *ignore, + struct wlr_scene_output *force) { + if (node->type == WLR_SCENE_NODE_TREE) { + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + scene_node_output_update(child, outputs, ignore, force); + } + return; + } + + update_node_update_outputs(node, outputs, ignore, force); +} + +static void scene_output_update_geometry(struct wlr_scene_output *scene_output, + bool force_update) { + wlr_damage_ring_add_whole(&scene_output->damage_ring); + wlr_output_schedule_frame(scene_output->output); + + scene_node_output_update(&scene_output->scene->tree.node, + &scene_output->scene->outputs, NULL, force_update ? scene_output : NULL); +} + +static void scene_output_handle_commit(struct wl_listener *listener, void *data) { + struct wlr_scene_output *scene_output = wl_container_of(listener, + scene_output, output_commit); + struct wlr_output_event_commit *event = data; + const struct wlr_output_state *state = event->state; + + bool force_update = state->committed & ( + WLR_OUTPUT_STATE_TRANSFORM | + WLR_OUTPUT_STATE_SCALE | + WLR_OUTPUT_STATE_SUBPIXEL); + + if (force_update || state->committed & (WLR_OUTPUT_STATE_MODE | + WLR_OUTPUT_STATE_ENABLED)) { + scene_output_update_geometry(scene_output, force_update); + } + + // if the output has been committed with a certain damage, we know that region + // will be acknowledged by the backend so we don't need to keep track of it + // anymore + if (state->committed & WLR_OUTPUT_STATE_DAMAGE) { + if (wlr_swapchain_has_buffer(scene_output->output->swapchain, state->buffer)) { + pixman_region32_subtract(&scene_output->pending_commit_damage, + &scene_output->pending_commit_damage, &state->damage); + } else { + pixman_region32_union(&scene_output->pending_commit_damage, + &scene_output->pending_commit_damage, &state->damage); + } + } +} + +static void scene_output_handle_damage(struct wl_listener *listener, void *data) { + struct wlr_scene_output *scene_output = wl_container_of(listener, + scene_output, output_damage); + struct wlr_output_event_damage *event = data; + if (wlr_damage_ring_add(&scene_output->damage_ring, event->damage)) { + wlr_output_schedule_frame(scene_output->output); + } +} + +static void scene_output_handle_needs_frame(struct wl_listener *listener, void *data) { + struct wlr_scene_output *scene_output = wl_container_of(listener, + scene_output, output_needs_frame); + wlr_output_schedule_frame(scene_output->output); +} + +struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene, + struct wlr_output *output) { + struct wlr_scene_output *scene_output = calloc(1, sizeof(*scene_output)); + if (scene_output == NULL) { + return NULL; + } + + scene_output->output = output; + scene_output->scene = scene; + wlr_addon_init(&scene_output->addon, &output->addons, scene, &output_addon_impl); + + wlr_damage_ring_init(&scene_output->damage_ring); + pixman_region32_init(&scene_output->pending_commit_damage); + wl_list_init(&scene_output->damage_highlight_regions); + + int prev_output_index = -1; + struct wl_list *prev_output_link = &scene->outputs; + + struct wlr_scene_output *current_output; + wl_list_for_each(current_output, &scene->outputs, link) { + if (prev_output_index + 1 != current_output->index) { + break; + } + + prev_output_index = current_output->index; + prev_output_link = ¤t_output->link; + } + + scene_output->index = prev_output_index + 1; + assert(scene_output->index < 64); + wl_list_insert(prev_output_link, &scene_output->link); + + wl_signal_init(&scene_output->events.destroy); + + scene_output->output_commit.notify = scene_output_handle_commit; + wl_signal_add(&output->events.commit, &scene_output->output_commit); + + scene_output->output_damage.notify = scene_output_handle_damage; + wl_signal_add(&output->events.damage, &scene_output->output_damage); + + scene_output->output_needs_frame.notify = scene_output_handle_needs_frame; + wl_signal_add(&output->events.needs_frame, &scene_output->output_needs_frame); + + scene_output_update_geometry(scene_output, false); + + return scene_output; +} + +static void highlight_region_destroy(struct highlight_region *damage) { + wl_list_remove(&damage->link); + pixman_region32_fini(&damage->region); + free(damage); +} + +void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { + if (scene_output == NULL) { + return; + } + + wl_signal_emit_mutable(&scene_output->events.destroy, NULL); + + scene_node_output_update(&scene_output->scene->tree.node, + &scene_output->scene->outputs, scene_output, NULL); + + struct highlight_region *damage, *tmp_damage; + wl_list_for_each_safe(damage, tmp_damage, &scene_output->damage_highlight_regions, link) { + highlight_region_destroy(damage); + } + + wlr_addon_finish(&scene_output->addon); + wlr_damage_ring_finish(&scene_output->damage_ring); + pixman_region32_fini(&scene_output->pending_commit_damage); + wl_list_remove(&scene_output->link); + wl_list_remove(&scene_output->output_commit.link); + wl_list_remove(&scene_output->output_damage.link); + wl_list_remove(&scene_output->output_needs_frame.link); + + wl_array_release(&scene_output->render_list); + free(scene_output); +} + +struct wlr_scene_output *wlr_scene_get_scene_output(struct wlr_scene *scene, + struct wlr_output *output) { + struct wlr_addon *addon = + wlr_addon_find(&output->addons, scene, &output_addon_impl); + if (addon == NULL) { + return NULL; + } + struct wlr_scene_output *scene_output = + wl_container_of(addon, scene_output, addon); + return scene_output; +} + +void wlr_scene_output_set_position(struct wlr_scene_output *scene_output, + int lx, int ly) { + if (scene_output->x == lx && scene_output->y == ly) { + return; + } + + scene_output->x = lx; + scene_output->y = ly; + + scene_output_update_geometry(scene_output, false); +} + +static bool scene_node_invisible(struct wlr_scene_node *node) { + if (node->type == WLR_SCENE_NODE_TREE) { + return true; + } else if (node->type == WLR_SCENE_NODE_RECT) { + struct wlr_scene_rect *rect = wlr_scene_rect_from_node(node); + + return rect->color[3] == 0.f; + } else if (node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); + + return buffer->buffer == NULL && buffer->texture == NULL; + } + + return false; +} + +struct render_list_constructor_data { + struct wlr_box box; + struct wl_array *render_list; + bool calculate_visibility; +}; + +static bool construct_render_list_iterator(struct wlr_scene_node *node, + int lx, int ly, void *_data) { + struct render_list_constructor_data *data = _data; + + if (scene_node_invisible(node)) { + return false; + } + + // while rendering, the background should always be black. + // If we see a black rect, we can ignore rendering everything under the rect + // and even the rect itself. + if (node->type == WLR_SCENE_NODE_RECT && data->calculate_visibility) { + struct wlr_scene_rect *rect = wlr_scene_rect_from_node(node); + float *black = (float[4]){ 0.f, 0.f, 0.f, 1.f }; + + if (memcmp(rect->color, black, sizeof(float) * 4) == 0) { + return false; + } + } + + pixman_region32_t intersection; + pixman_region32_init(&intersection); + pixman_region32_intersect_rect(&intersection, &node->visible, + data->box.x, data->box.y, + data->box.width, data->box.height); + if (!pixman_region32_not_empty(&intersection)) { + pixman_region32_fini(&intersection); + return false; + } + + pixman_region32_fini(&intersection); + + struct render_list_entry *entry = wl_array_add(data->render_list, sizeof(*entry)); + if (!entry) { + return false; + } + + *entry = (struct render_list_entry){ + .node = node, + .x = lx, + .y = ly, + }; + + return false; +} + +static void output_state_apply_damage(const struct render_data *data, + struct wlr_output_state *state) { + struct wlr_scene_output *output = data->output; + + pixman_region32_t frame_damage; + pixman_region32_init(&frame_damage); + pixman_region32_copy(&frame_damage, &output->damage_ring.current); + transform_output_damage(&frame_damage, data); + pixman_region32_union(&output->pending_commit_damage, + &output->pending_commit_damage, &frame_damage); + pixman_region32_fini(&frame_damage); + + wlr_output_state_set_damage(state, &output->pending_commit_damage); +} + +static void scene_buffer_send_dmabuf_feedback(const struct wlr_scene *scene, + struct wlr_scene_buffer *scene_buffer, + const struct wlr_linux_dmabuf_feedback_v1_init_options *options) { + if (!scene->linux_dmabuf_v1) { + return; + } + + struct wlr_scene_surface *surface = wlr_scene_surface_try_from_buffer(scene_buffer); + if (!surface) { + return; + } + + // compare to the previous options so that we don't send + // duplicate feedback events. + if (memcmp(options, &scene_buffer->prev_feedback_options, sizeof(*options)) == 0) { + return; + } + + scene_buffer->prev_feedback_options = *options; + + struct wlr_linux_dmabuf_feedback_v1 feedback = {0}; + if (!wlr_linux_dmabuf_feedback_v1_init_with_options(&feedback, options)) { + return; + } + + wlr_linux_dmabuf_v1_set_surface_feedback(scene->linux_dmabuf_v1, + surface->surface, &feedback); + + wlr_linux_dmabuf_feedback_v1_finish(&feedback); +} + +static bool scene_entry_try_direct_scanout(struct render_list_entry *entry, + struct wlr_output_state *state, const struct render_data *data) { + struct wlr_scene_output *scene_output = data->output; + struct wlr_scene_node *node = entry->node; + + if (!scene_output->scene->direct_scanout) { + return false; + } + + if (scene_output->scene->debug_damage_option == + WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) { + // We don't want to enter direct scan out if we have highlight regions + // enabled. Otherwise, we won't be able to render the damage regions. + return false; + } + + if (node->type != WLR_SCENE_NODE_BUFFER) { + return false; + } + + if (state->committed & (WLR_OUTPUT_STATE_MODE | + WLR_OUTPUT_STATE_ENABLED | + WLR_OUTPUT_STATE_RENDER_FORMAT)) { + // Legacy DRM will explode if we try to modeset with a direct scanout buffer + return false; + } + + if (!wlr_output_is_direct_scanout_allowed(scene_output->output)) { + return false; + } + + struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); + if (buffer->buffer == NULL) { + return false; + } + + int default_width = buffer->buffer->width; + int default_height = buffer->buffer->height; + wlr_output_transform_coords(buffer->transform, &default_width, &default_height); + struct wlr_fbox default_box = { + .width = default_width, + .height = default_height, + }; + + if (!wlr_fbox_empty(&buffer->src_box) && + !wlr_fbox_equal(&buffer->src_box, &default_box)) { + return false; + } + + if (buffer->transform != data->transform) { + return false; + } + + struct wlr_box node_box = { .x = entry->x, .y = entry->y }; + scene_node_get_size(node, &node_box.width, &node_box.height); + + if (!wlr_box_equal(&data->logical, &node_box)) { + return false; + } + + if (buffer->primary_output == scene_output) { + struct wlr_linux_dmabuf_feedback_v1_init_options options = { + .main_renderer = scene_output->output->renderer, + .scanout_primary_output = scene_output->output, + }; + + scene_buffer_send_dmabuf_feedback(scene_output->scene, buffer, &options); + entry->sent_dmabuf_feedback = true; + } + + struct wlr_output_state pending; + wlr_output_state_init(&pending); + if (!wlr_output_state_copy(&pending, state)) { + return false; + } + + wlr_output_state_set_buffer(&pending, buffer->buffer); + + if (!wlr_output_test_state(scene_output->output, &pending)) { + wlr_output_state_finish(&pending); + return false; + } + + wlr_output_state_copy(state, &pending); + wlr_output_state_finish(&pending); + + struct wlr_scene_output_sample_event sample_event = { + .output = scene_output, + .direct_scanout = true, + }; + wl_signal_emit_mutable(&buffer->events.output_sample, &sample_event); + return true; +} + +bool wlr_scene_output_commit(struct wlr_scene_output *scene_output, + const struct wlr_scene_output_state_options *options) { + if (!scene_output->output->needs_frame && !pixman_region32_not_empty( + &scene_output->pending_commit_damage)) { + return true; + } + + bool ok = false; + struct wlr_output_state state; + wlr_output_state_init(&state); + if (!wlr_scene_output_build_state(scene_output, &state, options)) { + goto out; + } + + ok = wlr_output_commit_state(scene_output->output, &state); + if (!ok) { + goto out; + } + +out: + wlr_output_state_finish(&state); + return ok; +} + +bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, + struct wlr_output_state *state, const struct wlr_scene_output_state_options *options) { + struct wlr_scene_output_state_options default_options = {0}; + if (!options) { + options = &default_options; + } + struct wlr_scene_timer *timer = options->timer; + struct timespec start_time; + if (timer) { + clock_gettime(CLOCK_MONOTONIC, &start_time); + wlr_scene_timer_finish(timer); + *timer = (struct wlr_scene_timer){0}; + } + + if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) { + // if the state is being disabled, do nothing. + return true; + } + + struct wlr_output *output = scene_output->output; + enum wlr_scene_debug_damage_option debug_damage = + scene_output->scene->debug_damage_option; + + struct render_data render_data = { + .transform = output->transform, + .scale = output->scale, + .logical = { .x = scene_output->x, .y = scene_output->y }, + .output = scene_output, + }; + + output_pending_resolution(output, state, + &render_data.trans_width, &render_data.trans_height); + + if (state->committed & WLR_OUTPUT_STATE_TRANSFORM) { + if (render_data.transform != state->transform) { + wlr_damage_ring_add_whole(&scene_output->damage_ring); + } + + render_data.transform = state->transform; + } + + if (state->committed & WLR_OUTPUT_STATE_SCALE) { + if (render_data.scale != state->scale) { + wlr_damage_ring_add_whole(&scene_output->damage_ring); + } + + render_data.scale = state->scale; + } + + wlr_output_transform_coords(render_data.transform, + &render_data.trans_width, &render_data.trans_height); + + render_data.logical.width = render_data.trans_width / render_data.scale; + render_data.logical.height = render_data.trans_height / render_data.scale; + + struct render_list_constructor_data list_con = { + .box = render_data.logical, + .render_list = &scene_output->render_list, + .calculate_visibility = scene_output->scene->calculate_visibility, + }; + + list_con.render_list->size = 0; + scene_nodes_in_box(&scene_output->scene->tree.node, &list_con.box, + construct_render_list_iterator, &list_con); + array_realloc(list_con.render_list, list_con.render_list->size); + + struct render_list_entry *list_data = list_con.render_list->data; + int list_len = list_con.render_list->size / sizeof(*list_data); + + if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_RERENDER) { + wlr_damage_ring_add_whole(&scene_output->damage_ring); + } + + output_state_apply_damage(&render_data, state); + + bool scanout = list_len == 1 && + scene_entry_try_direct_scanout(&list_data[0], state, &render_data); + + if (scene_output->prev_scanout != scanout) { + scene_output->prev_scanout = scanout; + wlr_log(WLR_DEBUG, "Direct scan-out %s", + scanout ? "enabled" : "disabled"); + } + + if (scanout) { + if (timer) { + struct timespec end_time, duration; + clock_gettime(CLOCK_MONOTONIC, &end_time); + timespec_sub(&duration, &end_time, &start_time); + timer->pre_render_duration = timespec_to_nsec(&duration); + } + return true; + } + + struct timespec now; + if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) { + struct wl_list *regions = &scene_output->damage_highlight_regions; + clock_gettime(CLOCK_MONOTONIC, &now); + + // add the current frame's damage if there is damage + if (pixman_region32_not_empty(&scene_output->damage_ring.current)) { + struct highlight_region *current_damage = calloc(1, sizeof(*current_damage)); + if (current_damage) { + pixman_region32_init(¤t_damage->region); + pixman_region32_copy(¤t_damage->region, + &scene_output->damage_ring.current); + current_damage->when = now; + wl_list_insert(regions, ¤t_damage->link); + } + } + + pixman_region32_t acc_damage; + pixman_region32_init(&acc_damage); + struct highlight_region *damage, *tmp_damage; + wl_list_for_each_safe(damage, tmp_damage, regions, link) { + // remove overlaping damage regions + pixman_region32_subtract(&damage->region, &damage->region, &acc_damage); + pixman_region32_union(&acc_damage, &acc_damage, &damage->region); + + // if this damage is too old or has nothing in it, get rid of it + struct timespec time_diff; + timespec_sub(&time_diff, &now, &damage->when); + if (timespec_to_msec(&time_diff) >= HIGHLIGHT_DAMAGE_FADEOUT_TIME || + !pixman_region32_not_empty(&damage->region)) { + highlight_region_destroy(damage); + } + } + + wlr_damage_ring_add(&scene_output->damage_ring, &acc_damage); + pixman_region32_fini(&acc_damage); + } + + wlr_damage_ring_set_bounds(&scene_output->damage_ring, + render_data.trans_width, render_data.trans_height); + + if (!wlr_output_configure_primary_swapchain(output, state, &output->swapchain)) { + return false; + } + + struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain, NULL); + if (buffer == NULL) { + return false; + } + + if (timer) { + timer->render_timer = wlr_render_timer_create(output->renderer); + + struct timespec end_time, duration; + clock_gettime(CLOCK_MONOTONIC, &end_time); + timespec_sub(&duration, &end_time, &start_time); + timer->pre_render_duration = timespec_to_nsec(&duration); + } + + struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer, + &(struct wlr_buffer_pass_options){ + .timer = timer ? timer->render_timer : NULL, + }); + if (render_pass == NULL) { + wlr_buffer_unlock(buffer); + return false; + } + + render_data.render_pass = render_pass; + + pixman_region32_init(&render_data.damage); + wlr_damage_ring_rotate_buffer(&scene_output->damage_ring, buffer, + &render_data.damage); + + pixman_region32_t background; + pixman_region32_init(&background); + pixman_region32_copy(&background, &render_data.damage); + + // Cull areas of the background that are occluded by opaque regions of + // scene nodes above. Those scene nodes will just render atop having us + // never see the background. + if (scene_output->scene->calculate_visibility) { + for (int i = list_len - 1; i >= 0; i--) { + struct render_list_entry *entry = &list_data[i]; + + // We must only cull opaque regions that are visible by the node. + // The node's visibility will have the knowledge of a black rect + // that may have been omitted from the render list via the black + // rect optimization. In order to ensure we don't cull background + // rendering in that black rect region, consider the node's visibility. + pixman_region32_t opaque; + pixman_region32_init(&opaque); + scene_node_opaque_region(entry->node, entry->x, entry->y, &opaque); + pixman_region32_intersect(&opaque, &opaque, &entry->node->visible); + + pixman_region32_translate(&opaque, -scene_output->x, -scene_output->y); + wlr_region_scale(&opaque, &opaque, render_data.scale); + pixman_region32_subtract(&background, &background, &opaque); + pixman_region32_fini(&opaque); + } + + if (floor(render_data.scale) != render_data.scale) { + wlr_region_expand(&background, &background, 1); + + // reintersect with the damage because we never want to render + // outside of the damage region + pixman_region32_intersect(&background, &background, &render_data.damage); + } + } + + transform_output_damage(&background, &render_data); + wlr_render_pass_add_rect(render_pass, &(struct wlr_render_rect_options){ + .box = { .width = buffer->width, .height = buffer->height }, + .color = { .r = 0, .g = 0, .b = 0, .a = 1 }, + .clip = &background, + }); + pixman_region32_fini(&background); + + for (int i = list_len - 1; i >= 0; i--) { + struct render_list_entry *entry = &list_data[i]; + scene_entry_render(entry, &render_data); + + if (entry->node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(entry->node); + + if (buffer->primary_output == scene_output && !entry->sent_dmabuf_feedback) { + struct wlr_linux_dmabuf_feedback_v1_init_options options = { + .main_renderer = output->renderer, + .scanout_primary_output = NULL, + }; + + scene_buffer_send_dmabuf_feedback(scene_output->scene, buffer, &options); + } + } + } + + if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) { + struct highlight_region *damage; + wl_list_for_each(damage, &scene_output->damage_highlight_regions, link) { + struct timespec time_diff; + timespec_sub(&time_diff, &now, &damage->when); + int64_t time_diff_ms = timespec_to_msec(&time_diff); + float alpha = 1.0 - (double)time_diff_ms / HIGHLIGHT_DAMAGE_FADEOUT_TIME; + + wlr_render_pass_add_rect(render_pass, &(struct wlr_render_rect_options){ + .box = { .width = buffer->width, .height = buffer->height }, + .color = { .r = alpha * 0.5, .g = 0, .b = 0, .a = alpha * 0.5 }, + .clip = &damage->region, + }); + } + } + + wlr_output_add_software_cursors_to_render_pass(output, render_pass, &render_data.damage); + + pixman_region32_fini(&render_data.damage); + + if (!wlr_render_pass_submit(render_pass)) { + wlr_buffer_unlock(buffer); + + // if we failed to render the buffer, it will have undefined contents + // Trash the damage ring + wlr_damage_ring_add_whole(&scene_output->damage_ring); + return false; + } + + wlr_output_state_set_buffer(state, buffer); + wlr_buffer_unlock(buffer); + + if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT && + !wl_list_empty(&scene_output->damage_highlight_regions)) { + wlr_output_schedule_frame(scene_output->output); + } + + return true; +} + +int64_t wlr_scene_timer_get_duration_ns(struct wlr_scene_timer *timer) { + int64_t pre_render = timer->pre_render_duration; + if (!timer->render_timer) { + return pre_render; + } + int64_t render = wlr_render_timer_get_duration_ns(timer->render_timer); + return render != -1 ? pre_render + render : -1; +} + +void wlr_scene_timer_finish(struct wlr_scene_timer *timer) { + if (timer->render_timer) { + wlr_render_timer_destroy(timer->render_timer); + } +} + +static void scene_node_send_frame_done(struct wlr_scene_node *node, + struct wlr_scene_output *scene_output, struct timespec *now) { + if (!node->enabled) { + return; + } + + if (node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_scene_buffer *scene_buffer = + wlr_scene_buffer_from_node(node); + + if (scene_buffer->primary_output == scene_output) { + wlr_scene_buffer_send_frame_done(scene_buffer, now); + } + } else if (node->type == WLR_SCENE_NODE_TREE) { + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + scene_node_send_frame_done(child, scene_output, now); + } + } +} + +void wlr_scene_output_send_frame_done(struct wlr_scene_output *scene_output, + struct timespec *now) { + scene_node_send_frame_done(&scene_output->scene->tree.node, + scene_output, now); +} + +static void scene_output_for_each_scene_buffer(const struct wlr_box *output_box, + struct wlr_scene_node *node, int lx, int ly, + wlr_scene_buffer_iterator_func_t user_iterator, void *user_data) { + if (!node->enabled) { + return; + } + + lx += node->x; + ly += node->y; + + if (node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_box node_box = { .x = lx, .y = ly }; + scene_node_get_size(node, &node_box.width, &node_box.height); + + struct wlr_box intersection; + if (wlr_box_intersection(&intersection, output_box, &node_box)) { + struct wlr_scene_buffer *scene_buffer = + wlr_scene_buffer_from_node(node); + user_iterator(scene_buffer, lx, ly, user_data); + } + } else if (node->type == WLR_SCENE_NODE_TREE) { + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + scene_output_for_each_scene_buffer(output_box, child, lx, ly, + user_iterator, user_data); + } + } +} + +void wlr_scene_output_for_each_buffer(struct wlr_scene_output *scene_output, + wlr_scene_buffer_iterator_func_t iterator, void *user_data) { + struct wlr_box box = { .x = scene_output->x, .y = scene_output->y }; + wlr_output_effective_resolution(scene_output->output, + &box.width, &box.height); + scene_output_for_each_scene_buffer(&box, &scene_output->scene->tree.node, 0, 0, + iterator, user_data); +} diff --git a/types/scene/xdg_shell.c b/types/scene/xdg_shell.c new file mode 100644 index 0000000..9e3ffb0 --- /dev/null +++ b/types/scene/xdg_shell.c @@ -0,0 +1,129 @@ +#include +#include +#include + +struct wlr_scene_xdg_surface { + struct wlr_scene_tree *tree; + struct wlr_xdg_surface *xdg_surface; + struct wlr_scene_tree *surface_tree; + + struct wl_listener tree_destroy; + struct wl_listener xdg_surface_destroy; + struct wl_listener xdg_surface_map; + struct wl_listener xdg_surface_unmap; + struct wl_listener xdg_surface_commit; +}; + +static void scene_xdg_surface_handle_tree_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene_xdg_surface *scene_xdg_surface = + wl_container_of(listener, scene_xdg_surface, tree_destroy); + // tree and surface_node will be cleaned up by scene_node_finish + wl_list_remove(&scene_xdg_surface->tree_destroy.link); + wl_list_remove(&scene_xdg_surface->xdg_surface_destroy.link); + wl_list_remove(&scene_xdg_surface->xdg_surface_map.link); + wl_list_remove(&scene_xdg_surface->xdg_surface_unmap.link); + wl_list_remove(&scene_xdg_surface->xdg_surface_commit.link); + free(scene_xdg_surface); +} + +static void scene_xdg_surface_handle_xdg_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene_xdg_surface *scene_xdg_surface = + wl_container_of(listener, scene_xdg_surface, xdg_surface_destroy); + wlr_scene_node_destroy(&scene_xdg_surface->tree->node); +} + +static void scene_xdg_surface_handle_xdg_surface_map(struct wl_listener *listener, + void *data) { + struct wlr_scene_xdg_surface *scene_xdg_surface = + wl_container_of(listener, scene_xdg_surface, xdg_surface_map); + wlr_scene_node_set_enabled(&scene_xdg_surface->tree->node, true); +} + +static void scene_xdg_surface_handle_xdg_surface_unmap(struct wl_listener *listener, + void *data) { + struct wlr_scene_xdg_surface *scene_xdg_surface = + wl_container_of(listener, scene_xdg_surface, xdg_surface_unmap); + wlr_scene_node_set_enabled(&scene_xdg_surface->tree->node, false); +} + +static void scene_xdg_surface_update_position( + struct wlr_scene_xdg_surface *scene_xdg_surface) { + struct wlr_xdg_surface *xdg_surface = scene_xdg_surface->xdg_surface; + + struct wlr_box geo = {0}; + wlr_xdg_surface_get_geometry(xdg_surface, &geo); + wlr_scene_node_set_position(&scene_xdg_surface->surface_tree->node, + -geo.x, -geo.y); + + if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { + struct wlr_xdg_popup *popup = xdg_surface->popup; + if (popup != NULL) { + wlr_scene_node_set_position(&scene_xdg_surface->tree->node, + popup->current.geometry.x, popup->current.geometry.y); + } + } +} + +static void scene_xdg_surface_handle_xdg_surface_commit(struct wl_listener *listener, + void *data) { + struct wlr_scene_xdg_surface *scene_xdg_surface = + wl_container_of(listener, scene_xdg_surface, xdg_surface_commit); + scene_xdg_surface_update_position(scene_xdg_surface); +} + +struct wlr_scene_tree *wlr_scene_xdg_surface_create( + struct wlr_scene_tree *parent, struct wlr_xdg_surface *xdg_surface) { + struct wlr_scene_xdg_surface *scene_xdg_surface = + calloc(1, sizeof(*scene_xdg_surface)); + if (scene_xdg_surface == NULL) { + return NULL; + } + + scene_xdg_surface->xdg_surface = xdg_surface; + + scene_xdg_surface->tree = wlr_scene_tree_create(parent); + if (scene_xdg_surface->tree == NULL) { + free(scene_xdg_surface); + return NULL; + } + + scene_xdg_surface->surface_tree = wlr_scene_subsurface_tree_create( + scene_xdg_surface->tree, xdg_surface->surface); + if (scene_xdg_surface->surface_tree == NULL) { + wlr_scene_node_destroy(&scene_xdg_surface->tree->node); + free(scene_xdg_surface); + return NULL; + } + + scene_xdg_surface->tree_destroy.notify = + scene_xdg_surface_handle_tree_destroy; + wl_signal_add(&scene_xdg_surface->tree->node.events.destroy, + &scene_xdg_surface->tree_destroy); + + scene_xdg_surface->xdg_surface_destroy.notify = + scene_xdg_surface_handle_xdg_surface_destroy; + wl_signal_add(&xdg_surface->events.destroy, &scene_xdg_surface->xdg_surface_destroy); + + scene_xdg_surface->xdg_surface_map.notify = + scene_xdg_surface_handle_xdg_surface_map; + wl_signal_add(&xdg_surface->surface->events.map, + &scene_xdg_surface->xdg_surface_map); + + scene_xdg_surface->xdg_surface_unmap.notify = + scene_xdg_surface_handle_xdg_surface_unmap; + wl_signal_add(&xdg_surface->surface->events.unmap, + &scene_xdg_surface->xdg_surface_unmap); + + scene_xdg_surface->xdg_surface_commit.notify = + scene_xdg_surface_handle_xdg_surface_commit; + wl_signal_add(&xdg_surface->surface->events.commit, + &scene_xdg_surface->xdg_surface_commit); + + wlr_scene_node_set_enabled(&scene_xdg_surface->tree->node, + xdg_surface->surface->mapped); + scene_xdg_surface_update_position(scene_xdg_surface); + + return scene_xdg_surface->tree; +} diff --git a/types/seat/wlr_seat.c b/types/seat/wlr_seat.c new file mode 100644 index 0000000..2e8e4a8 --- /dev/null +++ b/types/seat/wlr_seat.c @@ -0,0 +1,492 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_seat.h" +#include "util/global.h" + +#define SEAT_VERSION 9 + +static void seat_handle_get_pointer(struct wl_client *client, + struct wl_resource *seat_resource, uint32_t id) { + uint32_t version = wl_resource_get_version(seat_resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + if (!seat_client) { + // The client still needs a resource, so here's a dummy: + seat_client_create_inert_pointer(client, version, id); + return; + } + if (!(seat_client->seat->accumulated_capabilities & WL_SEAT_CAPABILITY_POINTER)) { + wl_resource_post_error(seat_resource, WL_SEAT_ERROR_MISSING_CAPABILITY, + "wl_seat.get_pointer called when no pointer capability has existed"); + return; + } + + seat_client_create_pointer(seat_client, version, id); +} + +static void seat_handle_get_keyboard(struct wl_client *client, + struct wl_resource *seat_resource, uint32_t id) { + uint32_t version = wl_resource_get_version(seat_resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + if (!seat_client) { + seat_client_create_inert_keyboard(client, version, id); + return; + } + if (!(seat_client->seat->accumulated_capabilities & WL_SEAT_CAPABILITY_KEYBOARD)) { + wl_resource_post_error(seat_resource, WL_SEAT_ERROR_MISSING_CAPABILITY, + "wl_seat.get_keyboard called when no keyboard capability has existed"); + return; + } + + seat_client_create_keyboard(seat_client, version, id); +} + +static void seat_handle_get_touch(struct wl_client *client, + struct wl_resource *seat_resource, uint32_t id) { + uint32_t version = wl_resource_get_version(seat_resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + if (!seat_client) { + seat_client_create_inert_touch(client, version, id); + return; + } + if (!(seat_client->seat->accumulated_capabilities & WL_SEAT_CAPABILITY_TOUCH)) { + wl_resource_post_error(seat_resource, WL_SEAT_ERROR_MISSING_CAPABILITY, + "wl_seat.get_touch called when no touch capability has existed"); + return; + } + + seat_client_create_touch(seat_client, version, id); +} + +static void seat_client_destroy(struct wlr_seat_client *client) { + wl_signal_emit_mutable(&client->events.destroy, client); + + if (client == client->seat->pointer_state.focused_client) { + client->seat->pointer_state.focused_client = NULL; + } + if (client == client->seat->keyboard_state.focused_client) { + client->seat->keyboard_state.focused_client = NULL; + } + + if (client->seat->drag && client == client->seat->drag->seat_client) { + client->seat->drag->seat_client = NULL; + } + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &client->pointers) { + seat_client_destroy_pointer(resource); + } + wl_resource_for_each_safe(resource, tmp, &client->keyboards) { + seat_client_destroy_keyboard(resource); + } + wl_resource_for_each_safe(resource, tmp, &client->touches) { + seat_client_destroy_touch(resource); + } + wl_resource_for_each_safe(resource, tmp, &client->data_devices) { + // Make the data device inert + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + wl_resource_set_user_data(resource, NULL); + } + wl_resource_for_each_safe(resource, tmp, &client->resources) { + // Make the seat resource inert + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + wl_resource_set_user_data(resource, NULL); + } + + wl_list_remove(&client->link); + free(client); +} + +static void seat_client_handle_resource_destroy( + struct wl_resource *seat_resource) { + struct wlr_seat_client *client = + wlr_seat_client_from_resource(seat_resource); + if (!client) { + return; + } + + wl_list_remove(wl_resource_get_link(seat_resource)); + if (!wl_list_empty(&client->resources)) { + return; + } + + seat_client_destroy(client); +} + +static void seat_handle_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_seat_interface seat_impl = { + .get_pointer = seat_handle_get_pointer, + .get_keyboard = seat_handle_get_keyboard, + .get_touch = seat_handle_get_touch, + .release = seat_handle_release, +}; + +static struct wlr_seat_client *seat_client_create(struct wlr_seat *wlr_seat, + struct wl_client *client, struct wl_resource *wl_resource) { + struct wlr_seat_client *seat_client = calloc(1, sizeof(*seat_client)); + if (!seat_client) { + return NULL; + } + + seat_client->client = client; + seat_client->seat = wlr_seat; + wl_list_init(&seat_client->resources); + wl_list_init(&seat_client->pointers); + wl_list_init(&seat_client->keyboards); + wl_list_init(&seat_client->touches); + wl_list_init(&seat_client->data_devices); + wl_signal_init(&seat_client->events.destroy); + + wl_list_insert(&wlr_seat->clients, &seat_client->link); + + struct wlr_surface *pointer_focus = + wlr_seat->pointer_state.focused_surface; + if (pointer_focus != NULL && + wl_resource_get_client(pointer_focus->resource) == client) { + wlr_seat->pointer_state.focused_client = seat_client; + } + + struct wlr_surface *keyboard_focus = + wlr_seat->keyboard_state.focused_surface; + if (keyboard_focus != NULL && + wl_resource_get_client(keyboard_focus->resource) == client) { + wlr_seat->keyboard_state.focused_client = seat_client; + } + + return seat_client; +} + +static void seat_handle_bind(struct wl_client *client, void *_wlr_seat, + uint32_t version, uint32_t id) { + // `wlr_seat` can be NULL if the seat global is being destroyed + struct wlr_seat *wlr_seat = _wlr_seat; + + struct wl_resource *wl_resource = + wl_resource_create(client, &wl_seat_interface, version, id); + if (wl_resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(wl_resource, &seat_impl, NULL, + seat_client_handle_resource_destroy); + wl_list_init(wl_resource_get_link(wl_resource)); + if (wlr_seat == NULL) { + return; + } + + struct wlr_seat_client *seat_client = + wlr_seat_client_for_wl_client(wlr_seat, client); + if (!seat_client) { + seat_client = seat_client_create(wlr_seat, client, wl_resource); + } + + if (seat_client == NULL) { + wl_resource_destroy(wl_resource); + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_user_data(wl_resource, seat_client); + wl_list_insert(&seat_client->resources, wl_resource_get_link(wl_resource)); + if (version >= WL_SEAT_NAME_SINCE_VERSION) { + wl_seat_send_name(wl_resource, wlr_seat->name); + } + wl_seat_send_capabilities(wl_resource, wlr_seat->capabilities); +} + +void wlr_seat_destroy(struct wlr_seat *seat) { + if (!seat) { + return; + } + + wlr_seat_pointer_clear_focus(seat); + wlr_seat_keyboard_clear_focus(seat); + + wlr_seat_set_keyboard(seat, NULL); + + struct wlr_touch_point *point; + wl_list_for_each(point, &seat->touch_state.touch_points, link) { + wlr_seat_touch_point_clear_focus(seat, 0, point->touch_id); + } + + wl_signal_emit_mutable(&seat->events.destroy, seat); + + wl_list_remove(&seat->display_destroy.link); + + wlr_data_source_destroy(seat->selection_source); + wlr_primary_selection_source_destroy(seat->primary_selection_source); + + struct wlr_seat_client *client, *tmp; + wl_list_for_each_safe(client, tmp, &seat->clients, link) { + seat_client_destroy(client); + } + + wlr_global_destroy_safe(seat->global); + free(seat->pointer_state.default_grab); + free(seat->keyboard_state.default_grab); + free(seat->touch_state.default_grab); + free(seat->name); + free(seat); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_seat *seat = + wl_container_of(listener, seat, display_destroy); + wlr_seat_destroy(seat); +} + +struct wlr_seat *wlr_seat_create(struct wl_display *display, const char *name) { + struct wlr_seat *seat = calloc(1, sizeof(*seat)); + if (!seat) { + return NULL; + } + + // pointer state + seat->pointer_state.seat = seat; + wl_list_init(&seat->pointer_state.surface_destroy.link); + + struct wlr_seat_pointer_grab *pointer_grab = calloc(1, sizeof(*pointer_grab)); + if (!pointer_grab) { + free(seat); + return NULL; + } + pointer_grab->interface = &default_pointer_grab_impl; + pointer_grab->seat = seat; + seat->pointer_state.default_grab = pointer_grab; + seat->pointer_state.grab = pointer_grab; + + wl_signal_init(&seat->pointer_state.events.focus_change); + + // keyboard state + struct wlr_seat_keyboard_grab *keyboard_grab = calloc(1, sizeof(*keyboard_grab)); + if (!keyboard_grab) { + free(pointer_grab); + free(seat); + return NULL; + } + keyboard_grab->interface = &default_keyboard_grab_impl; + keyboard_grab->seat = seat; + seat->keyboard_state.default_grab = keyboard_grab; + seat->keyboard_state.grab = keyboard_grab; + + seat->keyboard_state.seat = seat; + wl_list_init(&seat->keyboard_state.surface_destroy.link); + + wl_signal_init(&seat->keyboard_state.events.focus_change); + + // touch state + struct wlr_seat_touch_grab *touch_grab = calloc(1, sizeof(*touch_grab)); + if (!touch_grab) { + free(pointer_grab); + free(keyboard_grab); + free(seat); + return NULL; + } + touch_grab->interface = &default_touch_grab_impl; + touch_grab->seat = seat; + seat->touch_state.default_grab = touch_grab; + seat->touch_state.grab = touch_grab; + + seat->touch_state.seat = seat; + wl_list_init(&seat->touch_state.touch_points); + + seat->global = wl_global_create(display, &wl_seat_interface, + SEAT_VERSION, seat, seat_handle_bind); + if (seat->global == NULL) { + free(touch_grab); + free(pointer_grab); + free(keyboard_grab); + free(seat); + return NULL; + } + seat->display = display; + seat->name = strdup(name); + wl_list_init(&seat->clients); + wl_list_init(&seat->selection_offers); + wl_list_init(&seat->drag_offers); + + wl_signal_init(&seat->events.request_start_drag); + wl_signal_init(&seat->events.start_drag); + + wl_signal_init(&seat->events.request_set_cursor); + + wl_signal_init(&seat->events.request_set_selection); + wl_signal_init(&seat->events.set_selection); + wl_signal_init(&seat->events.request_set_primary_selection); + wl_signal_init(&seat->events.set_primary_selection); + + wl_signal_init(&seat->events.pointer_grab_begin); + wl_signal_init(&seat->events.pointer_grab_end); + + wl_signal_init(&seat->events.keyboard_grab_begin); + wl_signal_init(&seat->events.keyboard_grab_end); + + wl_signal_init(&seat->events.touch_grab_begin); + wl_signal_init(&seat->events.touch_grab_end); + + wl_signal_init(&seat->events.destroy); + + seat->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &seat->display_destroy); + + return seat; +} + +struct wlr_seat_client *wlr_seat_client_for_wl_client(struct wlr_seat *wlr_seat, + struct wl_client *wl_client) { + struct wlr_seat_client *seat_client; + wl_list_for_each(seat_client, &wlr_seat->clients, link) { + if (seat_client->client == wl_client) { + return seat_client; + } + } + return NULL; +} + +void wlr_seat_set_capabilities(struct wlr_seat *wlr_seat, + uint32_t capabilities) { + // if the capabilities haven't changed (i.e a redundant mouse was removed), + // we don't actually have to do anything + if (capabilities == wlr_seat->capabilities) { + return; + } + wlr_seat->capabilities = capabilities; + wlr_seat->accumulated_capabilities |= capabilities; + + struct wlr_seat_client *client; + wl_list_for_each(client, &wlr_seat->clients, link) { + // Make resources inert if necessary + if ((capabilities & WL_SEAT_CAPABILITY_POINTER) == 0) { + struct wlr_seat_client *focused_client = + wlr_seat->pointer_state.focused_client; + struct wlr_surface *focused_surface = + wlr_seat->pointer_state.focused_surface; + + if (focused_client != NULL && focused_surface != NULL) { + seat_client_send_pointer_leave_raw(focused_client, focused_surface); + } + + // Note: we don't set focused client/surface to NULL since we need + // them to send the enter event if the pointer is recreated + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &client->pointers) { + seat_client_destroy_pointer(resource); + } + } + if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD) == 0) { + struct wlr_seat_client *focused_client = + wlr_seat->keyboard_state.focused_client; + struct wlr_surface *focused_surface = + wlr_seat->keyboard_state.focused_surface; + + if (focused_client != NULL && focused_surface != NULL) { + seat_client_send_keyboard_leave_raw(focused_client, + focused_surface); + } + + // Note: we don't set focused client/surface to NULL since we need + // them to send the enter event if the keyboard is recreated + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &client->keyboards) { + seat_client_destroy_keyboard(resource); + } + } + if ((capabilities & WL_SEAT_CAPABILITY_TOUCH) == 0) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &client->touches) { + seat_client_destroy_touch(resource); + } + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &client->resources) { + wl_seat_send_capabilities(resource, capabilities); + } + } +} + +void wlr_seat_set_name(struct wlr_seat *wlr_seat, const char *name) { + free(wlr_seat->name); + wlr_seat->name = strdup(name); + struct wlr_seat_client *client; + wl_list_for_each(client, &wlr_seat->clients, link) { + struct wl_resource *resource; + wl_resource_for_each(resource, &client->resources) { + wl_seat_send_name(resource, name); + } + } +} + +struct wlr_seat_client *wlr_seat_client_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_seat_interface, + &seat_impl)); + return wl_resource_get_user_data(resource); +} + +uint32_t wlr_seat_client_next_serial(struct wlr_seat_client *client) { + uint32_t serial = wl_display_next_serial(wl_client_get_display(client->client)); + struct wlr_serial_ringset *set = &client->serials; + + if (set->count == 0) { + set->data[0].min_incl = serial; + set->data[0].max_incl = serial; + set->count = 1; + set->end = 0; + } else if (set->data[set->end].max_incl + 1 != serial) { + if (set->count < WLR_SERIAL_RINGSET_SIZE) { + set->count++; + } + set->end = (set->end + 1) % WLR_SERIAL_RINGSET_SIZE; + set->data[set->end].min_incl = serial; + set->data[set->end].max_incl = serial; + } else { + set->data[set->end].max_incl = serial; + } + + return serial; +} + +bool wlr_seat_client_validate_event_serial(struct wlr_seat_client *client, uint32_t serial) { + uint32_t cur = wl_display_get_serial(wl_client_get_display(client->client)); + struct wlr_serial_ringset *set = &client->serials; + uint32_t rev_dist = cur - serial; + + if (rev_dist >= UINT32_MAX / 2) { + // serial is closer to being 'newer' instead of 'older' than + // the current serial, so it's either invalid or incredibly old + return false; + } + + for (int i = 0; i < set->count; i++) { + int j = (set->end - i + WLR_SERIAL_RINGSET_SIZE) % WLR_SERIAL_RINGSET_SIZE; + if (rev_dist < cur - set->data[j].max_incl) { + return false; + } + if (rev_dist <= cur - set->data[j].min_incl) { + return true; + } + } + + // Iff the set is full, then `rev_dist` is large enough that serial + // could already have been recycled out of the set. + return set->count == WLR_SERIAL_RINGSET_SIZE; +} diff --git a/types/seat/wlr_seat_keyboard.c b/types/seat/wlr_seat_keyboard.c new file mode 100644 index 0000000..dcbc0a3 --- /dev/null +++ b/types/seat/wlr_seat_keyboard.c @@ -0,0 +1,478 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_data_device.h" +#include "types/wlr_seat.h" + +static void default_keyboard_enter(struct wlr_seat_keyboard_grab *grab, + struct wlr_surface *surface, const uint32_t keycodes[], size_t num_keycodes, + const struct wlr_keyboard_modifiers *modifiers) { + wlr_seat_keyboard_enter(grab->seat, surface, keycodes, num_keycodes, modifiers); +} + +static void default_keyboard_clear_focus(struct wlr_seat_keyboard_grab *grab) { + wlr_seat_keyboard_clear_focus(grab->seat); +} + +static void default_keyboard_key(struct wlr_seat_keyboard_grab *grab, + uint32_t time, uint32_t key, uint32_t state) { + wlr_seat_keyboard_send_key(grab->seat, time, key, state); +} + +static void default_keyboard_modifiers(struct wlr_seat_keyboard_grab *grab, + const struct wlr_keyboard_modifiers *modifiers) { + wlr_seat_keyboard_send_modifiers(grab->seat, modifiers); +} + +static void default_keyboard_cancel(struct wlr_seat_keyboard_grab *grab) { + // cannot be cancelled +} + +const struct wlr_keyboard_grab_interface default_keyboard_grab_impl = { + .enter = default_keyboard_enter, + .clear_focus = default_keyboard_clear_focus, + .key = default_keyboard_key, + .modifiers = default_keyboard_modifiers, + .cancel = default_keyboard_cancel, +}; + + +static void keyboard_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_keyboard_interface keyboard_impl = { + .release = keyboard_release, +}; + +static struct wlr_seat_client *seat_client_from_keyboard_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_keyboard_interface, + &keyboard_impl)); + return wl_resource_get_user_data(resource); +} + +static void keyboard_handle_resource_destroy(struct wl_resource *resource) { + seat_client_destroy_keyboard(resource); +} + + +void wlr_seat_keyboard_send_key(struct wlr_seat *wlr_seat, uint32_t time, + uint32_t key, uint32_t state) { + struct wlr_seat_client *client = wlr_seat->keyboard_state.focused_client; + if (!client) { + return; + } + + uint32_t serial = wlr_seat_client_next_serial(client); + struct wl_resource *resource; + wl_resource_for_each(resource, &client->keyboards) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + + wl_keyboard_send_key(resource, serial, time, key, state); + } +} + +static void seat_client_send_keymap(struct wlr_seat_client *client, + struct wlr_keyboard *keyboard); + +static void handle_keyboard_keymap(struct wl_listener *listener, void *data) { + struct wlr_seat_keyboard_state *state = + wl_container_of(listener, state, keyboard_keymap); + struct wlr_seat_client *client; + struct wlr_keyboard *keyboard = data; + if (keyboard == state->keyboard) { + wl_list_for_each(client, &state->seat->clients, link) { + seat_client_send_keymap(client, state->keyboard); + } + } +} + +static void seat_client_send_repeat_info(struct wlr_seat_client *client, + struct wlr_keyboard *keyboard); + +static void handle_keyboard_repeat_info(struct wl_listener *listener, + void *data) { + struct wlr_seat_keyboard_state *state = + wl_container_of(listener, state, keyboard_repeat_info); + struct wlr_seat_client *client; + wl_list_for_each(client, &state->seat->clients, link) { + seat_client_send_repeat_info(client, state->keyboard); + } +} + +static void handle_keyboard_destroy(struct wl_listener *listener, void *data) { + struct wlr_seat_keyboard_state *state = + wl_container_of(listener, state, keyboard_destroy); + wlr_seat_set_keyboard(state->seat, NULL); +} + +void wlr_seat_set_keyboard(struct wlr_seat *seat, + struct wlr_keyboard *keyboard) { + // TODO call this on device key event before the event reaches the + // compositor and set a pending keyboard and then send the new keyboard + // state on the next keyboard notify event. + if (seat->keyboard_state.keyboard == keyboard) { + return; + } + + if (seat->keyboard_state.keyboard) { + wl_list_remove(&seat->keyboard_state.keyboard_destroy.link); + wl_list_remove(&seat->keyboard_state.keyboard_keymap.link); + wl_list_remove(&seat->keyboard_state.keyboard_repeat_info.link); + seat->keyboard_state.keyboard = NULL; + } + + if (keyboard) { + seat->keyboard_state.keyboard = keyboard; + + wl_signal_add(&keyboard->base.events.destroy, + &seat->keyboard_state.keyboard_destroy); + seat->keyboard_state.keyboard_destroy.notify = handle_keyboard_destroy; + wl_signal_add(&keyboard->events.keymap, + &seat->keyboard_state.keyboard_keymap); + seat->keyboard_state.keyboard_keymap.notify = handle_keyboard_keymap; + wl_signal_add(&keyboard->events.repeat_info, + &seat->keyboard_state.keyboard_repeat_info); + seat->keyboard_state.keyboard_repeat_info.notify = + handle_keyboard_repeat_info; + + struct wlr_seat_client *client; + wl_list_for_each(client, &seat->clients, link) { + seat_client_send_keymap(client, keyboard); + seat_client_send_repeat_info(client, keyboard); + } + + wlr_seat_keyboard_send_modifiers(seat, &keyboard->modifiers); + } else { + seat->keyboard_state.keyboard = NULL; + } +} + +struct wlr_keyboard *wlr_seat_get_keyboard(struct wlr_seat *seat) { + return seat->keyboard_state.keyboard; +} + +void wlr_seat_keyboard_start_grab(struct wlr_seat *wlr_seat, + struct wlr_seat_keyboard_grab *grab) { + grab->seat = wlr_seat; + wlr_seat->keyboard_state.grab = grab; + + wl_signal_emit_mutable(&wlr_seat->events.keyboard_grab_begin, grab); +} + +void wlr_seat_keyboard_end_grab(struct wlr_seat *wlr_seat) { + struct wlr_seat_keyboard_grab *grab = wlr_seat->keyboard_state.grab; + + if (grab != wlr_seat->keyboard_state.default_grab) { + wlr_seat->keyboard_state.grab = wlr_seat->keyboard_state.default_grab; + wl_signal_emit_mutable(&wlr_seat->events.keyboard_grab_end, grab); + if (grab->interface->cancel) { + grab->interface->cancel(grab); + } + } +} + +static void seat_keyboard_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_seat_keyboard_state *state = wl_container_of( + listener, state, surface_destroy); + wl_list_remove(&state->surface_destroy.link); + wl_list_init(&state->surface_destroy.link); + wlr_seat_keyboard_clear_focus(state->seat); +} + +void wlr_seat_keyboard_send_modifiers(struct wlr_seat *seat, + const struct wlr_keyboard_modifiers *modifiers) { + struct wlr_seat_client *client = seat->keyboard_state.focused_client; + if (client == NULL) { + return; + } + + uint32_t serial = wlr_seat_client_next_serial(client); + struct wl_resource *resource; + wl_resource_for_each(resource, &client->keyboards) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + + if (modifiers == NULL) { + wl_keyboard_send_modifiers(resource, serial, 0, 0, 0, 0); + } else { + wl_keyboard_send_modifiers(resource, serial, + modifiers->depressed, modifiers->latched, + modifiers->locked, modifiers->group); + } + } +} + +void seat_client_send_keyboard_leave_raw(struct wlr_seat_client *seat_client, + struct wlr_surface *surface) { + uint32_t serial = wlr_seat_client_next_serial(seat_client); + struct wl_resource *resource; + wl_resource_for_each(resource, &seat_client->keyboards) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + wl_keyboard_send_leave(resource, serial, surface->resource); + } +} + +void wlr_seat_keyboard_enter(struct wlr_seat *seat, + struct wlr_surface *surface, const uint32_t keycodes[], size_t num_keycodes, + const struct wlr_keyboard_modifiers *modifiers) { + if (seat->keyboard_state.focused_surface == surface) { + // this surface already got an enter notify + return; + } + + struct wlr_seat_client *client = NULL; + + if (surface) { + struct wl_client *wl_client = wl_resource_get_client(surface->resource); + client = wlr_seat_client_for_wl_client(seat, wl_client); + } + + struct wlr_seat_client *focused_client = + seat->keyboard_state.focused_client; + struct wlr_surface *focused_surface = + seat->keyboard_state.focused_surface; + + // leave the previously entered surface + if (focused_client != NULL && focused_surface != NULL) { + seat_client_send_keyboard_leave_raw(focused_client, focused_surface); + } + + // enter the current surface + if (client != NULL) { + struct wl_array keys = { + .data = (void *)keycodes, + .size = num_keycodes * sizeof(keycodes[0]), + }; + uint32_t serial = wlr_seat_client_next_serial(client); + struct wl_resource *resource; + wl_resource_for_each(resource, &client->keyboards) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + wl_keyboard_send_enter(resource, serial, surface->resource, &keys); + } + } + + // reinitialize the focus destroy events + wl_list_remove(&seat->keyboard_state.surface_destroy.link); + wl_list_init(&seat->keyboard_state.surface_destroy.link); + if (surface) { + wl_signal_add(&surface->events.destroy, + &seat->keyboard_state.surface_destroy); + seat->keyboard_state.surface_destroy.notify = + seat_keyboard_handle_surface_destroy; + } + + seat->keyboard_state.focused_client = client; + seat->keyboard_state.focused_surface = surface; + + if (client != NULL) { + // tell new client about any modifier change last, + // as it targets seat->keyboard_state.focused_client + wlr_seat_keyboard_send_modifiers(seat, modifiers); + + seat_client_send_selection(client); + } + + struct wlr_seat_keyboard_focus_change_event event = { + .seat = seat, + .old_surface = focused_surface, + .new_surface = surface, + }; + wl_signal_emit_mutable(&seat->keyboard_state.events.focus_change, &event); +} + +void wlr_seat_keyboard_notify_enter(struct wlr_seat *seat, + struct wlr_surface *surface, const uint32_t keycodes[], size_t num_keycodes, + const struct wlr_keyboard_modifiers *modifiers) { + // NULL surfaces are prohibited in the grab-compatible API. Use + // wlr_seat_keyboard_notify_clear_focus() instead. + assert(surface); + struct wlr_seat_keyboard_grab *grab = seat->keyboard_state.grab; + grab->interface->enter(grab, surface, keycodes, num_keycodes, modifiers); +} + +void wlr_seat_keyboard_clear_focus(struct wlr_seat *seat) { + wlr_seat_keyboard_enter(seat, NULL, NULL, 0, NULL); +} + +void wlr_seat_keyboard_notify_clear_focus(struct wlr_seat *seat) { + struct wlr_seat_keyboard_grab *grab = seat->keyboard_state.grab; + grab->interface->clear_focus(grab); +} + +bool wlr_seat_keyboard_has_grab(struct wlr_seat *seat) { + return seat->keyboard_state.grab->interface != &default_keyboard_grab_impl; +} + +void wlr_seat_keyboard_notify_modifiers(struct wlr_seat *seat, + const struct wlr_keyboard_modifiers *modifiers) { + clock_gettime(CLOCK_MONOTONIC, &seat->last_event); + struct wlr_seat_keyboard_grab *grab = seat->keyboard_state.grab; + grab->interface->modifiers(grab, modifiers); +} + +void wlr_seat_keyboard_notify_key(struct wlr_seat *seat, uint32_t time, + uint32_t key, uint32_t state) { + clock_gettime(CLOCK_MONOTONIC, &seat->last_event); + struct wlr_seat_keyboard_grab *grab = seat->keyboard_state.grab; + grab->interface->key(grab, time, key, state); +} + + +static void seat_client_send_keymap(struct wlr_seat_client *client, + struct wlr_keyboard *keyboard) { + if (!keyboard) { + return; + } + + enum wl_keyboard_keymap_format format; + int fd, devnull = -1; + uint32_t size; + if (keyboard->keymap != NULL) { + format = WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1; + fd = keyboard->keymap_fd; + size = keyboard->keymap_size; + } else { + format = WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP; + devnull = open("/dev/null", O_RDONLY | O_CLOEXEC); + if (devnull < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open /dev/null"); + return; + } + fd = devnull; + size = 0; + } + + // TODO: We should probably lift all of the keys set by the other + // keyboard + struct wl_resource *resource; + wl_resource_for_each(resource, &client->keyboards) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + + wl_keyboard_send_keymap(resource, format, fd, size); + } + + if (devnull >= 0) { + close(devnull); + } +} + +static void seat_client_send_repeat_info(struct wlr_seat_client *client, + struct wlr_keyboard *keyboard) { + if (!keyboard) { + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &client->keyboards) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + + if (wl_resource_get_version(resource) >= + WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION) { + wl_keyboard_send_repeat_info(resource, + keyboard->repeat_info.rate, keyboard->repeat_info.delay); + } + } +} + +void seat_client_create_keyboard(struct wlr_seat_client *seat_client, + uint32_t version, uint32_t id) { + struct wl_resource *resource = wl_resource_create(seat_client->client, + &wl_keyboard_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(seat_client->client); + return; + } + wl_resource_set_implementation(resource, &keyboard_impl, seat_client, + keyboard_handle_resource_destroy); + wl_list_insert(&seat_client->keyboards, wl_resource_get_link(resource)); + + if ((seat_client->seat->capabilities & WL_SEAT_CAPABILITY_KEYBOARD) == 0) { + wl_resource_set_user_data(resource, NULL); + return; + } + + struct wlr_keyboard *keyboard = seat_client->seat->keyboard_state.keyboard; + if (keyboard == NULL) { + return; + } + seat_client_send_keymap(seat_client, keyboard); + seat_client_send_repeat_info(seat_client, keyboard); + + struct wlr_seat_client *focused_client = + seat_client->seat->keyboard_state.focused_client; + struct wlr_surface *focused_surface = + seat_client->seat->keyboard_state.focused_surface; + + // Send an enter event if there is a focused client/surface stored + if (focused_client == seat_client && focused_surface != NULL) { + uint32_t *keycodes = keyboard->keycodes; + size_t num_keycodes = keyboard->num_keycodes; + + struct wl_array keys; + wl_array_init(&keys); + for (size_t i = 0; i < num_keycodes; ++i) { + uint32_t *p = wl_array_add(&keys, sizeof(uint32_t)); + if (!p) { + wlr_log(WLR_ERROR, "Cannot allocate memory, skipping keycode: %" PRIu32 "\n", + keycodes[i]); + continue; + } + *p = keycodes[i]; + } + + uint32_t serial = wlr_seat_client_next_serial(focused_client); + struct wl_resource *resource; + wl_resource_for_each(resource, &focused_client->keyboards) { + if (wl_resource_get_id(resource) == id) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + wl_keyboard_send_enter(resource, serial, + focused_surface->resource, &keys); + } + } + + wl_array_release(&keys); + + wlr_seat_keyboard_send_modifiers(seat_client->seat, + &keyboard->modifiers); + } +} + +void seat_client_create_inert_keyboard(struct wl_client *client, + uint32_t version, uint32_t id) { + struct wl_resource *resource = + wl_resource_create(client, &wl_keyboard_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &keyboard_impl, NULL, NULL); +} + +void seat_client_destroy_keyboard(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + wl_resource_set_user_data(resource, NULL); +} diff --git a/types/seat/wlr_seat_pointer.c b/types/seat/wlr_seat_pointer.c new file mode 100644 index 0000000..9618d52 --- /dev/null +++ b/types/seat/wlr_seat_pointer.c @@ -0,0 +1,585 @@ +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_seat.h" +#include "util/set.h" + +static void default_pointer_enter(struct wlr_seat_pointer_grab *grab, + struct wlr_surface *surface, double sx, double sy) { + wlr_seat_pointer_enter(grab->seat, surface, sx, sy); +} + +static void default_pointer_clear_focus(struct wlr_seat_pointer_grab *grab) { + wlr_seat_pointer_clear_focus(grab->seat); +} + +static void default_pointer_motion(struct wlr_seat_pointer_grab *grab, + uint32_t time, double sx, double sy) { + wlr_seat_pointer_send_motion(grab->seat, time, sx, sy); +} + +static uint32_t default_pointer_button(struct wlr_seat_pointer_grab *grab, + uint32_t time, uint32_t button, enum wl_pointer_button_state state) { + return wlr_seat_pointer_send_button(grab->seat, time, button, state); +} + +static void default_pointer_axis(struct wlr_seat_pointer_grab *grab, + uint32_t time, enum wl_pointer_axis orientation, double value, + int32_t value_discrete, enum wl_pointer_axis_source source, + enum wl_pointer_axis_relative_direction relative_direction) { + wlr_seat_pointer_send_axis(grab->seat, time, orientation, value, + value_discrete, source, relative_direction); +} + +static void default_pointer_frame(struct wlr_seat_pointer_grab *grab) { + wlr_seat_pointer_send_frame(grab->seat); +} + +static void default_pointer_cancel(struct wlr_seat_pointer_grab *grab) { + // cannot be cancelled +} + +const struct wlr_pointer_grab_interface default_pointer_grab_impl = { + .enter = default_pointer_enter, + .clear_focus = default_pointer_clear_focus, + .motion = default_pointer_motion, + .button = default_pointer_button, + .axis = default_pointer_axis, + .frame = default_pointer_frame, + .cancel = default_pointer_cancel, +}; + + +static void pointer_send_frame(struct wl_resource *resource) { + if (wl_resource_get_version(resource) >= + WL_POINTER_FRAME_SINCE_VERSION) { + wl_pointer_send_frame(resource); + } +} + +static const struct wl_pointer_interface pointer_impl; + +struct wlr_seat_client *wlr_seat_client_from_pointer_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_pointer_interface, + &pointer_impl)); + return wl_resource_get_user_data(resource); +} + +static void pointer_cursor_surface_handle_commit(struct wlr_surface *surface) { + pixman_region32_clear(&surface->input_region); + if (wlr_surface_has_buffer(surface)) { + wlr_surface_map(surface); + } +} + +static const struct wlr_surface_role pointer_cursor_surface_role = { + .name = "wl_pointer-cursor", + .no_object = true, + .commit = pointer_cursor_surface_handle_commit, +}; + +static void pointer_set_cursor(struct wl_client *client, + struct wl_resource *pointer_resource, uint32_t serial, + struct wl_resource *surface_resource, + int32_t hotspot_x, int32_t hotspot_y) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_pointer_resource(pointer_resource); + if (seat_client == NULL) { + return; + } + + struct wlr_surface *surface = NULL; + if (surface_resource != NULL) { + surface = wlr_surface_from_resource(surface_resource); + if (!wlr_surface_set_role(surface, &pointer_cursor_surface_role, + surface_resource, WL_POINTER_ERROR_ROLE)) { + return; + } + + pointer_cursor_surface_handle_commit(surface); + } + + struct wlr_seat_pointer_request_set_cursor_event event = { + .seat_client = seat_client, + .surface = surface, + .serial = serial, + .hotspot_x = hotspot_x, + .hotspot_y = hotspot_y, + }; + wl_signal_emit_mutable(&seat_client->seat->events.request_set_cursor, &event); +} + +static void pointer_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_pointer_interface pointer_impl = { + .set_cursor = pointer_set_cursor, + .release = pointer_release, +}; + +static void pointer_handle_resource_destroy(struct wl_resource *resource) { + seat_client_destroy_pointer(resource); +} + + +bool wlr_seat_pointer_surface_has_focus(struct wlr_seat *wlr_seat, + struct wlr_surface *surface) { + return surface == wlr_seat->pointer_state.focused_surface; +} + +static void seat_pointer_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_seat_pointer_state *state = + wl_container_of(listener, state, surface_destroy); + wl_list_remove(&state->surface_destroy.link); + wl_list_init(&state->surface_destroy.link); + wlr_seat_pointer_clear_focus(state->seat); +} + +void seat_client_send_pointer_leave_raw(struct wlr_seat_client *seat_client, + struct wlr_surface *surface) { + uint32_t serial = wlr_seat_client_next_serial(seat_client); + struct wl_resource *resource; + wl_resource_for_each(resource, &seat_client->pointers) { + if (wlr_seat_client_from_pointer_resource(resource) == NULL) { + continue; + } + + wl_pointer_send_leave(resource, serial, surface->resource); + pointer_send_frame(resource); + } +} + +void wlr_seat_pointer_enter(struct wlr_seat *wlr_seat, + struct wlr_surface *surface, double sx, double sy) { + if (wlr_seat->pointer_state.focused_surface == surface) { + // this surface already got an enter notify + return; + } + + struct wlr_seat_client *client = NULL; + if (surface) { + struct wl_client *wl_client = wl_resource_get_client(surface->resource); + client = wlr_seat_client_for_wl_client(wlr_seat, wl_client); + } + + struct wlr_seat_client *focused_client = + wlr_seat->pointer_state.focused_client; + struct wlr_surface *focused_surface = + wlr_seat->pointer_state.focused_surface; + + // leave the previously entered surface + if (focused_client != NULL && focused_surface != NULL) { + seat_client_send_pointer_leave_raw(focused_client, focused_surface); + } + + // enter the current surface + if (client != NULL && surface != NULL) { + uint32_t serial = wlr_seat_client_next_serial(client); + struct wl_resource *resource; + wl_resource_for_each(resource, &client->pointers) { + if (wlr_seat_client_from_pointer_resource(resource) == NULL) { + continue; + } + + wl_pointer_send_enter(resource, serial, surface->resource, + wl_fixed_from_double(sx), wl_fixed_from_double(sy)); + pointer_send_frame(resource); + } + } + + // reinitialize the focus destroy events + wl_list_remove(&wlr_seat->pointer_state.surface_destroy.link); + wl_list_init(&wlr_seat->pointer_state.surface_destroy.link); + if (surface != NULL) { + wl_signal_add(&surface->events.destroy, + &wlr_seat->pointer_state.surface_destroy); + wlr_seat->pointer_state.surface_destroy.notify = + seat_pointer_handle_surface_destroy; + } + + wlr_seat->pointer_state.focused_client = client; + wlr_seat->pointer_state.focused_surface = surface; + if (surface != NULL) { + wlr_seat_pointer_warp(wlr_seat, sx, sy); + } else { + wlr_seat_pointer_warp(wlr_seat, NAN, NAN); + } + + struct wlr_seat_pointer_focus_change_event event = { + .seat = wlr_seat, + .new_surface = surface, + .old_surface = focused_surface, + .sx = sx, + .sy = sy, + }; + wl_signal_emit_mutable(&wlr_seat->pointer_state.events.focus_change, &event); +} + +void wlr_seat_pointer_clear_focus(struct wlr_seat *wlr_seat) { + wlr_seat_pointer_enter(wlr_seat, NULL, 0, 0); +} + +void wlr_seat_pointer_warp(struct wlr_seat *wlr_seat, double sx, double sy) { + wlr_seat->pointer_state.sx = sx; + wlr_seat->pointer_state.sy = sy; +} + +void wlr_seat_pointer_send_motion(struct wlr_seat *wlr_seat, uint32_t time, + double sx, double sy) { + struct wlr_seat_client *client = wlr_seat->pointer_state.focused_client; + if (client == NULL) { + return; + } + + // Ensure we don't send duplicate motion events. Instead of comparing with an + // epsilon, chop off some precision by converting to a `wl_fixed_t` first, + // since that is what a client receives. + wl_fixed_t sx_fixed = wl_fixed_from_double(sx); + wl_fixed_t sy_fixed = wl_fixed_from_double(sy); + if (wl_fixed_from_double(wlr_seat->pointer_state.sx) != sx_fixed || + wl_fixed_from_double(wlr_seat->pointer_state.sy) != sy_fixed) { + struct wl_resource *resource; + wl_resource_for_each(resource, &client->pointers) { + if (wlr_seat_client_from_pointer_resource(resource) == NULL) { + continue; + } + + wl_pointer_send_motion(resource, time, sx_fixed, sy_fixed); + } + } + + wlr_seat_pointer_warp(wlr_seat, sx, sy); +} + +uint32_t wlr_seat_pointer_send_button(struct wlr_seat *wlr_seat, uint32_t time, + uint32_t button, enum wl_pointer_button_state state) { + struct wlr_seat_client *client = wlr_seat->pointer_state.focused_client; + if (client == NULL) { + return 0; + } + + uint32_t serial = wlr_seat_client_next_serial(client); + struct wl_resource *resource; + wl_resource_for_each(resource, &client->pointers) { + if (wlr_seat_client_from_pointer_resource(resource) == NULL) { + continue; + } + + wl_pointer_send_button(resource, serial, time, button, state); + } + return serial; +} + +static bool should_reset_value120_accumulators(int32_t current, int32_t last) { + if (last == 0) { + return true; + } + + return (current < 0 && last > 0) || (current > 0 && last < 0); +} + +static void update_value120_accumulators(struct wlr_seat_client *client, + enum wl_pointer_axis orientation, + double value, int32_t value_discrete, + double *low_res_value, int32_t *low_res_value_discrete) { + if (value_discrete == 0) { + // Continuous scrolling has no effect on accumulators + return; + } + + int32_t *acc_discrete = &client->value120.acc_discrete[orientation]; + int32_t *last_discrete = &client->value120.last_discrete[orientation]; + double *acc_axis = &client->value120.acc_axis[orientation]; + + if (should_reset_value120_accumulators(value_discrete, *last_discrete)) { + *acc_discrete = 0; + *acc_axis = 0; + } + *acc_discrete += value_discrete; + *last_discrete = value_discrete; + *acc_axis += value; + + // Compute low resolution event values for older clients and reset + // the accumulators if needed + *low_res_value_discrete = *acc_discrete / WLR_POINTER_AXIS_DISCRETE_STEP; + if (*low_res_value_discrete == 0) { + *low_res_value = 0; + } else { + *acc_discrete -= *low_res_value_discrete * WLR_POINTER_AXIS_DISCRETE_STEP; + *low_res_value = *acc_axis; + *acc_axis = 0; + } +} + +void wlr_seat_pointer_send_axis(struct wlr_seat *wlr_seat, uint32_t time, + enum wl_pointer_axis orientation, double value, + int32_t value_discrete, enum wl_pointer_axis_source source, + enum wl_pointer_axis_relative_direction relative_direction) { + struct wlr_seat_client *client = wlr_seat->pointer_state.focused_client; + if (client == NULL) { + return; + } + + bool send_source = false; + if (wlr_seat->pointer_state.sent_axis_source) { + assert(wlr_seat->pointer_state.cached_axis_source == source); + } else { + wlr_seat->pointer_state.sent_axis_source = true; + wlr_seat->pointer_state.cached_axis_source = source; + send_source = true; + } + + double low_res_value = 0.0; + int32_t low_res_value_discrete = 0; + update_value120_accumulators(client, orientation, value, value_discrete, + &low_res_value, &low_res_value_discrete); + + struct wl_resource *resource; + wl_resource_for_each(resource, &client->pointers) { + if (wlr_seat_client_from_pointer_resource(resource) == NULL) { + continue; + } + + uint32_t version = wl_resource_get_version(resource); + + if (version < WL_POINTER_AXIS_VALUE120_SINCE_VERSION && + value_discrete != 0 && low_res_value_discrete == 0) { + // The client doesn't support high resolution discrete scrolling + // and we haven't accumulated enough wheel clicks for a single + // low resolution event. Don't send anything. + continue; + } + + if (send_source && version >= WL_POINTER_AXIS_SOURCE_SINCE_VERSION) { + wl_pointer_send_axis_source(resource, source); + } + if (value) { + if (version >= WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION) { + wl_pointer_send_axis_relative_direction(resource, + orientation, relative_direction); + } + if (value_discrete) { + if (version >= WL_POINTER_AXIS_VALUE120_SINCE_VERSION) { + // High resolution discrete scrolling + wl_pointer_send_axis_value120(resource, orientation, + value_discrete); + wl_pointer_send_axis(resource, time, orientation, + wl_fixed_from_double(value)); + } else { + // Low resolution discrete scrolling + if (version >= WL_POINTER_AXIS_DISCRETE_SINCE_VERSION) { + wl_pointer_send_axis_discrete(resource, orientation, + low_res_value_discrete); + } + wl_pointer_send_axis(resource, time, orientation, + wl_fixed_from_double(low_res_value)); + } + } else { + // Continuous scrolling + wl_pointer_send_axis(resource, time, orientation, + wl_fixed_from_double(value)); + } + } else if (version >= WL_POINTER_AXIS_STOP_SINCE_VERSION) { + wl_pointer_send_axis_stop(resource, time, orientation); + } + } +} + +void wlr_seat_pointer_send_frame(struct wlr_seat *wlr_seat) { + struct wlr_seat_client *client = wlr_seat->pointer_state.focused_client; + if (client == NULL) { + return; + } + + wlr_seat->pointer_state.sent_axis_source = false; + + struct wl_resource *resource; + wl_resource_for_each(resource, &client->pointers) { + if (wlr_seat_client_from_pointer_resource(resource) == NULL) { + continue; + } + + pointer_send_frame(resource); + } +} + +void wlr_seat_pointer_start_grab(struct wlr_seat *wlr_seat, + struct wlr_seat_pointer_grab *grab) { + assert(wlr_seat); + grab->seat = wlr_seat; + wlr_seat->pointer_state.grab = grab; + + wl_signal_emit_mutable(&wlr_seat->events.pointer_grab_begin, grab); +} + +void wlr_seat_pointer_end_grab(struct wlr_seat *wlr_seat) { + struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + if (grab != wlr_seat->pointer_state.default_grab) { + wlr_seat->pointer_state.grab = wlr_seat->pointer_state.default_grab; + wl_signal_emit_mutable(&wlr_seat->events.pointer_grab_end, grab); + if (grab->interface->cancel) { + grab->interface->cancel(grab); + } + } +} + +void wlr_seat_pointer_notify_enter(struct wlr_seat *wlr_seat, + struct wlr_surface *surface, double sx, double sy) { + // NULL surfaces are prohibited in the grab-compatible API. Use + // wlr_seat_pointer_notify_clear_focus() instead. + assert(surface); + struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + grab->interface->enter(grab, surface, sx, sy); +} + +void wlr_seat_pointer_notify_clear_focus(struct wlr_seat *wlr_seat) { + struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + grab->interface->clear_focus(grab); +} + +void wlr_seat_pointer_notify_motion(struct wlr_seat *wlr_seat, uint32_t time, + double sx, double sy) { + clock_gettime(CLOCK_MONOTONIC, &wlr_seat->last_event); + struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + grab->interface->motion(grab, time, sx, sy); +} + +uint32_t wlr_seat_pointer_notify_button(struct wlr_seat *wlr_seat, + uint32_t time, uint32_t button, enum wl_pointer_button_state state) { + clock_gettime(CLOCK_MONOTONIC, &wlr_seat->last_event); + + struct wlr_seat_pointer_state* pointer_state = &wlr_seat->pointer_state; + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (pointer_state->button_count == 0) { + pointer_state->grab_button = button; + pointer_state->grab_time = time; + } + set_add(pointer_state->buttons, &pointer_state->button_count, + WLR_POINTER_BUTTONS_CAP, button); + } else { + set_remove(pointer_state->buttons, &pointer_state->button_count, + WLR_POINTER_BUTTONS_CAP, button); + } + + + struct wlr_seat_pointer_grab *grab = pointer_state->grab; + uint32_t serial = grab->interface->button(grab, time, button, state); + + if (serial && pointer_state->button_count == 1 && + state == WL_POINTER_BUTTON_STATE_PRESSED) { + pointer_state->grab_serial = serial; + } + + return serial; +} + +void wlr_seat_pointer_notify_axis(struct wlr_seat *wlr_seat, uint32_t time, + enum wl_pointer_axis orientation, double value, + int32_t value_discrete, enum wl_pointer_axis_source source, + enum wl_pointer_axis_relative_direction relative_direction) { + clock_gettime(CLOCK_MONOTONIC, &wlr_seat->last_event); + struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + grab->interface->axis(grab, time, orientation, value, value_discrete, + source, relative_direction); +} + +void wlr_seat_pointer_notify_frame(struct wlr_seat *wlr_seat) { + clock_gettime(CLOCK_MONOTONIC, &wlr_seat->last_event); + struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + if (grab->interface->frame) { + grab->interface->frame(grab); + } +} + +bool wlr_seat_pointer_has_grab(struct wlr_seat *seat) { + return seat->pointer_state.grab->interface != &default_pointer_grab_impl; +} + +void seat_client_create_pointer(struct wlr_seat_client *seat_client, + uint32_t version, uint32_t id) { + struct wl_resource *resource = wl_resource_create(seat_client->client, + &wl_pointer_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(seat_client->client); + return; + } + wl_resource_set_implementation(resource, &pointer_impl, seat_client, + &pointer_handle_resource_destroy); + wl_list_insert(&seat_client->pointers, wl_resource_get_link(resource)); + + if ((seat_client->seat->capabilities & WL_SEAT_CAPABILITY_POINTER) == 0) { + wl_resource_set_user_data(resource, NULL); + return; + } + + struct wlr_seat_client *focused_client = + seat_client->seat->pointer_state.focused_client; + struct wlr_surface *focused_surface = + seat_client->seat->pointer_state.focused_surface; + + // Send an enter event if there is a focused client/surface stored + if (focused_client == seat_client && focused_surface != NULL) { + double sx = seat_client->seat->pointer_state.sx; + double sy = seat_client->seat->pointer_state.sy; + + uint32_t serial = wlr_seat_client_next_serial(focused_client); + struct wl_resource *resource; + wl_resource_for_each(resource, &focused_client->pointers) { + if (wl_resource_get_id(resource) == id) { + if (wlr_seat_client_from_pointer_resource(resource) == NULL) { + continue; + } + + wl_pointer_send_enter(resource, serial, focused_surface->resource, + wl_fixed_from_double(sx), wl_fixed_from_double(sy)); + pointer_send_frame(resource); + } + } + } +} + +void seat_client_create_inert_pointer(struct wl_client *client, + uint32_t version, uint32_t id) { + struct wl_resource *resource = + wl_resource_create(client, &wl_pointer_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &pointer_impl, NULL, NULL); +} + +void seat_client_destroy_pointer(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + wl_resource_set_user_data(resource, NULL); +} + +bool wlr_seat_validate_pointer_grab_serial(struct wlr_seat *seat, + struct wlr_surface *origin, uint32_t serial) { + if (seat->pointer_state.button_count != 1 || + seat->pointer_state.grab_serial != serial) { + wlr_log(WLR_DEBUG, "Pointer grab serial validation failed: " + "button_count=%zu grab_serial=%"PRIu32" (got %"PRIu32")", + seat->pointer_state.button_count, + seat->pointer_state.grab_serial, serial); + return false; + } + + if (origin != NULL && seat->pointer_state.focused_surface != origin) { + wlr_log(WLR_DEBUG, "Pointer grab serial validation failed: " + "invalid origin surface"); + return false; + } + + return true; +} diff --git a/types/seat/wlr_seat_touch.c b/types/seat/wlr_seat_touch.c new file mode 100644 index 0000000..5a14000 --- /dev/null +++ b/types/seat/wlr_seat_touch.c @@ -0,0 +1,495 @@ +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_seat.h" + +static uint32_t default_touch_down(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + return wlr_seat_touch_send_down(grab->seat, point->surface, time, + point->touch_id, point->sx, point->sy); +} + +static void default_touch_up(struct wlr_seat_touch_grab *grab, uint32_t time, + struct wlr_touch_point *point) { + wlr_seat_touch_send_up(grab->seat, time, point->touch_id); +} + +static void default_touch_motion(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + if (!point->focus_surface || point->focus_surface == point->surface) { + wlr_seat_touch_send_motion(grab->seat, time, point->touch_id, point->sx, + point->sy); + } +} + +static void default_touch_enter(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + // not handled by default +} + +static void default_touch_frame(struct wlr_seat_touch_grab *grab) { + wlr_seat_touch_send_frame(grab->seat); +} + +static void default_touch_cancel(struct wlr_seat_touch_grab *grab) { + // cannot be cancelled +} + +static void default_touch_wl_cancel(struct wlr_seat_touch_grab *grab, + struct wlr_surface *surface) { + wlr_seat_touch_send_cancel(grab->seat, surface); +} + +const struct wlr_touch_grab_interface default_touch_grab_impl = { + .down = default_touch_down, + .up = default_touch_up, + .motion = default_touch_motion, + .enter = default_touch_enter, + .frame = default_touch_frame, + .cancel = default_touch_cancel, + .wl_cancel = default_touch_wl_cancel, +}; + + +static void touch_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_touch_interface touch_impl = { + .release = touch_release, +}; + +static void touch_handle_resource_destroy(struct wl_resource *resource) { + seat_client_destroy_touch(resource); +} + +static struct wlr_seat_client *seat_client_from_touch_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_touch_interface, + &touch_impl)); + return wl_resource_get_user_data(resource); +} + + +void wlr_seat_touch_start_grab(struct wlr_seat *wlr_seat, + struct wlr_seat_touch_grab *grab) { + grab->seat = wlr_seat; + wlr_seat->touch_state.grab = grab; + + wl_signal_emit_mutable(&wlr_seat->events.touch_grab_begin, grab); +} + +void wlr_seat_touch_end_grab(struct wlr_seat *wlr_seat) { + struct wlr_seat_touch_grab *grab = wlr_seat->touch_state.grab; + + if (grab != wlr_seat->touch_state.default_grab) { + wlr_seat->touch_state.grab = wlr_seat->touch_state.default_grab; + wl_signal_emit_mutable(&wlr_seat->events.touch_grab_end, grab); + if (grab->interface->cancel) { + grab->interface->cancel(grab); + } + } +} + +static void touch_point_clear_focus(struct wlr_touch_point *point) { + if (point->focus_surface) { + wl_list_remove(&point->focus_surface_destroy.link); + point->focus_client = NULL; + point->focus_surface = NULL; + } +} + +static void touch_point_destroy(struct wlr_touch_point *point) { + wl_signal_emit_mutable(&point->events.destroy, point); + + touch_point_clear_focus(point); + wl_list_remove(&point->surface_destroy.link); + wl_list_remove(&point->client_destroy.link); + wl_list_remove(&point->link); + free(point); +} + +static void touch_point_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_touch_point *point = + wl_container_of(listener, point, surface_destroy); + // Touch point itself is destroyed on up event + point->surface = NULL; + wl_list_remove(&point->surface_destroy.link); + wl_list_init(&point->surface_destroy.link); +} + +static void touch_point_handle_client_destroy(struct wl_listener *listener, + void *data) { + struct wlr_touch_point *point = + wl_container_of(listener, point, client_destroy); + touch_point_destroy(point); +} + +static struct wlr_touch_point *touch_point_create( + struct wlr_seat *seat, int32_t touch_id, + struct wlr_surface *surface, double sx, double sy) { + struct wl_client *wl_client = wl_resource_get_client(surface->resource); + struct wlr_seat_client *client = + wlr_seat_client_for_wl_client(seat, wl_client); + + if (client == NULL || wl_list_empty(&client->touches)) { + // touch points are not valid without a connected client with touch + return NULL; + } + + struct wlr_touch_point *point = calloc(1, sizeof(*point)); + if (!point) { + return NULL; + } + + point->touch_id = touch_id; + point->surface = surface; + point->client = client; + + point->sx = sx; + point->sy = sy; + + wl_signal_init(&point->events.destroy); + + wl_signal_add(&surface->events.destroy, &point->surface_destroy); + point->surface_destroy.notify = touch_point_handle_surface_destroy; + wl_signal_add(&client->events.destroy, &point->client_destroy); + point->client_destroy.notify = touch_point_handle_client_destroy; + wl_list_insert(&seat->touch_state.touch_points, &point->link); + + return point; +} + +struct wlr_touch_point *wlr_seat_touch_get_point( + struct wlr_seat *seat, int32_t touch_id) { + struct wlr_touch_point *point = NULL; + wl_list_for_each(point, &seat->touch_state.touch_points, link) { + if (point->touch_id == touch_id) { + return point; + } + } + + return NULL; +} + +uint32_t wlr_seat_touch_notify_down(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t time, int32_t touch_id, double sx, + double sy) { + clock_gettime(CLOCK_MONOTONIC, &seat->last_event); + struct wlr_seat_touch_grab *grab = seat->touch_state.grab; + struct wlr_touch_point *point = + touch_point_create(seat, touch_id, surface, sx, sy); + if (!point) { + wlr_log(WLR_ERROR, "could not create touch point"); + return 0; + } + + uint32_t serial = grab->interface->down(grab, time, point); + + if (!serial) { + touch_point_destroy(point); + return 0; + } + + if (serial && wlr_seat_touch_num_points(seat) == 1) { + seat->touch_state.grab_serial = serial; + seat->touch_state.grab_id = touch_id; + } + + return serial; +} + +void wlr_seat_touch_notify_up(struct wlr_seat *seat, uint32_t time, + int32_t touch_id) { + clock_gettime(CLOCK_MONOTONIC, &seat->last_event); + struct wlr_seat_touch_grab *grab = seat->touch_state.grab; + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + return; + } + + grab->interface->up(grab, time, point); + + touch_point_destroy(point); +} + +void wlr_seat_touch_notify_motion(struct wlr_seat *seat, uint32_t time, + int32_t touch_id, double sx, double sy) { + clock_gettime(CLOCK_MONOTONIC, &seat->last_event); + struct wlr_seat_touch_grab *grab = seat->touch_state.grab; + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + return; + } + + point->sx = sx; + point->sy = sy; + + grab->interface->motion(grab, time, point); +} + +void wlr_seat_touch_notify_frame(struct wlr_seat *seat) { + struct wlr_seat_touch_grab *grab = seat->touch_state.grab; + if (grab->interface->frame) { + grab->interface->frame(grab); + } +} + +void wlr_seat_touch_notify_cancel(struct wlr_seat *seat, + struct wlr_surface *surface) { + struct wlr_seat_touch_grab *grab = seat->touch_state.grab; + if (grab->interface->wl_cancel) { + grab->interface->wl_cancel(grab, surface); + } + + struct wl_client *client = wl_resource_get_client(surface->resource); + struct wlr_seat_client *seat_client = wlr_seat_client_for_wl_client(seat, client); + if (seat_client == NULL) { + return; + } + struct wlr_touch_point *point, *tmp; + wl_list_for_each_safe(point, tmp, &seat->touch_state.touch_points, link) { + if (point->client == seat_client) { + touch_point_destroy(point); + } + } +} + +static void handle_point_focus_destroy(struct wl_listener *listener, + void *data) { + struct wlr_touch_point *point = + wl_container_of(listener, point, focus_surface_destroy); + touch_point_clear_focus(point); +} + +static void touch_point_set_focus(struct wlr_touch_point *point, + struct wlr_surface *surface, double sx, double sy) { + if (point->focus_surface == surface) { + return; + } + + touch_point_clear_focus(point); + + if (surface && surface->resource) { + struct wlr_seat_client *client = + wlr_seat_client_for_wl_client(point->client->seat, + wl_resource_get_client(surface->resource)); + + if (client && !wl_list_empty(&client->touches)) { + wl_signal_add(&surface->events.destroy, &point->focus_surface_destroy); + point->focus_surface_destroy.notify = handle_point_focus_destroy; + point->focus_surface = surface; + point->focus_client = client; + point->sx = sx; + point->sy = sy; + } + } +} + +void wlr_seat_touch_point_focus(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t time, int32_t touch_id, double sx, + double sy) { + assert(surface); + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + wlr_log(WLR_ERROR, "got touch point focus for unknown touch point"); + return; + } + struct wlr_surface *focus = point->focus_surface; + touch_point_set_focus(point, surface, sx, sy); + + if (focus != point->focus_surface) { + struct wlr_seat_touch_grab *grab = seat->touch_state.grab; + grab->interface->enter(grab, time, point); + } +} + +void wlr_seat_touch_point_clear_focus(struct wlr_seat *seat, uint32_t time, + int32_t touch_id) { + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + wlr_log(WLR_ERROR, "got touch point focus for unknown touch point"); + return; + } + + touch_point_clear_focus(point); +} + +uint32_t wlr_seat_touch_send_down(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t time, int32_t touch_id, double sx, + double sy) { + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + wlr_log(WLR_ERROR, "got touch down for unknown touch point"); + return 0; + } + + uint32_t serial = wlr_seat_client_next_serial(point->client); + struct wl_resource *resource; + wl_resource_for_each(resource, &point->client->touches) { + if (seat_client_from_touch_resource(resource) == NULL) { + continue; + } + wl_touch_send_down(resource, serial, time, surface->resource, + touch_id, wl_fixed_from_double(sx), wl_fixed_from_double(sy)); + } + + point->client->needs_touch_frame = true; + + return serial; +} + +void wlr_seat_touch_send_up(struct wlr_seat *seat, uint32_t time, int32_t touch_id) { + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + wlr_log(WLR_ERROR, "got touch up for unknown touch point"); + return; + } + + uint32_t serial = wlr_seat_client_next_serial(point->client); + struct wl_resource *resource; + wl_resource_for_each(resource, &point->client->touches) { + if (seat_client_from_touch_resource(resource) == NULL) { + continue; + } + wl_touch_send_up(resource, serial, time, touch_id); + } + + point->client->needs_touch_frame = true; +} + +void wlr_seat_touch_send_motion(struct wlr_seat *seat, uint32_t time, int32_t touch_id, + double sx, double sy) { + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + wlr_log(WLR_ERROR, "got touch motion for unknown touch point"); + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &point->client->touches) { + if (seat_client_from_touch_resource(resource) == NULL) { + continue; + } + wl_touch_send_motion(resource, time, touch_id, wl_fixed_from_double(sx), + wl_fixed_from_double(sy)); + } + + point->client->needs_touch_frame = true; +} + +void wlr_seat_touch_send_frame(struct wlr_seat *seat) { + struct wlr_seat_client *seat_client; + wl_list_for_each(seat_client, &seat->clients, link) { + if (!seat_client->needs_touch_frame) { + continue; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &seat_client->touches) { + wl_touch_send_frame(resource); + } + seat_client->needs_touch_frame = false; + } +} + +void wlr_seat_touch_send_cancel(struct wlr_seat *seat, struct wlr_surface *surface) { + struct wl_client *client = wl_resource_get_client(surface->resource); + struct wlr_seat_client *seat_client = wlr_seat_client_for_wl_client(seat, client); + if (seat_client == NULL) { + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &seat_client->touches) { + if (seat_client_from_touch_resource(resource) == NULL) { + continue; + } + wl_touch_send_cancel(resource); + } +} + +int wlr_seat_touch_num_points(struct wlr_seat *seat) { + return wl_list_length(&seat->touch_state.touch_points); +} + +bool wlr_seat_touch_has_grab(struct wlr_seat *seat) { + return seat->touch_state.grab->interface != &default_touch_grab_impl; +} + + +void seat_client_create_touch(struct wlr_seat_client *seat_client, + uint32_t version, uint32_t id) { + struct wl_resource *resource = wl_resource_create(seat_client->client, + &wl_touch_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(seat_client->client); + return; + } + wl_resource_set_implementation(resource, &touch_impl, seat_client, + &touch_handle_resource_destroy); + wl_list_insert(&seat_client->touches, wl_resource_get_link(resource)); + + if ((seat_client->seat->capabilities & WL_SEAT_CAPABILITY_TOUCH) == 0) { + wl_resource_set_user_data(resource, NULL); + } +} + +void seat_client_create_inert_touch(struct wl_client *client, uint32_t version, + uint32_t id) { + struct wl_resource *resource = + wl_resource_create(client, &wl_touch_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &touch_impl, NULL, NULL); +} + +void seat_client_destroy_touch(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + wl_resource_set_user_data(resource, NULL); +} + +bool wlr_seat_validate_touch_grab_serial(struct wlr_seat *seat, + struct wlr_surface *origin, uint32_t serial, + struct wlr_touch_point **point_ptr) { + if (wlr_seat_touch_num_points(seat) != 1 || + seat->touch_state.grab_serial != serial) { + wlr_log(WLR_DEBUG, "Touch grab serial validation failed: " + "num_points=%d grab_serial=%"PRIu32" (got %"PRIu32")", + wlr_seat_touch_num_points(seat), + seat->touch_state.grab_serial, serial); + return false; + } + + struct wlr_touch_point *point; + wl_list_for_each(point, &seat->touch_state.touch_points, link) { + if (origin == NULL || point->surface == origin) { + if (point_ptr != NULL) { + *point_ptr = point; + } + return true; + } + } + + wlr_log(WLR_DEBUG, "Touch grab serial validation failed: " + "invalid origin surface"); + return false; +} + +bool wlr_surface_accepts_touch(struct wlr_seat *wlr_seat, struct wlr_surface *surface) { + struct wl_client *client = wl_resource_get_client(surface->resource); + struct wlr_seat_client *seat_client = wlr_seat_client_for_wl_client(wlr_seat, client); + if (!seat_client) { + return false; + } + return !wl_list_empty(&seat_client->touches); +} diff --git a/types/tablet_v2/wlr_tablet_v2.c b/types/tablet_v2/wlr_tablet_v2.c new file mode 100644 index 0000000..3182eff --- /dev/null +++ b/types/tablet_v2/wlr_tablet_v2.c @@ -0,0 +1,300 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tablet-unstable-v2-protocol.h" + +#define TABLET_MANAGER_VERSION 1 + +struct wlr_tablet_manager_client_v2 { + struct wl_list link; + struct wl_client *client; + struct wl_resource *resource; + struct wlr_tablet_manager_v2 *manager; + + struct wl_list tablet_seats; // wlr_tablet_seat_client_v2.link +}; + +static void tablet_seat_destroy(struct wlr_tablet_seat_v2 *seat) { + struct wlr_tablet_seat_client_v2 *client, *client_tmp; + wl_list_for_each_safe(client, client_tmp, &seat->clients, seat_link) { + tablet_seat_client_v2_destroy(client->resource); + } + + wl_list_remove(&seat->link); + wl_list_remove(&seat->seat_destroy.link); + free(seat); +} + +static void handle_wlr_seat_destroy(struct wl_listener *listener, void *data) { + struct wlr_tablet_seat_v2 *seat = + wl_container_of(listener, seat, seat_destroy); + tablet_seat_destroy(seat); +} + +static struct wlr_tablet_seat_v2 *create_tablet_seat( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat) { + struct wlr_tablet_seat_v2 *tablet_seat = calloc(1, sizeof(*tablet_seat)); + if (!tablet_seat) { + return NULL; + } + + tablet_seat->manager = manager; + tablet_seat->wlr_seat = wlr_seat; + + wl_list_init(&tablet_seat->clients); + + wl_list_init(&tablet_seat->tablets); + wl_list_init(&tablet_seat->tools); + wl_list_init(&tablet_seat->pads); + + tablet_seat->seat_destroy.notify = handle_wlr_seat_destroy; + wl_signal_add(&wlr_seat->events.destroy, &tablet_seat->seat_destroy); + + wl_list_insert(&manager->seats, &tablet_seat->link); + return tablet_seat; +} + +struct wlr_tablet_seat_v2 *get_or_create_tablet_seat( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat) { + struct wlr_tablet_seat_v2 *pos; + wl_list_for_each(pos, &manager->seats, link) { + if (pos->wlr_seat == wlr_seat) { + return pos; + } + } + + return create_tablet_seat(manager, wlr_seat); +} + +static void tablet_seat_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_tablet_seat_v2_interface seat_impl = { + .destroy = tablet_seat_handle_destroy, +}; + +struct wlr_tablet_seat_client_v2 *tablet_seat_client_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_tablet_seat_v2_interface, + &seat_impl)); + return wl_resource_get_user_data(resource); +} + +void tablet_seat_client_v2_destroy(struct wl_resource *resource) { + struct wlr_tablet_seat_client_v2 *seat = tablet_seat_client_from_resource(resource); + if (!seat) { + return; + } + + struct wlr_tablet_client_v2 *tablet; + struct wlr_tablet_client_v2 *tmp_tablet; + wl_list_for_each_safe(tablet, tmp_tablet, &seat->tablets, seat_link) { + destroy_tablet_v2(tablet->resource); + } + + struct wlr_tablet_pad_client_v2 *pad; + struct wlr_tablet_pad_client_v2 *tmp_pad; + wl_list_for_each_safe(pad, tmp_pad, &seat->pads, seat_link) { + destroy_tablet_pad_v2(pad->resource); + } + + struct wlr_tablet_tool_client_v2 *tool; + struct wlr_tablet_tool_client_v2 *tmp_tool; + wl_list_for_each_safe(tool, tmp_tool, &seat->tools, seat_link) { + destroy_tablet_tool_v2(tool->resource); + } + + wl_list_remove(&seat->seat_link); + wl_list_remove(&seat->client_link); + wl_list_remove(&seat->seat_client_destroy.link); + + free(seat); + wl_resource_set_user_data(resource, NULL); +} + +static void handle_seat_client_destroy(struct wl_listener *listener, void *data) { + struct wlr_tablet_seat_client_v2 *seat = + wl_container_of(listener, seat, seat_client_destroy); + tablet_seat_client_v2_destroy(seat->resource); +} + +static void tablet_manager_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct wlr_tablet_manager_client_v2 *tablet_manager_client_from_resource( + struct wl_resource *resource); + +static void get_tablet_seat(struct wl_client *wl_client, struct wl_resource *resource, + uint32_t id, struct wl_resource *seat_resource) { + struct wlr_tablet_manager_client_v2 *manager = + tablet_manager_client_from_resource(resource); + if (!manager) { + /* Inert manager, just set up the resource for later + * destruction, without allocations or advertising things + */ + wl_resource_set_implementation(seat_resource, &seat_impl, NULL, + tablet_seat_client_v2_destroy); + return; + } + struct wl_resource *tablet_seat_resource = wl_resource_create(wl_client, + &zwp_tablet_seat_v2_interface, TABLET_MANAGER_VERSION, id); + if (tablet_seat_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(tablet_seat_resource, &seat_impl, NULL, + tablet_seat_client_v2_destroy); + + struct wlr_seat_client *seat = wlr_seat_client_from_resource(seat_resource); + if (seat == NULL) { + return; + } + struct wlr_tablet_seat_v2 *tablet_seat = + get_or_create_tablet_seat(manager->manager, seat->seat); + + if (!tablet_seat) { // This can only happen when we ran out of memory + wl_client_post_no_memory(wl_client); + return; + } + + struct wlr_tablet_seat_client_v2 *seat_client = calloc(1, sizeof(*seat_client)); + if (seat_client == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + seat_client->resource = tablet_seat_resource; + seat_client->seat_client = seat; + seat_client->client = manager; + seat_client->wl_client = wl_client; + wl_list_init(&seat_client->tools); + wl_list_init(&seat_client->tablets); + wl_list_init(&seat_client->pads); + wl_resource_set_user_data(tablet_seat_resource, seat_client); + + seat_client->seat_client_destroy.notify = handle_seat_client_destroy; + wl_signal_add(&seat->events.destroy, &seat_client->seat_client_destroy); + + wl_list_insert(&manager->tablet_seats, &seat_client->client_link); + wl_list_insert(&tablet_seat->clients, &seat_client->seat_link); + + // We need to emit the devices already on the seat + struct wlr_tablet_v2_tablet *tablet_pos; + wl_list_for_each(tablet_pos, &tablet_seat->tablets, link) { + add_tablet_client(seat_client, tablet_pos); + } + + struct wlr_tablet_v2_tablet_pad *pad_pos; + wl_list_for_each(pad_pos, &tablet_seat->pads, link) { + add_tablet_pad_client(seat_client, pad_pos); + } + + struct wlr_tablet_v2_tablet_tool *tool_pos; + wl_list_for_each(tool_pos, &tablet_seat->tools, link) { + add_tablet_tool_client(seat_client, tool_pos); + } +} + +static const struct zwp_tablet_manager_v2_interface manager_impl = { + .get_tablet_seat = get_tablet_seat, + .destroy = tablet_manager_destroy, +}; + +static struct wlr_tablet_manager_client_v2 *tablet_manager_client_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_tablet_manager_v2_interface, + &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void wlr_tablet_manager_v2_destroy(struct wl_resource *resource) { + struct wlr_tablet_manager_client_v2 *client = + tablet_manager_client_from_resource(resource); + if (!client) { + return; + } + + struct wlr_tablet_seat_client_v2 *pos; + struct wlr_tablet_seat_client_v2 *tmp; + wl_list_for_each_safe(pos, tmp, &client->tablet_seats, client_link) { + tablet_seat_client_v2_destroy(pos->resource); + } + + wl_list_remove(&client->link); + + free(client); + wl_resource_set_user_data(resource, NULL); +} + +static void tablet_v2_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_tablet_manager_v2 *manager = data; + + struct wlr_tablet_manager_client_v2 *client = calloc(1, sizeof(*client)); + if (client == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_list_init(&client->tablet_seats); + + client->resource = + wl_resource_create(wl_client, &zwp_tablet_manager_v2_interface, version, id); + if (client->resource == NULL) { + free(client); + wl_client_post_no_memory(wl_client); + return; + } + client->client = wl_client; + client->manager = manager; + + wl_resource_set_implementation(client->resource, &manager_impl, client, + wlr_tablet_manager_v2_destroy); + wl_list_insert(&manager->clients, &client->link); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_tablet_manager_v2 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->wl_global); + free(manager); +} + +struct wlr_tablet_manager_v2 *wlr_tablet_v2_create(struct wl_display *display) { + struct wlr_tablet_manager_v2 *tablet = calloc(1, sizeof(*tablet)); + if (!tablet) { + return NULL; + } + + tablet->wl_global = wl_global_create(display, + &zwp_tablet_manager_v2_interface, TABLET_MANAGER_VERSION, + tablet, tablet_v2_bind); + if (tablet->wl_global == NULL) { + free(tablet); + return NULL; + } + + wl_signal_init(&tablet->events.destroy); + wl_list_init(&tablet->clients); + wl_list_init(&tablet->seats); + + tablet->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &tablet->display_destroy); + + return tablet; +} diff --git a/types/tablet_v2/wlr_tablet_v2_pad.c b/types/tablet_v2/wlr_tablet_v2_pad.c new file mode 100644 index 0000000..76208ca --- /dev/null +++ b/types/tablet_v2/wlr_tablet_v2_pad.c @@ -0,0 +1,703 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tablet-unstable-v2-protocol.h" + +static const struct wlr_tablet_pad_v2_grab_interface default_pad_grab_interface; + +struct tablet_pad_auxiliary_user_data { + struct wlr_tablet_pad_client_v2 *pad; + size_t index; +}; + +static void handle_tablet_pad_v2_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void destroy_tablet_pad_ring_v2(struct wl_resource *resource) { + struct tablet_pad_auxiliary_user_data *aux = wl_resource_get_user_data(resource); + + if (!aux) { + return; + } + + aux->pad->rings[aux->index] = NULL; + free(aux); + wl_resource_set_user_data(resource, NULL); +} + +static void handle_tablet_pad_ring_v2_set_feedback(struct wl_client *client, + struct wl_resource *resource, const char *description, + uint32_t serial) { + struct tablet_pad_auxiliary_user_data *aux = wl_resource_get_user_data(resource); + if (!aux) { + return; + } + + struct wlr_tablet_v2_event_feedback evt = { + .serial = serial, + .description = description, + .index = aux->index + }; + + wl_signal_emit_mutable(&aux->pad->pad->events.ring_feedback, &evt); +} + +static void handle_tablet_pad_ring_v2_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_tablet_pad_ring_v2_interface tablet_pad_ring_impl = { + .set_feedback = handle_tablet_pad_ring_v2_set_feedback, + .destroy = handle_tablet_pad_ring_v2_destroy, +}; + +static void destroy_tablet_pad_strip_v2(struct wl_resource *resource) { + struct tablet_pad_auxiliary_user_data *aux = wl_resource_get_user_data(resource); + if (!aux) { + return; + } + + aux->pad->strips[aux->index] = NULL; + free(aux); + wl_resource_set_user_data(resource, NULL); +} + +static void handle_tablet_pad_strip_v2_set_feedback(struct wl_client *client, + struct wl_resource *resource, const char *description, + uint32_t serial) { + struct tablet_pad_auxiliary_user_data *aux = wl_resource_get_user_data(resource); + if (!aux) { + return; + } + + struct wlr_tablet_v2_event_feedback evt = { + .serial = serial, + .description = description, + .index = aux->index + }; + + wl_signal_emit_mutable(&aux->pad->pad->events.strip_feedback, &evt); +} + +static void handle_tablet_pad_strip_v2_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_tablet_pad_strip_v2_interface tablet_pad_strip_impl = { + .set_feedback = handle_tablet_pad_strip_v2_set_feedback, + .destroy = handle_tablet_pad_strip_v2_destroy, +}; + +static void handle_tablet_pad_v2_set_feedback( struct wl_client *client, + struct wl_resource *resource, uint32_t button, + const char *description, uint32_t serial) { + struct wlr_tablet_pad_client_v2 *pad = tablet_pad_client_from_resource(resource); + if (!pad) { + return; + } + + struct wlr_tablet_v2_event_feedback evt = { + .serial = serial, + .index = button, + .description = description, + }; + + wl_signal_emit_mutable(&pad->pad->events.button_feedback, &evt); +} + +static const struct zwp_tablet_pad_v2_interface tablet_pad_impl = { + .set_feedback = handle_tablet_pad_v2_set_feedback, + .destroy = handle_tablet_pad_v2_destroy, +}; + +static void destroy_tablet_pad_group_v2(struct wl_resource *resource) { + struct tablet_pad_auxiliary_user_data *aux = wl_resource_get_user_data(resource); + + if (!aux) { + return; + } + + aux->pad->groups[aux->index] = NULL; + free(aux); + wl_resource_set_user_data(resource, NULL); +} + +void destroy_tablet_pad_v2(struct wl_resource *resource) { + struct wlr_tablet_pad_client_v2 *pad = + tablet_pad_client_from_resource(resource); + + if (!pad) { + return; + } + + wl_list_remove(&pad->seat_link); + wl_list_remove(&pad->pad_link); + + /* This isn't optimal, if the client destroys the resources in another + * order, it will be disconnected. + * But this makes things *way* easier for us, and (untested) I doubt + * clients will destroy it in another order. + */ + for (size_t i = 0; i < pad->group_count; ++i) { + if (pad->groups[i]) { + destroy_tablet_pad_group_v2(pad->groups[i]); + } + } + free(pad->groups); + + for (size_t i = 0; i < pad->ring_count; ++i) { + if (pad->rings[i]) { + destroy_tablet_pad_ring_v2(pad->rings[i]); + } + } + free(pad->rings); + + for (size_t i = 0; i < pad->strip_count; ++i) { + if (pad->strips[i]) { + destroy_tablet_pad_strip_v2(pad->strips[i]); + } + } + free(pad->strips); + + if (pad->pad->current_client == pad) { + pad->pad->current_client = NULL; + } + free(pad); + wl_resource_set_user_data(resource, NULL); +} + +static void handle_tablet_pad_group_v2_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_tablet_pad_group_v2_interface tablet_pad_group_impl = { + .destroy = handle_tablet_pad_group_v2_destroy, +}; + +static void add_tablet_pad_group(struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_tablet_pad_client_v2 *client, + struct wlr_tablet_pad_group *group, size_t index) { + + uint32_t version = wl_resource_get_version(client->resource); + client->groups[index] = wl_resource_create(client->client, + &zwp_tablet_pad_group_v2_interface, version, 0); + if (!client->groups[index]) { + wl_client_post_no_memory(client->client); + return; + } + struct tablet_pad_auxiliary_user_data *user_data = calloc(1, sizeof(*user_data)); + if (!user_data) { + wl_client_post_no_memory(client->client); + return; + } + user_data->pad = client; + user_data->index = index; + wl_resource_set_implementation(client->groups[index], &tablet_pad_group_impl, + user_data, destroy_tablet_pad_group_v2); + + zwp_tablet_pad_v2_send_group(client->resource, client->groups[index]); + zwp_tablet_pad_group_v2_send_modes(client->groups[index], group->mode_count); + + struct wl_array button_array; + wl_array_init(&button_array); + wl_array_add(&button_array, group->button_count * sizeof(int)); + memcpy(button_array.data, group->buttons, group->button_count * sizeof(int)); + zwp_tablet_pad_group_v2_send_buttons(client->groups[index], &button_array); + wl_array_release(&button_array); + + client->strip_count = group->strip_count; + for (size_t i = 0; i < group->strip_count; ++i) { + size_t strip = group->strips[i]; + struct tablet_pad_auxiliary_user_data *user_data = calloc(1, sizeof(*user_data)); + if (!user_data) { + wl_client_post_no_memory(client->client); + return; + } + user_data->pad = client; + user_data->index = strip; + client->strips[strip] = wl_resource_create(client->client, + &zwp_tablet_pad_strip_v2_interface, version, 0); + if (!client->strips[strip]) { + free(user_data); + wl_client_post_no_memory(client->client); + return; + } + wl_resource_set_implementation(client->strips[strip], + &tablet_pad_strip_impl, user_data, destroy_tablet_pad_strip_v2); + zwp_tablet_pad_group_v2_send_strip(client->groups[index], + client->strips[strip]); + } + + client->ring_count = group->ring_count; + for (size_t i = 0; i < group->ring_count; ++i) { + size_t ring = group->rings[i]; + struct tablet_pad_auxiliary_user_data *user_data = calloc(1, sizeof(*user_data)); + if (!user_data) { + wl_client_post_no_memory(client->client); + return; + } + user_data->pad = client; + user_data->index = ring; + client->rings[ring] = wl_resource_create(client->client, + &zwp_tablet_pad_ring_v2_interface, version, 0); + if (!client->rings[ring]) { + free(user_data); + wl_client_post_no_memory(client->client); + return; + } + wl_resource_set_implementation(client->rings[ring], + &tablet_pad_ring_impl, user_data, destroy_tablet_pad_ring_v2); + zwp_tablet_pad_group_v2_send_ring(client->groups[index], + client->rings[ring]); + } + + zwp_tablet_pad_group_v2_send_done(client->groups[index]); +} + +void add_tablet_pad_client(struct wlr_tablet_seat_client_v2 *seat, + struct wlr_tablet_v2_tablet_pad *pad) { + struct wlr_tablet_pad_client_v2 *client = calloc(1, sizeof(*client)); + if (!client) { + wl_client_post_no_memory(seat->wl_client); + return; + } + client->pad = pad; + client->seat = seat; + + client->groups = calloc(wl_list_length(&pad->wlr_pad->groups), sizeof(*client->groups)); + if (!client->groups) { + wl_client_post_no_memory(seat->wl_client); + free(client); + return; + } + + client->rings = calloc(pad->wlr_pad->ring_count, sizeof(*client->rings)); + if (!client->rings) { + wl_client_post_no_memory(seat->wl_client); + free(client->groups); + free(client); + return; + } + + client->strips = calloc(pad->wlr_pad->strip_count, sizeof(*client->strips)); + if (!client->strips) { + wl_client_post_no_memory(seat->wl_client); + free(client->groups); + free(client->rings); + free(client); + return; + } + + uint32_t version = wl_resource_get_version(seat->resource); + client->resource = wl_resource_create(seat->wl_client, + &zwp_tablet_pad_v2_interface, version, 0); + if (!client->resource) { + wl_client_post_no_memory(seat->wl_client); + free(client->groups); + free(client->rings); + free(client->strips); + free(client); + return; + } + wl_resource_set_implementation(client->resource, &tablet_pad_impl, + client, destroy_tablet_pad_v2); + zwp_tablet_seat_v2_send_pad_added(seat->resource, client->resource); + client->client = seat->wl_client; + + // Send the expected events + if (pad->wlr_pad->button_count) { + zwp_tablet_pad_v2_send_buttons(client->resource, pad->wlr_pad->button_count); + } + + const char **path_ptr; + wl_array_for_each(path_ptr, &pad->wlr_pad->paths) { + zwp_tablet_pad_v2_send_path(client->resource, *path_ptr); + } + + size_t i = 0; + struct wlr_tablet_pad_group *group; + client->group_count = pad->group_count; + wl_list_for_each(group, &pad->wlr_pad->groups, link) { + add_tablet_pad_group(pad, client, group, i++); + } + + zwp_tablet_pad_v2_send_done(client->resource); + + wl_list_insert(&seat->pads, &client->seat_link); + wl_list_insert(&pad->clients, &client->pad_link); +} + +static void handle_wlr_tablet_pad_destroy(struct wl_listener *listener, void *data) { + struct wlr_tablet_v2_tablet_pad *pad = + wl_container_of(listener, pad, pad_destroy); + + struct wlr_tablet_pad_client_v2 *client; + struct wlr_tablet_pad_client_v2 *tmp_client; + wl_list_for_each_safe(client, tmp_client, &pad->clients, pad_link) { + zwp_tablet_pad_v2_send_removed(client->resource); + destroy_tablet_pad_v2(client->resource); + } + + wl_list_remove(&pad->clients); + wl_list_remove(&pad->link); + wl_list_remove(&pad->pad_destroy.link); + wl_list_remove(&pad->events.button_feedback.listener_list); + wl_list_remove(&pad->events.strip_feedback.listener_list); + wl_list_remove(&pad->events.ring_feedback.listener_list); + free(pad); +} + +struct wlr_tablet_v2_tablet_pad *wlr_tablet_pad_create( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat, + struct wlr_input_device *wlr_device) { + assert(wlr_device->type == WLR_INPUT_DEVICE_TABLET_PAD); + struct wlr_tablet_seat_v2 *seat = get_or_create_tablet_seat(manager, wlr_seat); + if (!seat) { + return NULL; + } + struct wlr_tablet_pad *wlr_pad = wlr_tablet_pad_from_input_device(wlr_device); + struct wlr_tablet_v2_tablet_pad *pad = calloc(1, sizeof(*pad)); + if (!pad) { + return NULL; + } + pad->default_grab.interface = &default_pad_grab_interface; + pad->default_grab.pad = pad; + pad->grab = &pad->default_grab; + + pad->group_count = wl_list_length(&wlr_pad->groups); + pad->groups = calloc(pad->group_count, sizeof(uint32_t)); + if (!pad->groups) { + free(pad); + return NULL; + } + + pad->wlr_pad = wlr_pad; + wl_list_init(&pad->clients); + + pad->pad_destroy.notify = handle_wlr_tablet_pad_destroy; + wl_signal_add(&wlr_device->events.destroy, &pad->pad_destroy); + wl_list_insert(&seat->pads, &pad->link); + + // We need to create a tablet client for all clients on the seat + struct wlr_tablet_seat_client_v2 *pos; + wl_list_for_each(pos, &seat->clients, seat_link) { + // Tell the clients about the new tool + add_tablet_pad_client(pos, pad); + } + + wl_signal_init(&pad->events.button_feedback); + wl_signal_init(&pad->events.strip_feedback); + wl_signal_init(&pad->events.ring_feedback); + + return pad; +} + +struct wlr_tablet_pad_client_v2 *tablet_pad_client_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_tablet_pad_v2_interface, + &tablet_pad_impl)); + return wl_resource_get_user_data(resource); +} + +/* Actual protocol foo */ +uint32_t wlr_send_tablet_v2_tablet_pad_enter( + struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + struct wl_client *client = wl_resource_get_client(surface->resource); + + struct wlr_tablet_client_v2 *tablet_tmp; + struct wlr_tablet_client_v2 *tablet_client = NULL; + wl_list_for_each(tablet_tmp, &tablet->clients, tablet_link) { + if (tablet_tmp->client == client) { + tablet_client = tablet_tmp; + break; + } + } + + // Couldn't find the client binding for the surface's client. Either + // the client didn't bind tablet_v2 at all, or not for the relevant + // seat + if (!tablet_client) { + return 0; + } + + struct wlr_tablet_pad_client_v2 *pad_tmp = NULL; + struct wlr_tablet_pad_client_v2 *pad_client = NULL; + wl_list_for_each(pad_tmp, &pad->clients, pad_link) { + if (pad_tmp->client == client) { + pad_client = pad_tmp; + break; + } + } + + // Couldn't find the client binding for the surface's client. Either + // the client didn't bind tablet_v2 at all, or not for the relevant + // seat + if (!pad_client) { + return 0; + } + + pad->current_client = pad_client; + + uint32_t serial = wlr_seat_client_next_serial( + pad_client->seat->seat_client); + + zwp_tablet_pad_v2_send_enter(pad_client->resource, serial, + tablet_client->resource, surface->resource); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + uint32_t time = now.tv_nsec / 1000; + + for (size_t i = 0; i < pad->group_count; ++i) { + if (pad_client->groups[i]) { + zwp_tablet_pad_group_v2_send_mode_switch( + pad_client->groups[i], time, serial, pad->groups[i]); + } + } + + return serial; +} + +void wlr_send_tablet_v2_tablet_pad_button( + struct wlr_tablet_v2_tablet_pad *pad, size_t button, + uint32_t time, enum zwp_tablet_pad_v2_button_state state) { + + if (pad->current_client) { + zwp_tablet_pad_v2_send_button(pad->current_client->resource, + time, button, state); + } +} + +void wlr_send_tablet_v2_tablet_pad_strip(struct wlr_tablet_v2_tablet_pad *pad, + uint32_t strip, double position, bool finger, uint32_t time) { + if (!pad->current_client || + !pad->current_client->strips || + !pad->current_client->strips[strip]) { + return; + } + struct wl_resource *resource = pad->current_client->strips[strip]; + + if (finger) { + zwp_tablet_pad_strip_v2_send_source(resource, ZWP_TABLET_PAD_STRIP_V2_SOURCE_FINGER); + } + + if (position < 0) { + zwp_tablet_pad_strip_v2_send_stop(resource); + } else { + zwp_tablet_pad_strip_v2_send_position(resource, position * 65535); + } + zwp_tablet_pad_strip_v2_send_frame(resource, time); +} + +void wlr_send_tablet_v2_tablet_pad_ring(struct wlr_tablet_v2_tablet_pad *pad, + uint32_t ring, double position, bool finger, uint32_t time) { + if (!pad->current_client || + !pad->current_client->rings || + !pad->current_client->rings[ring]) { + return; + } + struct wl_resource *resource = pad->current_client->rings[ring]; + + if (finger) { + zwp_tablet_pad_ring_v2_send_source(resource, ZWP_TABLET_PAD_RING_V2_SOURCE_FINGER); + } + + if (position < 0) { + zwp_tablet_pad_ring_v2_send_stop(resource); + } else { + zwp_tablet_pad_ring_v2_send_angle(resource, wl_fixed_from_double(position)); + } + zwp_tablet_pad_ring_v2_send_frame(resource, time); +} + +uint32_t wlr_send_tablet_v2_tablet_pad_leave(struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_surface *surface) { + struct wl_client *client = wl_resource_get_client(surface->resource); + if (!pad->current_client || client != pad->current_client->client) { + return 0; + } + + + uint32_t serial = wlr_seat_client_next_serial( + pad->current_client->seat->seat_client); + + zwp_tablet_pad_v2_send_leave(pad->current_client->resource, serial, surface->resource); + return serial; +} + +uint32_t wlr_send_tablet_v2_tablet_pad_mode(struct wlr_tablet_v2_tablet_pad *pad, + size_t group, uint32_t mode, uint32_t time) { + if (!pad->current_client || + !pad->current_client->groups || + !pad->current_client->groups[group] ) { + return 0; + } + + if (pad->groups[group] == mode) { + return 0; + } + + pad->groups[group] = mode; + + uint32_t serial = wlr_seat_client_next_serial( + pad->current_client->seat->seat_client); + + zwp_tablet_pad_group_v2_send_mode_switch( + pad->current_client->groups[group], time, serial, mode); + return serial; +} + +bool wlr_surface_accepts_tablet_v2(struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + struct wl_client *client = wl_resource_get_client(surface->resource); + + if (tablet->current_client && + tablet->current_client->client == client) { + return true; + } + + struct wlr_tablet_client_v2 *tablet_tmp; + wl_list_for_each(tablet_tmp, &tablet->clients, tablet_link) { + if (tablet_tmp->client == client) { + return true; + } + } + + return false; +} + + +uint32_t wlr_tablet_v2_tablet_pad_notify_enter( + struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + if (pad->grab && pad->grab->interface->enter) { + return pad->grab->interface->enter(pad->grab, tablet, surface); + } + + return 0; +} + +void wlr_tablet_v2_tablet_pad_notify_button( + struct wlr_tablet_v2_tablet_pad *pad, size_t button, + uint32_t time, enum zwp_tablet_pad_v2_button_state state) { + if (pad->grab && pad->grab->interface->button) { + pad->grab->interface->button(pad->grab, button, time, state); + } +} + +void wlr_tablet_v2_tablet_pad_notify_strip( + struct wlr_tablet_v2_tablet_pad *pad, + uint32_t strip, double position, bool finger, uint32_t time) { + if (pad->grab && pad->grab->interface->strip) { + pad->grab->interface->strip(pad->grab, strip, position, finger, time); + } +} + +void wlr_tablet_v2_tablet_pad_notify_ring( + struct wlr_tablet_v2_tablet_pad *pad, + uint32_t ring, double position, bool finger, uint32_t time) { + if (pad->grab && pad->grab->interface->ring) { + pad->grab->interface->ring(pad->grab, ring, position, finger, time); + } +} + +uint32_t wlr_tablet_v2_tablet_pad_notify_leave( + struct wlr_tablet_v2_tablet_pad *pad, struct wlr_surface *surface) { + if (pad->grab && pad->grab->interface->leave) { + return pad->grab->interface->leave(pad->grab, surface); + } + + return 0; +} + +uint32_t wlr_tablet_v2_tablet_pad_notify_mode( + struct wlr_tablet_v2_tablet_pad *pad, + size_t group, uint32_t mode, uint32_t time) { + if (pad->grab && pad->grab->interface->mode) { + return pad->grab->interface->mode(pad->grab, group, mode, time); + } + + return 0; +} + +void wlr_tablet_v2_start_grab(struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_tablet_pad_v2_grab *grab) { + if (grab != &pad->default_grab) { + struct wlr_tablet_pad_v2_grab *prev = pad->grab; + grab->pad = pad; + pad->grab = grab; + if (prev && prev->interface->cancel) { + prev->interface->cancel(prev); + } + } +} + +void wlr_tablet_v2_end_grab(struct wlr_tablet_v2_tablet_pad *pad) { + struct wlr_tablet_pad_v2_grab *grab = pad->grab; + if (grab && grab != &pad->default_grab) { + pad->grab = &pad->default_grab; + if (grab->interface->cancel) { + grab->interface->cancel(grab); + } + } +} + +static uint32_t default_pad_enter( + struct wlr_tablet_pad_v2_grab *grab, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + return wlr_send_tablet_v2_tablet_pad_enter(grab->pad, tablet, surface); +} + +static void default_pad_button(struct wlr_tablet_pad_v2_grab *grab,size_t button, + uint32_t time, enum zwp_tablet_pad_v2_button_state state) { + wlr_send_tablet_v2_tablet_pad_button(grab->pad, button, time, state); +} + +static void default_pad_strip(struct wlr_tablet_pad_v2_grab *grab, + uint32_t strip, double position, bool finger, uint32_t time) { + wlr_send_tablet_v2_tablet_pad_strip(grab->pad, strip, position, finger, time); +} + +static void default_pad_ring(struct wlr_tablet_pad_v2_grab *grab, + uint32_t ring, double position, bool finger, uint32_t time) { + wlr_send_tablet_v2_tablet_pad_ring(grab->pad, ring, position, finger, time); +} + +static uint32_t default_pad_leave(struct wlr_tablet_pad_v2_grab *grab, + struct wlr_surface *surface) { + return wlr_send_tablet_v2_tablet_pad_leave(grab->pad, surface); +} + +static uint32_t default_pad_mode(struct wlr_tablet_pad_v2_grab *grab, + size_t group, uint32_t mode, uint32_t time) { + return wlr_send_tablet_v2_tablet_pad_mode(grab->pad, group, mode, time); +} + +static void default_pad_cancel(struct wlr_tablet_pad_v2_grab *grab) { + // Do nothing, the default cancel can be ignored. +} + +static const struct wlr_tablet_pad_v2_grab_interface default_pad_grab_interface = { + .enter = default_pad_enter, + .button = default_pad_button, + .strip = default_pad_strip, + .ring = default_pad_ring, + .leave = default_pad_leave, + .mode = default_pad_mode, + .cancel = default_pad_cancel, +}; diff --git a/types/tablet_v2/wlr_tablet_v2_tablet.c b/types/tablet_v2/wlr_tablet_v2_tablet.c new file mode 100644 index 0000000..81ccbbc --- /dev/null +++ b/types/tablet_v2/wlr_tablet_v2_tablet.c @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tablet-unstable-v2-protocol.h" + +void destroy_tablet_v2(struct wl_resource *resource) { + struct wlr_tablet_client_v2 *tablet = tablet_client_from_resource(resource); + + if (!tablet) { + return; + } + + wl_list_remove(&tablet->seat_link); + wl_list_remove(&tablet->tablet_link); + free(tablet); + wl_resource_set_user_data(resource, NULL); +} + +static void handle_tablet_v2_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_tablet_v2_interface tablet_impl = { + .destroy = handle_tablet_v2_destroy, +}; + +static void handle_wlr_tablet_destroy(struct wl_listener *listener, void *data) { + struct wlr_tablet_v2_tablet *tablet = + wl_container_of(listener, tablet, tablet_destroy); + + struct wlr_tablet_client_v2 *pos; + struct wlr_tablet_client_v2 *tmp; + wl_list_for_each_safe(pos, tmp, &tablet->clients, tablet_link) { + zwp_tablet_v2_send_removed(pos->resource); + } + + wl_list_remove(&tablet->clients); + wl_list_remove(&tablet->link); + wl_list_remove(&tablet->tablet_destroy.link); + free(tablet); +} + +struct wlr_tablet_v2_tablet *wlr_tablet_create( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat, + struct wlr_input_device *wlr_device) { + assert(wlr_device->type == WLR_INPUT_DEVICE_TABLET); + struct wlr_tablet_seat_v2 *seat = get_or_create_tablet_seat(manager, wlr_seat); + if (!seat) { + return NULL; + } + struct wlr_tablet *wlr_tablet = wlr_tablet_from_input_device(wlr_device); + struct wlr_tablet_v2_tablet *tablet = calloc(1, sizeof(*tablet)); + if (!tablet) { + return NULL; + } + + tablet->wlr_tablet = wlr_tablet; + tablet->wlr_device = wlr_device; + wl_list_init(&tablet->clients); + + + tablet->tablet_destroy.notify = handle_wlr_tablet_destroy; + wl_signal_add(&wlr_device->events.destroy, &tablet->tablet_destroy); + wl_list_insert(&seat->tablets, &tablet->link); + + // We need to create a tablet client for all clients on the seat + struct wlr_tablet_seat_client_v2 *pos; + wl_list_for_each(pos, &seat->clients, seat_link) { + // Tell the clients about the new tool + add_tablet_client(pos, tablet); + } + + return tablet; +} + + +void add_tablet_client(struct wlr_tablet_seat_client_v2 *seat, + struct wlr_tablet_v2_tablet *tablet) { + struct wlr_tablet_client_v2 *client = calloc(1, sizeof(*client)); + if (!client) { + return; + } + + uint32_t version = wl_resource_get_version(seat->resource); + client->resource = + wl_resource_create(seat->wl_client, &zwp_tablet_v2_interface, + version, 0); + if (!client->resource) { + wl_resource_post_no_memory(seat->resource); + free(client); + return; + } + wl_resource_set_implementation(client->resource, &tablet_impl, + client, destroy_tablet_v2); + zwp_tablet_seat_v2_send_tablet_added(seat->resource, client->resource); + + if (tablet->wlr_tablet->base.name) { + zwp_tablet_v2_send_name(client->resource, + tablet->wlr_tablet->base.name); + } + if (tablet->wlr_tablet->usb_vendor_id != 0) { + zwp_tablet_v2_send_id(client->resource, + tablet->wlr_tablet->usb_vendor_id, tablet->wlr_tablet->usb_product_id); + } + + const char **path_ptr; + wl_array_for_each(path_ptr, &tablet->wlr_tablet->paths) { + zwp_tablet_v2_send_path(client->resource, *path_ptr); + } + + zwp_tablet_v2_send_done(client->resource); + + client->client = seat->wl_client; + wl_list_insert(&seat->tablets, &client->seat_link); + wl_list_insert(&tablet->clients, &client->tablet_link); +} + + +struct wlr_tablet_client_v2 *tablet_client_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_tablet_v2_interface, + &tablet_impl)); + return wl_resource_get_user_data(resource); +} diff --git a/types/tablet_v2/wlr_tablet_v2_tool.c b/types/tablet_v2/wlr_tablet_v2_tool.c new file mode 100644 index 0000000..9700e90 --- /dev/null +++ b/types/tablet_v2/wlr_tablet_v2_tool.c @@ -0,0 +1,837 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "util/set.h" +#include "util/time.h" +#include "tablet-unstable-v2-protocol.h" + +static const struct wlr_tablet_tool_v2_grab_interface default_tool_grab_interface; + +static void tablet_tool_cursor_surface_handle_commit(struct wlr_surface *surface) { + pixman_region32_clear(&surface->input_region); + if (wlr_surface_has_buffer(surface)) { + wlr_surface_map(surface); + } +} + +static const struct wlr_surface_role tablet_tool_cursor_surface_role = { + .name = "wp_tablet_tool-cursor", + .no_object = true, + .commit = tablet_tool_cursor_surface_handle_commit, +}; + +static void handle_tablet_tool_v2_set_cursor(struct wl_client *client, + struct wl_resource *resource, uint32_t serial, + struct wl_resource *surface_resource, + int32_t hotspot_x, int32_t hotspot_y) { + struct wlr_tablet_tool_client_v2 *tool = tablet_tool_client_from_resource(resource); + if (!tool || !tool->tool) { + return; + } + + struct wlr_surface *surface = NULL; + if (surface_resource != NULL) { + surface = wlr_surface_from_resource(surface_resource); + if (!wlr_surface_set_role(surface, &tablet_tool_cursor_surface_role, + surface_resource, ZWP_TABLET_TOOL_V2_ERROR_ROLE)) { + return; + } + + tablet_tool_cursor_surface_handle_commit(surface); + } + + struct wlr_tablet_v2_event_cursor evt = { + .surface = surface, + .serial = serial, + .hotspot_x = hotspot_x, + .hotspot_y = hotspot_y, + .seat_client = tool->seat->seat_client, + }; + + wl_signal_emit_mutable(&tool->tool->events.set_cursor, &evt); +} + +static void handle_tablet_tool_v2_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} +static const struct zwp_tablet_tool_v2_interface tablet_tool_impl = { + .set_cursor = handle_tablet_tool_v2_set_cursor, + .destroy = handle_tablet_tool_v2_destroy, +}; + +static enum zwp_tablet_tool_v2_type tablet_type_from_wlr_type( + enum wlr_tablet_tool_type wlr_type) { + switch(wlr_type) { + case WLR_TABLET_TOOL_TYPE_PEN: + return ZWP_TABLET_TOOL_V2_TYPE_PEN; + case WLR_TABLET_TOOL_TYPE_ERASER: + return ZWP_TABLET_TOOL_V2_TYPE_ERASER; + case WLR_TABLET_TOOL_TYPE_BRUSH: + return ZWP_TABLET_TOOL_V2_TYPE_BRUSH; + case WLR_TABLET_TOOL_TYPE_PENCIL: + return ZWP_TABLET_TOOL_V2_TYPE_PENCIL; + case WLR_TABLET_TOOL_TYPE_AIRBRUSH: + return ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH; + case WLR_TABLET_TOOL_TYPE_MOUSE: + return ZWP_TABLET_TOOL_V2_TYPE_MOUSE; + case WLR_TABLET_TOOL_TYPE_LENS: + return ZWP_TABLET_TOOL_V2_TYPE_LENS; + case WLR_TABLET_TOOL_TYPE_TOTEM: + // missing, see: + // https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/19 + abort(); + } + abort(); // unreachable +} + +void destroy_tablet_tool_v2(struct wl_resource *resource) { + struct wlr_tablet_tool_client_v2 *client = + tablet_tool_client_from_resource(resource); + + if (!client) { + return; + } + + if (client->frame_source) { + wl_event_source_remove(client->frame_source); + } + + if (client->tool && client->tool->current_client == client) { + client->tool->current_client = NULL; + } + + wl_list_remove(&client->seat_link); + wl_list_remove(&client->tool_link); + free(client); + + wl_resource_set_user_data(resource, NULL); +} + +void add_tablet_tool_client(struct wlr_tablet_seat_client_v2 *seat, + struct wlr_tablet_v2_tablet_tool *tool) { + struct wlr_tablet_tool_client_v2 *client = calloc(1, sizeof(*client)); + if (!client) { + return; + } + client->tool = tool; + client->seat = seat; + + uint32_t version = wl_resource_get_version(seat->resource); + client->resource = wl_resource_create(seat->wl_client, + &zwp_tablet_tool_v2_interface, version, 0); + if (!client->resource) { + free(client); + return; + } + wl_resource_set_implementation(client->resource, &tablet_tool_impl, + client, destroy_tablet_tool_v2); + zwp_tablet_seat_v2_send_tool_added(seat->resource, client->resource); + + // Send the expected events + if (tool->wlr_tool->hardware_serial) { + zwp_tablet_tool_v2_send_hardware_serial( + client->resource, + tool->wlr_tool->hardware_serial >> 32, + tool->wlr_tool->hardware_serial & 0xFFFFFFFF); + } + if (tool->wlr_tool->hardware_wacom) { + zwp_tablet_tool_v2_send_hardware_id_wacom( + client->resource, + tool->wlr_tool->hardware_wacom >> 32, + tool->wlr_tool->hardware_wacom & 0xFFFFFFFF); + } + zwp_tablet_tool_v2_send_type(client->resource, + tablet_type_from_wlr_type(tool->wlr_tool->type)); + + if (tool->wlr_tool->tilt) { + zwp_tablet_tool_v2_send_capability(client->resource, + ZWP_TABLET_TOOL_V2_CAPABILITY_TILT); + } + + if (tool->wlr_tool->pressure) { + zwp_tablet_tool_v2_send_capability(client->resource, + ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE); + } + + if (tool->wlr_tool->distance) { + zwp_tablet_tool_v2_send_capability(client->resource, + ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE); + } + + if (tool->wlr_tool->rotation) { + zwp_tablet_tool_v2_send_capability(client->resource, + ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION); + } + + if (tool->wlr_tool->slider) { + zwp_tablet_tool_v2_send_capability(client->resource, + ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER); + } + + if (tool->wlr_tool->wheel) { + zwp_tablet_tool_v2_send_capability(client->resource, + ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL); + } + + zwp_tablet_tool_v2_send_done(client->resource); + + client->client = seat->wl_client; + wl_list_insert(&seat->tools, &client->seat_link); + wl_list_insert(&tool->clients, &client->tool_link); +} + +static void handle_wlr_tablet_tool_destroy(struct wl_listener *listener, void *data) { + struct wlr_tablet_v2_tablet_tool *tool = + wl_container_of(listener, tool, tool_destroy); + + struct wlr_tablet_tool_client_v2 *pos; + struct wlr_tablet_tool_client_v2 *tmp; + wl_list_for_each_safe(pos, tmp, &tool->clients, tool_link) { + zwp_tablet_tool_v2_send_removed(pos->resource); + pos->tool = NULL; + } + + wl_list_remove(&tool->clients); + wl_list_remove(&tool->link); + wl_list_remove(&tool->tool_destroy.link); + wl_list_remove(&tool->events.set_cursor.listener_list); + wl_list_remove(&tool->surface_destroy.link); + free(tool); +} + +struct wlr_tablet_v2_tablet_tool *wlr_tablet_tool_create( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat, + struct wlr_tablet_tool *wlr_tool) { + switch (wlr_tool->type) { + case WLR_TABLET_TOOL_TYPE_PEN: + case WLR_TABLET_TOOL_TYPE_ERASER: + case WLR_TABLET_TOOL_TYPE_BRUSH: + case WLR_TABLET_TOOL_TYPE_PENCIL: + case WLR_TABLET_TOOL_TYPE_AIRBRUSH: + case WLR_TABLET_TOOL_TYPE_MOUSE: + case WLR_TABLET_TOOL_TYPE_LENS: + /* supported */ + break; + default: + /* Unsupported */ + return NULL; + } + + struct wlr_tablet_seat_v2 *seat = get_or_create_tablet_seat(manager, wlr_seat); + if (!seat) { + return NULL; + } + struct wlr_tablet_v2_tablet_tool *tool = calloc(1, sizeof(*tool)); + if (!tool) { + return NULL; + } + + tool->wlr_tool = wlr_tool; + wl_list_init(&tool->clients); + wl_list_init(&tool->surface_destroy.link); + tool->default_grab.tool = tool; + tool->default_grab.interface = &default_tool_grab_interface; + tool->grab = &tool->default_grab; + + tool->tool_destroy.notify = handle_wlr_tablet_tool_destroy; + wl_signal_add(&wlr_tool->events.destroy, &tool->tool_destroy); + wl_list_insert(&seat->tools, &tool->link); + + // We need to create a tablet client for all clients on the seat + struct wlr_tablet_seat_client_v2 *pos; + wl_list_for_each(pos, &seat->clients, seat_link) { + // Tell the clients about the new tool + add_tablet_tool_client(pos, tool); + } + + wl_signal_init(&tool->events.set_cursor); + + return tool; +} + +struct wlr_tablet_tool_client_v2 *tablet_tool_client_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_tablet_tool_v2_interface, + &tablet_tool_impl)); + return wl_resource_get_user_data(resource); +} + +static ssize_t tablet_tool_button_update(struct wlr_tablet_v2_tablet_tool *tool, + uint32_t button, enum zwp_tablet_pad_v2_button_state state) { + ssize_t i; + if (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED) { + i = set_add(tool->pressed_buttons, &tool->num_buttons, + WLR_TABLET_V2_TOOL_BUTTONS_CAP, button); + if (i != -1) { + tool->pressed_serials[i] = -1; + } else { + wlr_log(WLR_ERROR, "Failed to add tablet tool button %x", button); + } + } else { + i = set_remove(tool->pressed_buttons, &tool->num_buttons, + WLR_TABLET_V2_TOOL_BUTTONS_CAP, button); + if (i != -1) { + tool->pressed_serials[i] = tool->pressed_serials[tool->num_buttons]; + } else { + wlr_log(WLR_ERROR, "Failed to remove tablet tool button %x", button); + } + } + return i; +} + +static void send_tool_frame(void *data) { + struct wlr_tablet_tool_client_v2 *tool = data; + + zwp_tablet_tool_v2_send_frame(tool->resource, get_current_time_msec()); + tool->frame_source = NULL; +} + +static void queue_tool_frame(struct wlr_tablet_tool_client_v2 *tool) { + struct wl_display *display = wl_client_get_display(tool->client); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + if (!tool->frame_source) { + tool->frame_source = + wl_event_loop_add_idle(loop, send_tool_frame, tool); + } +} + +static void handle_tablet_tool_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_tablet_v2_tablet_tool *tool = + wl_container_of(listener, tool, surface_destroy); + wlr_send_tablet_v2_tablet_tool_proximity_out(tool); +} + +void wlr_send_tablet_v2_tablet_tool_proximity_in( + struct wlr_tablet_v2_tablet_tool *tool, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + struct wl_client *client = wl_resource_get_client(surface->resource); + + if (tool->focused_surface == surface) { + return; + } + + wlr_send_tablet_v2_tablet_tool_proximity_out(tool); + + struct wlr_tablet_client_v2 *tablet_tmp; + struct wlr_tablet_client_v2 *tablet_client = NULL; + wl_list_for_each(tablet_tmp, &tablet->clients, tablet_link) { + if (tablet_tmp->client == client) { + tablet_client = tablet_tmp; + break; + } + } + + // Couldn't find the client binding for the surface's client. Either + // the client didn't bind tablet_v2 at all, or not for the relevant + // seat + if (!tablet_client) { + return; + } + + struct wlr_tablet_tool_client_v2 *tool_tmp = NULL; + struct wlr_tablet_tool_client_v2 *tool_client = NULL; + wl_list_for_each(tool_tmp, &tool->clients, tool_link) { + if (tool_tmp->client == client) { + tool_client = tool_tmp; + break; + } + } + + // Couldn't find the client binding for the surface's client. Either + // the client didn't bind tablet_v2 at all, or not for the relevant + // seat + if (!tool_client) { + return; + } + + // Reinitialize the focus destroy events + wl_list_remove(&tool->surface_destroy.link); + wl_signal_add(&surface->events.destroy, &tool->surface_destroy); + tool->surface_destroy.notify = handle_tablet_tool_surface_destroy; + + tool->current_client = tool_client; + + uint32_t serial = wlr_seat_client_next_serial(tool_client->seat->seat_client); + tool->focused_surface = surface; + tool->proximity_serial = serial; + + zwp_tablet_tool_v2_send_proximity_in(tool_client->resource, serial, + tablet_client->resource, surface->resource); + /* Send all the pressed buttons */ + for (size_t i = 0; i < tool->num_buttons; ++i) { + wlr_send_tablet_v2_tablet_tool_button(tool, + tool->pressed_buttons[i], + ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED); + } + if (tool->is_down) { + wlr_send_tablet_v2_tablet_tool_down(tool); + } + + queue_tool_frame(tool_client); +} + +void wlr_send_tablet_v2_tablet_tool_motion( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y) { + if (!tool->current_client) { + return; + } + + zwp_tablet_tool_v2_send_motion(tool->current_client->resource, + wl_fixed_from_double(x), wl_fixed_from_double(y)); + + queue_tool_frame(tool->current_client); +} + +void wlr_send_tablet_v2_tablet_tool_proximity_out( + struct wlr_tablet_v2_tablet_tool *tool) { + if (tool->current_client) { + for (size_t i = 0; i < tool->num_buttons; ++i) { + zwp_tablet_tool_v2_send_button(tool->current_client->resource, + tool->pressed_serials[i], + tool->pressed_buttons[i], + ZWP_TABLET_PAD_V2_BUTTON_STATE_RELEASED); + } + if (tool->is_down) { + zwp_tablet_tool_v2_send_up(tool->current_client->resource); + } + if (tool->current_client->frame_source) { + wl_event_source_remove(tool->current_client->frame_source); + send_tool_frame(tool->current_client); + } + zwp_tablet_tool_v2_send_proximity_out(tool->current_client->resource); + send_tool_frame(tool->current_client); + + wl_list_remove(&tool->surface_destroy.link); + wl_list_init(&tool->surface_destroy.link); + tool->current_client = NULL; + tool->focused_surface = NULL; + } +} + +void wlr_send_tablet_v2_tablet_tool_pressure( + struct wlr_tablet_v2_tablet_tool *tool, double pressure) { + if (tool->current_client) { + zwp_tablet_tool_v2_send_pressure(tool->current_client->resource, + pressure * 65535); + + queue_tool_frame(tool->current_client); + } +} + +void wlr_send_tablet_v2_tablet_tool_distance( + struct wlr_tablet_v2_tablet_tool *tool, double distance) { + if (tool->current_client) { + zwp_tablet_tool_v2_send_distance(tool->current_client->resource, + distance * 65535); + + queue_tool_frame(tool->current_client); + } +} + +void wlr_send_tablet_v2_tablet_tool_tilt( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y) { + if (!tool->current_client) { + return; + } + + zwp_tablet_tool_v2_send_tilt(tool->current_client->resource, + wl_fixed_from_double(x), wl_fixed_from_double(y)); + + queue_tool_frame(tool->current_client); +} + +void wlr_send_tablet_v2_tablet_tool_rotation( + struct wlr_tablet_v2_tablet_tool *tool, double degrees) { + if (!tool->current_client) { + return; + } + + zwp_tablet_tool_v2_send_rotation(tool->current_client->resource, + wl_fixed_from_double(degrees)); + + queue_tool_frame(tool->current_client); +} + +void wlr_send_tablet_v2_tablet_tool_slider( + struct wlr_tablet_v2_tablet_tool *tool, double position) { + if (!tool->current_client) { + return; + } + + zwp_tablet_tool_v2_send_slider(tool->current_client->resource, + position * 65535); + + queue_tool_frame(tool->current_client); +} + +void wlr_send_tablet_v2_tablet_tool_button( + struct wlr_tablet_v2_tablet_tool *tool, uint32_t button, + enum zwp_tablet_pad_v2_button_state state) { + ssize_t index = tablet_tool_button_update(tool, button, state); + + if (tool->current_client) { + uint32_t serial = wlr_seat_client_next_serial( + tool->current_client->seat->seat_client); + if (index >= 0) { + tool->pressed_serials[index] = serial; + } + + zwp_tablet_tool_v2_send_button(tool->current_client->resource, + serial, button, state); + queue_tool_frame(tool->current_client); + } +} + +void wlr_send_tablet_v2_tablet_tool_wheel( + struct wlr_tablet_v2_tablet_tool *tool, double degrees, int32_t clicks) { + if (tool->current_client) { + zwp_tablet_tool_v2_send_wheel(tool->current_client->resource, + wl_fixed_from_double(degrees), clicks); + + queue_tool_frame(tool->current_client); + } +} + +void wlr_send_tablet_v2_tablet_tool_down(struct wlr_tablet_v2_tablet_tool *tool) { + if (tool->is_down) { + return; + } + + tool->is_down = true; + if (tool->current_client) { + uint32_t serial = wlr_seat_client_next_serial( + tool->current_client->seat->seat_client); + + zwp_tablet_tool_v2_send_down(tool->current_client->resource, + serial); + queue_tool_frame(tool->current_client); + + tool->down_serial = serial; + } +} + +void wlr_send_tablet_v2_tablet_tool_up(struct wlr_tablet_v2_tablet_tool *tool) { + if (!tool->is_down) { + return; + } + tool->is_down = false; + tool->down_serial = 0; + + if (tool->current_client) { + zwp_tablet_tool_v2_send_up(tool->current_client->resource); + queue_tool_frame(tool->current_client); + } +} + + +void wlr_tablet_v2_tablet_tool_notify_proximity_in( + struct wlr_tablet_v2_tablet_tool *tool, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + if (tool->grab->interface->proximity_in) { + tool->grab->interface->proximity_in(tool->grab, tablet, surface); + } +} + +void wlr_tablet_v2_tablet_tool_notify_down(struct wlr_tablet_v2_tablet_tool *tool) { + if (tool->grab->interface->down) { + tool->grab->interface->down(tool->grab); + } +} +void wlr_tablet_v2_tablet_tool_notify_up(struct wlr_tablet_v2_tablet_tool *tool) { + if (tool->grab->interface->up) { + tool->grab->interface->up(tool->grab); + } +} + +void wlr_tablet_v2_tablet_tool_notify_motion( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y) { + if (tool->grab->interface->motion) { + tool->grab->interface->motion(tool->grab, x, y); + } +} + +void wlr_tablet_v2_tablet_tool_notify_pressure( + struct wlr_tablet_v2_tablet_tool *tool, double pressure) { + if (tool->grab->interface->pressure) { + tool->grab->interface->pressure(tool->grab, pressure); + } +} + +void wlr_tablet_v2_tablet_tool_notify_distance( + struct wlr_tablet_v2_tablet_tool *tool, double distance) { + if (tool->grab->interface->distance) { + tool->grab->interface->distance(tool->grab, distance); + } +} + +void wlr_tablet_v2_tablet_tool_notify_tilt( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y) { + if (tool->grab->interface->tilt) { + tool->grab->interface->tilt(tool->grab, x, y); + } +} + +void wlr_tablet_v2_tablet_tool_notify_rotation( + struct wlr_tablet_v2_tablet_tool *tool, double degrees) { + if (tool->grab->interface->rotation) { + tool->grab->interface->rotation(tool->grab, degrees); + } +} + +void wlr_tablet_v2_tablet_tool_notify_slider( + struct wlr_tablet_v2_tablet_tool *tool, double position) { + if (tool->grab->interface->slider) { + tool->grab->interface->slider(tool->grab, position); + } +} + +void wlr_tablet_v2_tablet_tool_notify_wheel( + struct wlr_tablet_v2_tablet_tool *tool, double degrees, int32_t clicks) { + if (tool->grab->interface->wheel) { + tool->grab->interface->wheel(tool->grab, degrees, clicks); + } +} + +void wlr_tablet_v2_tablet_tool_notify_proximity_out( + struct wlr_tablet_v2_tablet_tool *tool) { + if (tool->grab->interface->proximity_out) { + tool->grab->interface->proximity_out(tool->grab); + } +} + +void wlr_tablet_v2_tablet_tool_notify_button( + struct wlr_tablet_v2_tablet_tool *tool, uint32_t button, + enum zwp_tablet_pad_v2_button_state state) { + if (tool->grab->interface->button) { + tool->grab->interface->button(tool->grab, button, state); + } +} + +void wlr_tablet_tool_v2_start_grab(struct wlr_tablet_v2_tablet_tool *tool, + struct wlr_tablet_tool_v2_grab *grab) { + wlr_tablet_tool_v2_end_grab(tool); + tool->grab = grab; +} + +void wlr_tablet_tool_v2_end_grab(struct wlr_tablet_v2_tablet_tool *tool) { + if (tool->grab->interface->cancel) { + tool->grab->interface->cancel(tool->grab); + } + tool->grab = &tool->default_grab; +} + + +static void default_tool_proximity_in( + struct wlr_tablet_tool_v2_grab *grab, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + wlr_send_tablet_v2_tablet_tool_proximity_in(grab->tool, tablet, surface); +} + +static void default_tool_down(struct wlr_tablet_tool_v2_grab *grab) { + wlr_send_tablet_v2_tablet_tool_down(grab->tool); +} +static void default_tool_up(struct wlr_tablet_tool_v2_grab *grab) { + wlr_send_tablet_v2_tablet_tool_up(grab->tool); +} + +static void default_tool_motion( + struct wlr_tablet_tool_v2_grab *grab, double x, double y) { + wlr_send_tablet_v2_tablet_tool_motion(grab->tool, x, y); +} + +static void default_tool_pressure( + struct wlr_tablet_tool_v2_grab *grab, double pressure) { + wlr_send_tablet_v2_tablet_tool_pressure(grab->tool, pressure); +} + +static void default_tool_distance( + struct wlr_tablet_tool_v2_grab *grab, double distance) { + wlr_send_tablet_v2_tablet_tool_distance(grab->tool, distance); +} + +static void default_tool_tilt( + struct wlr_tablet_tool_v2_grab *grab, double x, double y) { + wlr_send_tablet_v2_tablet_tool_tilt(grab->tool, x, y); +} + +static void default_tool_rotation( + struct wlr_tablet_tool_v2_grab *grab, double degrees) { + wlr_send_tablet_v2_tablet_tool_rotation(grab->tool, degrees); +} + +static void default_tool_slider( + struct wlr_tablet_tool_v2_grab *grab, double position) { + wlr_send_tablet_v2_tablet_tool_slider(grab->tool, position); +} + +static void default_tool_wheel( + struct wlr_tablet_tool_v2_grab *grab, double degrees, int32_t clicks) { + wlr_send_tablet_v2_tablet_tool_wheel(grab->tool, degrees, clicks); +} + +static void default_tool_proximity_out(struct wlr_tablet_tool_v2_grab *grab) { + wlr_send_tablet_v2_tablet_tool_proximity_out(grab->tool); +} + +static void default_tool_button( + struct wlr_tablet_tool_v2_grab *grab, uint32_t button, + enum zwp_tablet_pad_v2_button_state state) { + wlr_send_tablet_v2_tablet_tool_button(grab->tool, button, state); +} + +static void default_tool_cancel(struct wlr_tablet_tool_v2_grab *grab) { + /* Do nothing. Default grab can't be canceled */ +} + +static const struct wlr_tablet_tool_v2_grab_interface + default_tool_grab_interface = { + .proximity_in = default_tool_proximity_in, + .down = default_tool_down, + .up = default_tool_up, + .motion = default_tool_motion, + .pressure = default_tool_pressure, + .distance = default_tool_distance, + .tilt = default_tool_tilt, + .rotation = default_tool_rotation, + .slider = default_tool_slider, + .wheel = default_tool_wheel, + .proximity_out = default_tool_proximity_out, + .button = default_tool_button, + .cancel = default_tool_cancel, +}; + +struct implicit_grab_state { + struct wlr_surface *original; + bool released; + + struct wlr_surface *focused; + struct wlr_tablet_v2_tablet *tablet; +}; + +static void check_and_release_implicit_grab(struct wlr_tablet_tool_v2_grab *grab) { + struct implicit_grab_state *state = grab->data; + /* Still button or tip pressed. We should hold the grab */ + if (grab->tool->is_down || grab->tool->num_buttons > 0 || state->released) { + return; + } + + state->released = true; + + /* We should still focus the same surface. Do nothing */ + if (state->original == state->focused) { + wlr_tablet_tool_v2_end_grab(grab->tool); + return; + } + + wlr_send_tablet_v2_tablet_tool_proximity_out(grab->tool); + if (state->focused) { + wlr_send_tablet_v2_tablet_tool_proximity_in(grab->tool, + state->tablet, state->focused); + } + + wlr_tablet_tool_v2_end_grab(grab->tool); +} + +static void implicit_tool_proximity_in( + struct wlr_tablet_tool_v2_grab *grab, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + + /* As long as we got an implicit grab, proximity won't change + * But should track the currently focused surface to change to it when + * the grab is released. + */ + struct implicit_grab_state *state = grab->data; + state->focused = surface; + state->tablet = tablet; +} + +static void implicit_tool_proximity_out(struct wlr_tablet_tool_v2_grab *grab) { + struct implicit_grab_state *state = grab->data; + state->focused = NULL; +} + +static void implicit_tool_down(struct wlr_tablet_tool_v2_grab *grab) { + wlr_send_tablet_v2_tablet_tool_down(grab->tool); +} + +static void implicit_tool_up(struct wlr_tablet_tool_v2_grab *grab) { + wlr_send_tablet_v2_tablet_tool_up(grab->tool); + check_and_release_implicit_grab(grab); +} + +static void implicit_tool_button( + struct wlr_tablet_tool_v2_grab *grab, uint32_t button, + enum zwp_tablet_pad_v2_button_state state) { + wlr_send_tablet_v2_tablet_tool_button(grab->tool, button, state); + check_and_release_implicit_grab(grab); +} + +static void implicit_tool_cancel(struct wlr_tablet_tool_v2_grab *grab) { + check_and_release_implicit_grab(grab); + free(grab->data); + free(grab); +} + +static const struct wlr_tablet_tool_v2_grab_interface + implicit_tool_grab_interface = { + .proximity_in = implicit_tool_proximity_in, + .down = implicit_tool_down, + .up = implicit_tool_up, + .motion = default_tool_motion, + .pressure = default_tool_pressure, + .distance = default_tool_distance, + .tilt = default_tool_tilt, + .rotation = default_tool_rotation, + .slider = default_tool_slider, + .wheel = default_tool_wheel, + .proximity_out = implicit_tool_proximity_out, + .button = implicit_tool_button, + .cancel = implicit_tool_cancel, +}; + +bool wlr_tablet_tool_v2_has_implicit_grab( + struct wlr_tablet_v2_tablet_tool *tool) { + return tool->grab->interface == &implicit_tool_grab_interface; +} + +void wlr_tablet_tool_v2_start_implicit_grab( + struct wlr_tablet_v2_tablet_tool *tool) { + if (wlr_tablet_tool_v2_has_implicit_grab(tool) || !tool->focused_surface) { + return; + } + + /* No current implicit grab */ + if (!(tool->is_down || tool->num_buttons > 0)) { + return; + } + + struct wlr_tablet_tool_v2_grab *grab = calloc(1, sizeof(*grab)); + if (!grab) { + return; + } + + grab->interface = &implicit_tool_grab_interface; + grab->tool = tool; + struct implicit_grab_state *state = calloc(1, sizeof(*state)); + if (!state) { + free(grab); + return; + } + + state->original = tool->focused_surface; + state->focused = tool->focused_surface; + grab->data = state; + + wlr_tablet_tool_v2_start_grab(tool, grab); +} diff --git a/types/wlr_compositor.c b/types/wlr_compositor.c new file mode 100644 index 0000000..791eb77 --- /dev/null +++ b/types/wlr_compositor.c @@ -0,0 +1,1501 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_buffer.h" +#include "types/wlr_region.h" +#include "types/wlr_subcompositor.h" +#include "util/array.h" +#include "util/time.h" + +#define COMPOSITOR_VERSION 6 +#define CALLBACK_VERSION 1 + +static int min(int fst, int snd) { + if (fst < snd) { + return fst; + } else { + return snd; + } +} + +static int max(int fst, int snd) { + if (fst > snd) { + return fst; + } else { + return snd; + } +} + +static void set_pending_buffer_resource(struct wlr_surface *surface, + struct wl_resource *resource) { + wl_list_remove(&surface->pending_buffer_resource_destroy.link); + surface->pending_buffer_resource = resource; + if (resource != NULL) { + wl_resource_add_destroy_listener(resource, &surface->pending_buffer_resource_destroy); + } else { + wl_list_init(&surface->pending_buffer_resource_destroy.link); + } +} + +static void pending_buffer_resource_handle_destroy(struct wl_listener *listener, void *data) { + struct wlr_surface *surface = + wl_container_of(listener, surface, pending_buffer_resource_destroy); + + set_pending_buffer_resource(surface, NULL); +} + +static void surface_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + if (surface->role_resource != NULL) { + wl_resource_post_error(resource, + WL_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT, + "surface was destroyed before its role object"); + return; + } + wl_resource_destroy(resource); +} + +static void surface_handle_attach(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *buffer_resource, int32_t dx, int32_t dy) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + + if (wl_resource_get_version(resource) >= WL_SURFACE_OFFSET_SINCE_VERSION && + (dx != 0 || dy != 0)) { + wl_resource_post_error(resource, WL_SURFACE_ERROR_INVALID_OFFSET, + "Offset must be zero on wl_surface.attach version >= %"PRIu32, + WL_SURFACE_OFFSET_SINCE_VERSION); + return; + } + + surface->pending.committed |= WLR_SURFACE_STATE_BUFFER; + set_pending_buffer_resource(surface, buffer_resource); + + if (wl_resource_get_version(resource) < WL_SURFACE_OFFSET_SINCE_VERSION) { + surface->pending.committed |= WLR_SURFACE_STATE_OFFSET; + surface->pending.dx = dx; + surface->pending.dy = dy; + } +} + +static void surface_handle_damage(struct wl_client *client, + struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + if (width < 0 || height < 0) { + return; + } + surface->pending.committed |= WLR_SURFACE_STATE_SURFACE_DAMAGE; + pixman_region32_union_rect(&surface->pending.surface_damage, + &surface->pending.surface_damage, + x, y, width, height); +} + +static void callback_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void surface_handle_frame(struct wl_client *client, + struct wl_resource *resource, uint32_t callback) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + + struct wl_resource *callback_resource = wl_resource_create(client, + &wl_callback_interface, CALLBACK_VERSION, callback); + if (callback_resource == NULL) { + wl_resource_post_no_memory(resource); + return; + } + wl_resource_set_implementation(callback_resource, NULL, NULL, + callback_handle_resource_destroy); + + wl_list_insert(surface->pending.frame_callback_list.prev, + wl_resource_get_link(callback_resource)); + + surface->pending.committed |= WLR_SURFACE_STATE_FRAME_CALLBACK_LIST; +} + +static void surface_handle_set_opaque_region(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + surface->pending.committed |= WLR_SURFACE_STATE_OPAQUE_REGION; + if (region_resource) { + const pixman_region32_t *region = wlr_region_from_resource(region_resource); + pixman_region32_copy(&surface->pending.opaque, region); + } else { + pixman_region32_clear(&surface->pending.opaque); + } +} + +static void surface_handle_set_input_region(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + surface->pending.committed |= WLR_SURFACE_STATE_INPUT_REGION; + if (region_resource) { + const pixman_region32_t *region = wlr_region_from_resource(region_resource); + pixman_region32_copy(&surface->pending.input, region); + } else { + pixman_region32_fini(&surface->pending.input); + pixman_region32_init_rect(&surface->pending.input, + INT32_MIN, INT32_MIN, UINT32_MAX, UINT32_MAX); + } +} + +static void surface_state_transformed_buffer_size(struct wlr_surface_state *state, + int *width, int *height) { + *width = state->buffer_width; + *height = state->buffer_height; + wlr_output_transform_coords(state->transform, width, height); +} + +/** + * Computes the surface viewport source size, ie. the size after applying the + * surface's scale, transform and cropping (via the viewport's source + * rectangle) but before applying the viewport scaling (via the viewport's + * destination rectangle). + */ +static void surface_state_viewport_src_size(struct wlr_surface_state *state, + int *out_width, int *out_height) { + if (state->buffer_width == 0 && state->buffer_height == 0) { + *out_width = *out_height = 0; + return; + } + + if (state->viewport.has_src) { + *out_width = state->viewport.src.width; + *out_height = state->viewport.src.height; + } else { + surface_state_transformed_buffer_size(state, + out_width, out_height); + *out_width /= state->scale; + *out_height /= state->scale; + } +} + +static void surface_finalize_pending(struct wlr_surface *surface) { + struct wlr_surface_state *pending = &surface->pending; + + if ((pending->committed & WLR_SURFACE_STATE_BUFFER)) { + struct wl_resource *buffer_resource = surface->pending_buffer_resource; + if (buffer_resource != NULL) { + set_pending_buffer_resource(surface, NULL); + + pending->buffer = wlr_buffer_try_from_resource(buffer_resource); + if (pending->buffer == NULL) { + wlr_surface_reject_pending(surface, + buffer_resource, -1, "unknown buffer type"); + } + } + + if (pending->buffer != NULL) { + pending->buffer_width = pending->buffer->width; + pending->buffer_height = pending->buffer->height; + } else { + pending->buffer_width = pending->buffer_height = 0; + } + } + + if (!pending->viewport.has_src && + (pending->buffer_width % pending->scale != 0 || + pending->buffer_height % pending->scale != 0)) { + // TODO: send WL_SURFACE_ERROR_INVALID_SIZE error to cursor surfaces + // once this issue is resolved: + // https://gitlab.freedesktop.org/wayland/wayland/-/issues/194 + if (!surface->role + || strcmp(surface->role->name, "wl_pointer-cursor") == 0 + || strcmp(surface->role->name, "wp_tablet_tool-cursor") == 0) { + wlr_log(WLR_DEBUG, "Client bug: submitted a buffer whose size (%dx%d) " + "is not divisible by scale (%d)", pending->buffer_width, + pending->buffer_height, pending->scale); + } else { + wlr_surface_reject_pending(surface, surface->resource, + WL_SURFACE_ERROR_INVALID_SIZE, + "Buffer size (%dx%d) is not divisible by scale (%d)", + pending->buffer_width, pending->buffer_height, pending->scale); + } + } + + if (pending->viewport.has_dst) { + if (pending->buffer_width == 0 && pending->buffer_height == 0) { + pending->width = pending->height = 0; + } else { + pending->width = pending->viewport.dst_width; + pending->height = pending->viewport.dst_height; + } + } else { + surface_state_viewport_src_size(pending, &pending->width, &pending->height); + } + + pixman_region32_intersect_rect(&pending->surface_damage, + &pending->surface_damage, 0, 0, pending->width, pending->height); + + pixman_region32_intersect_rect(&pending->buffer_damage, + &pending->buffer_damage, 0, 0, pending->buffer_width, + pending->buffer_height); +} + +static void surface_update_damage(pixman_region32_t *buffer_damage, + struct wlr_surface_state *current, struct wlr_surface_state *pending) { + pixman_region32_clear(buffer_damage); + + // Copy over surface damage + buffer damage + pixman_region32_t surface_damage; + pixman_region32_init(&surface_damage); + + pixman_region32_copy(&surface_damage, &pending->surface_damage); + + if (pending->viewport.has_dst) { + int src_width, src_height; + surface_state_viewport_src_size(pending, &src_width, &src_height); + float scale_x = (float)pending->viewport.dst_width / src_width; + float scale_y = (float)pending->viewport.dst_height / src_height; + wlr_region_scale_xy(&surface_damage, &surface_damage, + 1.0 / scale_x, 1.0 / scale_y); + } + if (pending->viewport.has_src) { + // This is lossy: do a best-effort conversion + pixman_region32_translate(&surface_damage, + floor(pending->viewport.src.x), + floor(pending->viewport.src.y)); + } + + wlr_region_scale(&surface_damage, &surface_damage, pending->scale); + + int width, height; + surface_state_transformed_buffer_size(pending, &width, &height); + wlr_region_transform(&surface_damage, &surface_damage, + wlr_output_transform_invert(pending->transform), + width, height); + + pixman_region32_union(buffer_damage, + &pending->buffer_damage, &surface_damage); + + pixman_region32_fini(&surface_damage); +} + +static void *surface_synced_create_state(struct wlr_surface_synced *synced) { + void *state = calloc(1, synced->impl->state_size); + if (state == NULL) { + return NULL; + } + if (synced->impl->init_state) { + synced->impl->init_state(state); + } + return state; +} + +static void surface_synced_destroy_state(struct wlr_surface_synced *synced, + void *state) { + if (state == NULL) { + return; + } + if (synced->impl->finish_state) { + synced->impl->finish_state(state); + } + free(state); +} + +static void surface_synced_move_state(struct wlr_surface_synced *synced, + void *dst, void *src) { + if (synced->impl->move_state) { + synced->impl->move_state(dst, src); + } else { + memcpy(dst, src, synced->impl->state_size); + } +} + +/** + * Overwrite state with a copy of the next state, then clear the next state. + */ +static void surface_state_move(struct wlr_surface_state *state, + struct wlr_surface_state *next, struct wlr_surface *surface) { + state->width = next->width; + state->height = next->height; + state->buffer_width = next->buffer_width; + state->buffer_height = next->buffer_height; + + if (next->committed & WLR_SURFACE_STATE_SCALE) { + state->scale = next->scale; + } + if (next->committed & WLR_SURFACE_STATE_TRANSFORM) { + state->transform = next->transform; + } + if (next->committed & WLR_SURFACE_STATE_OFFSET) { + state->dx = next->dx; + state->dy = next->dy; + next->dx = next->dy = 0; + } else { + state->dx = state->dy = 0; + } + if (next->committed & WLR_SURFACE_STATE_BUFFER) { + wlr_buffer_unlock(state->buffer); + state->buffer = NULL; + if (next->buffer) { + state->buffer = wlr_buffer_lock(next->buffer); + } + wlr_buffer_unlock(next->buffer); + next->buffer = NULL; + } + if (next->committed & WLR_SURFACE_STATE_SURFACE_DAMAGE) { + pixman_region32_copy(&state->surface_damage, &next->surface_damage); + pixman_region32_clear(&next->surface_damage); + } else { + pixman_region32_clear(&state->surface_damage); + } + if (next->committed & WLR_SURFACE_STATE_BUFFER_DAMAGE) { + pixman_region32_copy(&state->buffer_damage, &next->buffer_damage); + pixman_region32_clear(&next->buffer_damage); + } else { + pixman_region32_clear(&state->buffer_damage); + } + if (next->committed & WLR_SURFACE_STATE_OPAQUE_REGION) { + pixman_region32_copy(&state->opaque, &next->opaque); + } + if (next->committed & WLR_SURFACE_STATE_INPUT_REGION) { + pixman_region32_copy(&state->input, &next->input); + } + if (next->committed & WLR_SURFACE_STATE_VIEWPORT) { + state->viewport = next->viewport; + } + if (next->committed & WLR_SURFACE_STATE_FRAME_CALLBACK_LIST) { + wl_list_insert_list(&state->frame_callback_list, + &next->frame_callback_list); + wl_list_init(&next->frame_callback_list); + } + + void **state_synced = state->synced.data; + void **next_synced = next->synced.data; + struct wlr_surface_synced *synced; + wl_list_for_each(synced, &surface->synced, link) { + surface_synced_move_state(synced, + state_synced[synced->index], next_synced[synced->index]); + } + + // commit subsurface order + struct wlr_subsurface_parent_state *sub_state_next, *sub_state; + wl_list_for_each(sub_state_next, &next->subsurfaces_below, link) { + sub_state = wlr_surface_synced_get_state(sub_state_next->synced, state); + wl_list_remove(&sub_state->link); + wl_list_insert(state->subsurfaces_below.prev, &sub_state->link); + } + wl_list_for_each(sub_state_next, &next->subsurfaces_above, link) { + sub_state = wlr_surface_synced_get_state(sub_state_next->synced, state); + wl_list_remove(&sub_state->link); + wl_list_insert(state->subsurfaces_above.prev, &sub_state->link); + } + + state->committed = next->committed; + next->committed = 0; + + state->seq = next->seq; + + state->cached_state_locks = next->cached_state_locks; + next->cached_state_locks = 0; +} + +static void surface_apply_damage(struct wlr_surface *surface) { + if (surface->current.buffer == NULL) { + // NULL commit + if (surface->buffer != NULL) { + wlr_buffer_unlock(&surface->buffer->base); + } + surface->buffer = NULL; + surface->opaque = false; + return; + } + + surface->opaque = buffer_is_opaque(surface->current.buffer); + + if (surface->buffer != NULL) { + if (wlr_client_buffer_apply_damage(surface->buffer, + surface->current.buffer, &surface->buffer_damage)) { + wlr_buffer_unlock(surface->current.buffer); + surface->current.buffer = NULL; + return; + } + } + + if (surface->renderer == NULL) { + return; + } + + struct wlr_client_buffer *buffer = wlr_client_buffer_create( + surface->current.buffer, surface->renderer); + + if (buffer == NULL) { + wlr_log(WLR_ERROR, "Failed to upload buffer"); + return; + } + + if (surface->buffer != NULL) { + wlr_buffer_unlock(&surface->buffer->base); + } + surface->buffer = buffer; +} + +static void surface_update_opaque_region(struct wlr_surface *surface) { + if (!wlr_surface_has_buffer(surface)) { + pixman_region32_clear(&surface->opaque_region); + return; + } + + if (surface->opaque) { + pixman_region32_fini(&surface->opaque_region); + pixman_region32_init_rect(&surface->opaque_region, + 0, 0, surface->current.width, surface->current.height); + return; + } + + pixman_region32_intersect_rect(&surface->opaque_region, + &surface->current.opaque, + 0, 0, surface->current.width, surface->current.height); +} + +static void surface_update_input_region(struct wlr_surface *surface) { + pixman_region32_intersect_rect(&surface->input_region, + &surface->current.input, + 0, 0, surface->current.width, surface->current.height); +} + +static bool surface_state_init(struct wlr_surface_state *state, + struct wlr_surface *surface); +static void surface_state_finish(struct wlr_surface_state *state); + +static void surface_cache_pending(struct wlr_surface *surface) { + struct wlr_surface_state *cached = calloc(1, sizeof(*cached)); + if (!cached) { + goto error; + } + + if (!surface_state_init(cached, surface)) { + goto error_cached; + } + + void **cached_synced = cached->synced.data; + struct wlr_surface_synced *synced; + wl_list_for_each(synced, &surface->synced, link) { + void *synced_state = surface_synced_create_state(synced); + if (synced_state == NULL) { + goto error_state; + } + cached_synced[synced->index] = synced_state; + } + + surface_state_move(cached, &surface->pending, surface); + + wl_list_insert(surface->cached.prev, &cached->cached_state_link); + + surface->pending.seq++; + + return; + +error_state: + surface_state_finish(cached); +error_cached: + free(cached); +error: + wl_resource_post_no_memory(surface->resource); +} + +static void surface_commit_state(struct wlr_surface *surface, + struct wlr_surface_state *next) { + assert(next->cached_state_locks == 0); + + bool invalid_buffer = next->committed & WLR_SURFACE_STATE_BUFFER; + + if (invalid_buffer && next->buffer == NULL) { + surface->unmap_commit = surface->mapped; + wlr_surface_unmap(surface); + } else { + surface->unmap_commit = false; + } + + surface_update_damage(&surface->buffer_damage, &surface->current, next); + + surface->previous.scale = surface->current.scale; + surface->previous.transform = surface->current.transform; + surface->previous.width = surface->current.width; + surface->previous.height = surface->current.height; + surface->previous.buffer_width = surface->current.buffer_width; + surface->previous.buffer_height = surface->current.buffer_height; + + surface_state_move(&surface->current, next, surface); + + if (invalid_buffer) { + surface_apply_damage(surface); + } + surface_update_opaque_region(surface); + surface_update_input_region(surface); + + struct wlr_subsurface *subsurface; + wl_list_for_each(subsurface, &surface->current.subsurfaces_below, current.link) { + subsurface_handle_parent_commit(subsurface); + } + wl_list_for_each(subsurface, &surface->current.subsurfaces_above, current.link) { + subsurface_handle_parent_commit(subsurface); + } + + // If we're committing the pending state, bump the pending sequence number + // here, to allow commit listeners to lock the new pending state. + if (next == &surface->pending) { + surface->pending.seq++; + } + + if (surface->role != NULL && surface->role->commit != NULL && + (surface->role_resource != NULL || surface->role->no_object)) { + surface->role->commit(surface); + } + + wl_signal_emit_mutable(&surface->events.commit, surface); + + // Release the buffer after emitting the commit event, so that listeners can + // access it. Don't leave the buffer locked so that wl_shm buffers can be + // released immediately on commit when they are uploaded to the GPU. + wlr_buffer_unlock(surface->current.buffer); + surface->current.buffer = NULL; +} + +static void surface_handle_commit(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + surface->handling_commit = true; + + surface_finalize_pending(surface); + + if (surface->role != NULL && surface->role->client_commit != NULL && + (surface->role_resource != NULL || surface->role->no_object)) { + surface->role->client_commit(surface); + } + + wl_signal_emit_mutable(&surface->events.client_commit, NULL); + + surface->handling_commit = false; + if (surface->pending_rejected) { + return; + } + + if (surface->pending.cached_state_locks > 0 || !wl_list_empty(&surface->cached)) { + surface_cache_pending(surface); + } else { + surface_commit_state(surface, &surface->pending); + } +} + +static void surface_handle_set_buffer_transform(struct wl_client *client, + struct wl_resource *resource, int32_t transform) { + if (transform < WL_OUTPUT_TRANSFORM_NORMAL || + transform > WL_OUTPUT_TRANSFORM_FLIPPED_270) { + wl_resource_post_error(resource, WL_SURFACE_ERROR_INVALID_TRANSFORM, + "Specified transform value (%d) is invalid", transform); + return; + } + struct wlr_surface *surface = wlr_surface_from_resource(resource); + surface->pending.committed |= WLR_SURFACE_STATE_TRANSFORM; + surface->pending.transform = transform; +} + +static void surface_handle_set_buffer_scale(struct wl_client *client, + struct wl_resource *resource, int32_t scale) { + if (scale <= 0) { + wl_resource_post_error(resource, WL_SURFACE_ERROR_INVALID_SCALE, + "Specified scale value (%d) is not positive", scale); + return; + } + struct wlr_surface *surface = wlr_surface_from_resource(resource); + surface->pending.committed |= WLR_SURFACE_STATE_SCALE; + surface->pending.scale = scale; +} + +static void surface_handle_damage_buffer(struct wl_client *client, + struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, + int32_t height) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + if (width < 0 || height < 0) { + return; + } + surface->pending.committed |= WLR_SURFACE_STATE_BUFFER_DAMAGE; + pixman_region32_union_rect(&surface->pending.buffer_damage, + &surface->pending.buffer_damage, + x, y, width, height); +} + +static void surface_handle_offset(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + + surface->pending.committed |= WLR_SURFACE_STATE_OFFSET; + surface->pending.dx = x; + surface->pending.dy = y; +} + +static const struct wl_surface_interface surface_implementation = { + .destroy = surface_handle_destroy, + .attach = surface_handle_attach, + .damage = surface_handle_damage, + .frame = surface_handle_frame, + .set_opaque_region = surface_handle_set_opaque_region, + .set_input_region = surface_handle_set_input_region, + .commit = surface_handle_commit, + .set_buffer_transform = surface_handle_set_buffer_transform, + .set_buffer_scale = surface_handle_set_buffer_scale, + .damage_buffer = surface_handle_damage_buffer, + .offset = surface_handle_offset, +}; + +struct wlr_surface *wlr_surface_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_surface_interface, + &surface_implementation)); + return wl_resource_get_user_data(resource); +} + +static bool surface_state_init(struct wlr_surface_state *state, + struct wlr_surface *surface) { + *state = (struct wlr_surface_state){ + .scale = 1, + .transform = WL_OUTPUT_TRANSFORM_NORMAL, + }; + + wl_list_init(&state->subsurfaces_above); + wl_list_init(&state->subsurfaces_below); + + wl_list_init(&state->frame_callback_list); + + pixman_region32_init(&state->surface_damage); + pixman_region32_init(&state->buffer_damage); + pixman_region32_init(&state->opaque); + pixman_region32_init_rect(&state->input, + INT32_MIN, INT32_MIN, UINT32_MAX, UINT32_MAX); + + wl_array_init(&state->synced); + void *ptr = wl_array_add(&state->synced, surface->synced_len * sizeof(void *)); + return ptr != NULL; +} + +static void surface_state_finish(struct wlr_surface_state *state) { + wlr_buffer_unlock(state->buffer); + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &state->frame_callback_list) { + wl_resource_destroy(resource); + } + + pixman_region32_fini(&state->surface_damage); + pixman_region32_fini(&state->buffer_damage); + pixman_region32_fini(&state->opaque); + pixman_region32_fini(&state->input); + + wl_array_release(&state->synced); +} + +static void surface_state_destroy_cached(struct wlr_surface_state *state, + struct wlr_surface *surface) { + void **synced_states = state->synced.data; + struct wlr_surface_synced *synced; + wl_list_for_each(synced, &surface->synced, link) { + surface_synced_destroy_state(synced, synced_states[synced->index]); + } + + surface_state_finish(state); + wl_list_remove(&state->cached_state_link); + free(state); +} + +static void surface_output_destroy(struct wlr_surface_output *surface_output); +static void surface_destroy_role_object(struct wlr_surface *surface); + +static void surface_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + + struct wlr_surface_output *surface_output, *surface_output_tmp; + wl_list_for_each_safe(surface_output, surface_output_tmp, + &surface->current_outputs, link) { + surface_output_destroy(surface_output); + } + + surface_destroy_role_object(surface); + + wl_signal_emit_mutable(&surface->events.destroy, surface); + + wlr_addon_set_finish(&surface->addons); + assert(wl_list_empty(&surface->synced)); + + struct wlr_surface_state *cached, *cached_tmp; + wl_list_for_each_safe(cached, cached_tmp, &surface->cached, cached_state_link) { + surface_state_destroy_cached(cached, surface); + } + + wl_list_remove(&surface->renderer_destroy.link); + wl_list_remove(&surface->role_resource_destroy.link); + + wl_list_remove(&surface->pending_buffer_resource_destroy.link); + + surface_state_finish(&surface->pending); + surface_state_finish(&surface->current); + pixman_region32_fini(&surface->buffer_damage); + pixman_region32_fini(&surface->opaque_region); + pixman_region32_fini(&surface->input_region); + if (surface->buffer != NULL) { + wlr_buffer_unlock(&surface->buffer->base); + } + free(surface); +} + +static void surface_handle_renderer_destroy(struct wl_listener *listener, + void *data) { + struct wlr_surface *surface = + wl_container_of(listener, surface, renderer_destroy); + wl_resource_destroy(surface->resource); +} + +static struct wlr_surface *surface_create(struct wl_client *client, + uint32_t version, uint32_t id, struct wlr_renderer *renderer) { + struct wlr_surface *surface = calloc(1, sizeof(*surface)); + if (!surface) { + wl_client_post_no_memory(client); + return NULL; + } + surface->resource = wl_resource_create(client, &wl_surface_interface, + version, id); + if (surface->resource == NULL) { + free(surface); + wl_client_post_no_memory(client); + return NULL; + } + wl_resource_set_implementation(surface->resource, &surface_implementation, + surface, surface_handle_resource_destroy); + + wlr_log(WLR_DEBUG, "New wlr_surface %p (res %p)", surface, surface->resource); + + surface->renderer = renderer; + + surface_state_init(&surface->current, surface); + surface_state_init(&surface->pending, surface); + surface->pending.seq = 1; + + wl_signal_init(&surface->events.client_commit); + wl_signal_init(&surface->events.commit); + wl_signal_init(&surface->events.map); + wl_signal_init(&surface->events.unmap); + wl_signal_init(&surface->events.destroy); + wl_signal_init(&surface->events.new_subsurface); + wl_list_init(&surface->current_outputs); + wl_list_init(&surface->cached); + pixman_region32_init(&surface->buffer_damage); + pixman_region32_init(&surface->opaque_region); + pixman_region32_init(&surface->input_region); + wlr_addon_set_init(&surface->addons); + wl_list_init(&surface->synced); + + if (renderer != NULL) { + wl_signal_add(&renderer->events.destroy, &surface->renderer_destroy); + surface->renderer_destroy.notify = surface_handle_renderer_destroy; + } else { + wl_list_init(&surface->renderer_destroy.link); + } + + wl_list_init(&surface->role_resource_destroy.link); + + surface->pending_buffer_resource_destroy.notify = pending_buffer_resource_handle_destroy; + wl_list_init(&surface->pending_buffer_resource_destroy.link); + + return surface; +} + +struct wlr_texture *wlr_surface_get_texture(struct wlr_surface *surface) { + if (surface->buffer == NULL) { + return NULL; + } + return surface->buffer->texture; +} + +bool wlr_surface_has_buffer(struct wlr_surface *surface) { + return wlr_surface_state_has_buffer(&surface->current); +} + +bool wlr_surface_state_has_buffer(const struct wlr_surface_state *state) { + return state->buffer_width > 0 && state->buffer_height > 0; +} + +void wlr_surface_map(struct wlr_surface *surface) { + if (surface->mapped) { + return; + } + assert(wlr_surface_has_buffer(surface)); + surface->mapped = true; + + struct wlr_subsurface *subsurface; + wl_list_for_each(subsurface, &surface->current.subsurfaces_below, current.link) { + subsurface_consider_map(subsurface); + } + wl_list_for_each(subsurface, &surface->current.subsurfaces_above, current.link) { + subsurface_consider_map(subsurface); + } + + wl_signal_emit_mutable(&surface->events.map, NULL); +} + +void wlr_surface_unmap(struct wlr_surface *surface) { + if (!surface->mapped) { + return; + } + surface->mapped = false; + wl_signal_emit_mutable(&surface->events.unmap, NULL); + if (surface->role != NULL && surface->role->unmap != NULL && + (surface->role_resource != NULL || surface->role->no_object)) { + surface->role->unmap(surface); + } + + struct wlr_subsurface *subsurface; + wl_list_for_each(subsurface, &surface->current.subsurfaces_below, current.link) { + wlr_surface_unmap(subsurface->surface); + } + wl_list_for_each(subsurface, &surface->current.subsurfaces_above, current.link) { + wlr_surface_unmap(subsurface->surface); + } +} + +void wlr_surface_reject_pending(struct wlr_surface *surface, struct wl_resource *resource, + uint32_t code, const char *msg, ...) { + assert(surface->handling_commit); + if (surface->pending_rejected) { + return; + } + + va_list args; + va_start(args, msg); + + // XXX: libwayland could expose wl_resource_post_error_vargs() instead + char buffer[128]; // Matches the size of the buffer used in libwayland + vsnprintf(buffer, sizeof(buffer), msg, args); + + wl_resource_post_error(resource, code, "%s", buffer); + surface->pending_rejected = true; + + va_end(args); +} + +bool wlr_surface_set_role(struct wlr_surface *surface, const struct wlr_surface_role *role, + struct wl_resource *error_resource, uint32_t error_code) { + assert(role != NULL); + + if (surface->role != NULL && surface->role != role) { + if (error_resource != NULL) { + wl_resource_post_error(error_resource, error_code, + "Cannot assign role %s to wl_surface@%" PRIu32 ", already has role %s", + role->name, wl_resource_get_id(surface->resource), + surface->role->name); + } + return false; + } + if (surface->role_resource != NULL) { + wl_resource_post_error(error_resource, error_code, + "Cannot reassign role %s to wl_surface@%" PRIu32 ", role object still exists", + role->name, wl_resource_get_id(surface->resource)); + return false; + } + + surface->role = role; + return true; +} + +static void surface_handle_role_resource_destroy(struct wl_listener *listener, void *data) { + struct wlr_surface *surface = wl_container_of(listener, surface, role_resource_destroy); + surface_destroy_role_object(surface); +} + +void wlr_surface_set_role_object(struct wlr_surface *surface, struct wl_resource *role_resource) { + assert(surface->role != NULL); + assert(!surface->role->no_object); + assert(surface->role_resource == NULL); + assert(role_resource != NULL); + surface->role_resource = role_resource; + surface->role_resource_destroy.notify = surface_handle_role_resource_destroy; + wl_resource_add_destroy_listener(role_resource, &surface->role_resource_destroy); +} + +static void surface_destroy_role_object(struct wlr_surface *surface) { + if (surface->role_resource == NULL) { + return; + } + wlr_surface_unmap(surface); + if (surface->role->destroy != NULL) { + surface->role->destroy(surface); + } + surface->role_resource = NULL; + wl_list_remove(&surface->role_resource_destroy.link); + wl_list_init(&surface->role_resource_destroy.link); +} + +uint32_t wlr_surface_lock_pending(struct wlr_surface *surface) { + surface->pending.cached_state_locks++; + return surface->pending.seq; +} + +void wlr_surface_unlock_cached(struct wlr_surface *surface, uint32_t seq) { + if (surface->pending.seq == seq) { + assert(surface->pending.cached_state_locks > 0); + surface->pending.cached_state_locks--; + return; + } + + bool found = false; + struct wlr_surface_state *cached; + wl_list_for_each(cached, &surface->cached, cached_state_link) { + if (cached->seq == seq) { + found = true; + break; + } + } + assert(found); + + assert(cached->cached_state_locks > 0); + cached->cached_state_locks--; + + if (cached->cached_state_locks != 0) { + return; + } + + if (cached->cached_state_link.prev != &surface->cached) { + // This isn't the first cached state. This means we're blocked on a + // previous cached state. + return; + } + + // TODO: consider merging all committed states together + struct wlr_surface_state *next, *tmp; + wl_list_for_each_safe(next, tmp, &surface->cached, cached_state_link) { + if (next->cached_state_locks > 0) { + break; + } + + surface_commit_state(surface, next); + surface_state_destroy_cached(next, surface); + } +} + +struct wlr_surface *wlr_surface_get_root_surface(struct wlr_surface *surface) { + struct wlr_subsurface *subsurface; + while ((subsurface = wlr_subsurface_try_from_wlr_surface(surface))) { + surface = subsurface->parent; + } + return surface; +} + +bool wlr_surface_point_accepts_input(struct wlr_surface *surface, + double sx, double sy) { + return sx >= 0 && sx < surface->current.width && + sy >= 0 && sy < surface->current.height && + pixman_region32_contains_point(&surface->input_region, + floor(sx), floor(sy), NULL); +} + +struct wlr_surface *wlr_surface_surface_at(struct wlr_surface *surface, + double sx, double sy, double *sub_x, double *sub_y) { + struct wlr_subsurface *subsurface; + wl_list_for_each_reverse(subsurface, &surface->current.subsurfaces_above, + current.link) { + if (!subsurface->surface->mapped) { + continue; + } + + double _sub_x = subsurface->current.x; + double _sub_y = subsurface->current.y; + struct wlr_surface *sub = wlr_surface_surface_at(subsurface->surface, + sx - _sub_x, sy - _sub_y, sub_x, sub_y); + if (sub != NULL) { + return sub; + } + } + + if (wlr_surface_point_accepts_input(surface, sx, sy)) { + if (sub_x) { + *sub_x = sx; + } + if (sub_y) { + *sub_y = sy; + } + return surface; + } + + wl_list_for_each_reverse(subsurface, &surface->current.subsurfaces_below, + current.link) { + if (!subsurface->surface->mapped) { + continue; + } + + double _sub_x = subsurface->current.x; + double _sub_y = subsurface->current.y; + struct wlr_surface *sub = wlr_surface_surface_at(subsurface->surface, + sx - _sub_x, sy - _sub_y, sub_x, sub_y); + if (sub != NULL) { + return sub; + } + } + + return NULL; +} + +static void surface_output_destroy(struct wlr_surface_output *surface_output) { + wl_list_remove(&surface_output->bind.link); + wl_list_remove(&surface_output->destroy.link); + wl_list_remove(&surface_output->link); + + free(surface_output); +} + +static void surface_handle_output_bind(struct wl_listener *listener, + void *data) { + struct wlr_output_event_bind *evt = data; + struct wlr_surface_output *surface_output = + wl_container_of(listener, surface_output, bind); + struct wl_client *client = wl_resource_get_client( + surface_output->surface->resource); + if (client == wl_resource_get_client(evt->resource)) { + wl_surface_send_enter(surface_output->surface->resource, evt->resource); + } +} + +static void surface_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_surface_output *surface_output = + wl_container_of(listener, surface_output, destroy); + surface_output_destroy(surface_output); +} + +void wlr_surface_send_enter(struct wlr_surface *surface, + struct wlr_output *output) { + struct wl_client *client = wl_resource_get_client(surface->resource); + struct wlr_surface_output *surface_output; + struct wl_resource *resource; + + wl_list_for_each(surface_output, &surface->current_outputs, link) { + if (surface_output->output == output) { + return; + } + } + + surface_output = calloc(1, sizeof(*surface_output)); + if (surface_output == NULL) { + return; + } + surface_output->bind.notify = surface_handle_output_bind; + surface_output->destroy.notify = surface_handle_output_destroy; + + wl_signal_add(&output->events.bind, &surface_output->bind); + wl_signal_add(&output->events.destroy, &surface_output->destroy); + + surface_output->surface = surface; + surface_output->output = output; + wl_list_insert(&surface->current_outputs, &surface_output->link); + + wl_resource_for_each(resource, &output->resources) { + if (client == wl_resource_get_client(resource)) { + wl_surface_send_enter(surface->resource, resource); + } + } +} + +void wlr_surface_send_leave(struct wlr_surface *surface, + struct wlr_output *output) { + struct wl_client *client = wl_resource_get_client(surface->resource); + struct wlr_surface_output *surface_output, *tmp; + struct wl_resource *resource; + + wl_list_for_each_safe(surface_output, tmp, + &surface->current_outputs, link) { + if (surface_output->output == output) { + surface_output_destroy(surface_output); + wl_resource_for_each(resource, &output->resources) { + if (client == wl_resource_get_client(resource)) { + wl_surface_send_leave(surface->resource, resource); + } + } + break; + } + } +} + +void wlr_surface_send_frame_done(struct wlr_surface *surface, + const struct timespec *when) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, + &surface->current.frame_callback_list) { + wl_callback_send_done(resource, timespec_to_msec(when)); + wl_resource_destroy(resource); + } +} + +static void surface_for_each_surface(struct wlr_surface *surface, int x, int y, + wlr_surface_iterator_func_t iterator, void *user_data) { + struct wlr_subsurface *subsurface; + wl_list_for_each(subsurface, &surface->current.subsurfaces_below, current.link) { + if (!subsurface->surface->mapped) { + continue; + } + + struct wlr_subsurface_parent_state *state = &subsurface->current; + int sx = state->x; + int sy = state->y; + + surface_for_each_surface(subsurface->surface, x + sx, y + sy, + iterator, user_data); + } + + iterator(surface, x, y, user_data); + + wl_list_for_each(subsurface, &surface->current.subsurfaces_above, current.link) { + if (!subsurface->surface->mapped) { + continue; + } + + struct wlr_subsurface_parent_state *state = &subsurface->current; + int sx = state->x; + int sy = state->y; + + surface_for_each_surface(subsurface->surface, x + sx, y + sy, + iterator, user_data); + } +} + +void wlr_surface_for_each_surface(struct wlr_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data) { + surface_for_each_surface(surface, 0, 0, iterator, user_data); +} + +struct bound_acc { + int32_t min_x, min_y; + int32_t max_x, max_y; +}; + +static void handle_bounding_box_surface(struct wlr_surface *surface, + int x, int y, void *data) { + struct bound_acc *acc = data; + + acc->min_x = min(x, acc->min_x); + acc->min_y = min(y, acc->min_y); + + acc->max_x = max(x + surface->current.width, acc->max_x); + acc->max_y = max(y + surface->current.height, acc->max_y); +} + +void wlr_surface_get_extends(struct wlr_surface *surface, struct wlr_box *box) { + struct bound_acc acc = { + .min_x = 0, + .min_y = 0, + .max_x = surface->current.width, + .max_y = surface->current.height, + }; + + wlr_surface_for_each_surface(surface, handle_bounding_box_surface, &acc); + + box->x = acc.min_x; + box->y = acc.min_y; + box->width = acc.max_x - acc.min_x; + box->height = acc.max_y - acc.min_y; +} + +static void crop_region(pixman_region32_t *dst, pixman_region32_t *src, + const struct wlr_box *box) { + pixman_region32_intersect_rect(dst, src, + box->x, box->y, box->width, box->height); + pixman_region32_translate(dst, -box->x, -box->y); +} + +void wlr_surface_get_effective_damage(struct wlr_surface *surface, + pixman_region32_t *damage) { + pixman_region32_clear(damage); + + // Transform and copy the buffer damage in terms of surface coordinates. + wlr_region_transform(damage, &surface->buffer_damage, + surface->current.transform, surface->current.buffer_width, + surface->current.buffer_height); + wlr_region_scale(damage, damage, 1.0 / (float)surface->current.scale); + + if (surface->current.viewport.has_src) { + struct wlr_box src_box = { + .x = floor(surface->current.viewport.src.x), + .y = floor(surface->current.viewport.src.y), + .width = ceil(surface->current.viewport.src.width), + .height = ceil(surface->current.viewport.src.height), + }; + crop_region(damage, damage, &src_box); + } + if (surface->current.viewport.has_dst) { + int src_width, src_height; + surface_state_viewport_src_size(&surface->current, + &src_width, &src_height); + float scale_x = (float)surface->current.viewport.dst_width / src_width; + float scale_y = (float)surface->current.viewport.dst_height / src_height; + wlr_region_scale_xy(damage, damage, scale_x, scale_y); + } +} + +void wlr_surface_get_buffer_source_box(struct wlr_surface *surface, + struct wlr_fbox *box) { + box->x = box->y = 0; + box->width = surface->current.buffer_width; + box->height = surface->current.buffer_height; + + if (surface->current.viewport.has_src) { + box->x = surface->current.viewport.src.x * surface->current.scale; + box->y = surface->current.viewport.src.y * surface->current.scale; + box->width = surface->current.viewport.src.width * surface->current.scale; + box->height = surface->current.viewport.src.height * surface->current.scale; + + int width, height; + surface_state_transformed_buffer_size(&surface->current, &width, &height); + wlr_fbox_transform(box, box, + wlr_output_transform_invert(surface->current.transform), + width, height); + } +} + +void wlr_surface_set_preferred_buffer_scale(struct wlr_surface *surface, + int32_t scale) { + assert(scale > 0); + + if (wl_resource_get_version(surface->resource) < + WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) { + return; + } + + if (surface->preferred_buffer_scale == scale) { + return; + } + + wl_surface_send_preferred_buffer_scale(surface->resource, scale); + surface->preferred_buffer_scale = scale; +} + +void wlr_surface_set_preferred_buffer_transform(struct wlr_surface *surface, + enum wl_output_transform transform) { + if (wl_resource_get_version(surface->resource) < + WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION) { + return; + } + + if (surface->preferred_buffer_transform == transform && + surface->preferred_buffer_transform_sent) { + return; + } + + wl_surface_send_preferred_buffer_transform(surface->resource, transform); + surface->preferred_buffer_transform_sent = true; + surface->preferred_buffer_transform = transform; +} + +static const struct wl_compositor_interface compositor_impl; + +static struct wlr_compositor *compositor_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_compositor_interface, + &compositor_impl)); + return wl_resource_get_user_data(resource); +} + +static void compositor_create_surface(struct wl_client *client, + struct wl_resource *resource, uint32_t id) { + struct wlr_compositor *compositor = compositor_from_resource(resource); + + struct wlr_surface *surface = surface_create(client, + wl_resource_get_version(resource), id, compositor->renderer); + if (surface == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_signal_emit_mutable(&compositor->events.new_surface, surface); +} + +static void compositor_create_region(struct wl_client *client, + struct wl_resource *resource, uint32_t id) { + region_create(client, wl_resource_get_version(resource), id); +} + +static const struct wl_compositor_interface compositor_impl = { + .create_surface = compositor_create_surface, + .create_region = compositor_create_region, +}; + +static void compositor_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_compositor *compositor = data; + + struct wl_resource *resource = + wl_resource_create(wl_client, &wl_compositor_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, &compositor_impl, compositor, NULL); +} + +static void compositor_handle_display_destroy( + struct wl_listener *listener, void *data) { + struct wlr_compositor *compositor = + wl_container_of(listener, compositor, display_destroy); + wl_signal_emit_mutable(&compositor->events.destroy, NULL); + wl_list_remove(&compositor->display_destroy.link); + wl_global_destroy(compositor->global); + free(compositor); +} + +struct wlr_compositor *wlr_compositor_create(struct wl_display *display, + uint32_t version, struct wlr_renderer *renderer) { + assert(version <= COMPOSITOR_VERSION); + + struct wlr_compositor *compositor = calloc(1, sizeof(*compositor)); + if (!compositor) { + return NULL; + } + + compositor->global = wl_global_create(display, &wl_compositor_interface, + version, compositor, compositor_bind); + if (!compositor->global) { + free(compositor); + return NULL; + } + compositor->renderer = renderer; + + wl_signal_init(&compositor->events.new_surface); + wl_signal_init(&compositor->events.destroy); + + compositor->display_destroy.notify = compositor_handle_display_destroy; + wl_display_add_destroy_listener(display, &compositor->display_destroy); + + return compositor; +} + +static bool surface_state_add_synced(struct wlr_surface_state *state, void *value) { + void **ptr = wl_array_add(&state->synced, sizeof(void *)); + if (ptr == NULL) { + return false; + } + *ptr = value; + return true; +} + +static void *surface_state_remove_synced(struct wlr_surface_state *state, + struct wlr_surface_synced *synced) { + void **synced_states = state->synced.data; + void *synced_state = synced_states[synced->index]; + array_remove_at(&state->synced, synced->index * sizeof(void *), sizeof(void *)); + return synced_state; +} + +static void surface_state_remove_and_destroy_synced(struct wlr_surface_state *state, + struct wlr_surface_synced *synced) { + void *synced_state = surface_state_remove_synced(state, synced); + surface_synced_destroy_state(synced, synced_state); +} + +bool wlr_surface_synced_init(struct wlr_surface_synced *synced, + struct wlr_surface *surface, const struct wlr_surface_synced_impl *impl, + void *pending, void *current) { + assert(impl->state_size > 0); + + struct wlr_surface_synced *other; + wl_list_for_each(other, &surface->synced, link) { + assert(synced != other); + } + + memset(pending, 0, impl->state_size); + memset(current, 0, impl->state_size); + if (impl->init_state) { + impl->init_state(pending); + impl->init_state(current); + } + if (!surface_state_add_synced(&surface->pending, pending)) { + goto error_init; + } + if (!surface_state_add_synced(&surface->current, current)) { + goto error_pending; + } + + *synced = (struct wlr_surface_synced){ + .surface = surface, + .impl = impl, + .index = surface->synced_len, + }; + + struct wlr_surface_state *cached; + wl_list_for_each(cached, &surface->cached, cached_state_link) { + void *synced_state = surface_synced_create_state(synced); + if (synced_state == NULL || + !surface_state_add_synced(cached, synced_state)) { + surface_synced_destroy_state(synced, synced_state); + goto error_cached; + } + } + + wl_list_insert(&surface->synced, &synced->link); + surface->synced_len++; + + return true; + +error_cached:; + struct wlr_surface_state *failed_at = cached; + wl_list_for_each(cached, &surface->cached, cached_state_link) { + if (cached == failed_at) { + break; + } + surface_state_remove_and_destroy_synced(cached, synced); + } + surface_state_remove_synced(&surface->current, synced); +error_pending: + surface_state_remove_synced(&surface->pending, synced); +error_init: + if (synced->impl->finish_state) { + synced->impl->finish_state(pending); + synced->impl->finish_state(current); + } + return false; +} + +void wlr_surface_synced_finish(struct wlr_surface_synced *synced) { + struct wlr_surface *surface = synced->surface; + + bool found = false; + struct wlr_surface_synced *other; + wl_list_for_each(other, &surface->synced, link) { + if (other == synced) { + found = true; + } else if (other->index > synced->index) { + other->index--; + } + } + assert(found); + + struct wlr_surface_state *cached; + wl_list_for_each(cached, &surface->cached, cached_state_link) { + surface_state_remove_and_destroy_synced(cached, synced); + } + + void *pending = surface_state_remove_synced(&surface->pending, synced); + void *current = surface_state_remove_synced(&surface->current, synced); + if (synced->impl->finish_state) { + synced->impl->finish_state(pending); + synced->impl->finish_state(current); + } + + wl_list_remove(&synced->link); + synced->surface->synced_len--; +} + +void *wlr_surface_synced_get_state(struct wlr_surface_synced *synced, + const struct wlr_surface_state *state) { + void **synced_states = state->synced.data; + return synced_states[synced->index]; +} diff --git a/types/wlr_content_type_v1.c b/types/wlr_content_type_v1.c new file mode 100644 index 0000000..0c59859 --- /dev/null +++ b/types/wlr_content_type_v1.c @@ -0,0 +1,198 @@ +#include +#include +#include +#include + +#define CONTENT_TYPE_VERSION 1 + +struct wlr_content_type_v1_surface { + struct wl_resource *resource; + struct wlr_addon addon; + enum wp_content_type_v1_type pending, current; + + struct wlr_surface_synced synced; +}; + +static void resource_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wp_content_type_v1_interface content_type_surface_impl; +static const struct wp_content_type_manager_v1_interface manager_impl; + +// Returns NULL if the resource is inert +static struct wlr_content_type_v1_surface *content_type_surface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_content_type_v1_interface, &content_type_surface_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_content_type_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_content_type_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void content_type_surface_handle_set_content_type(struct wl_client *client, + struct wl_resource *resource, uint32_t type) { + struct wlr_content_type_v1_surface *content_type_surface = + content_type_surface_from_resource(resource); + if (content_type_surface == NULL) { + return; + } + content_type_surface->pending = type; +} + +static const struct wp_content_type_v1_interface content_type_surface_impl = { + .destroy = resource_handle_destroy, + .set_content_type = content_type_surface_handle_set_content_type, +}; + +static void content_type_surface_destroy( + struct wlr_content_type_v1_surface *content_type_surface) { + if (content_type_surface == NULL) { + return; + } + wlr_addon_finish(&content_type_surface->addon); + wlr_surface_synced_finish(&content_type_surface->synced); + wl_resource_set_user_data(content_type_surface->resource, NULL); + free(content_type_surface); +} + +static void surface_addon_destroy(struct wlr_addon *addon) { + struct wlr_content_type_v1_surface *content_type_surface = + wl_container_of(addon, content_type_surface, addon); + content_type_surface_destroy(content_type_surface); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "wp_content_type_v1", + .destroy = surface_addon_destroy, +}; + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(enum wp_content_type_v1_type), +}; + +static void content_type_surface_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_content_type_v1_surface *content_type_surface = + content_type_surface_from_resource(resource); + content_type_surface_destroy(content_type_surface); +} + +static void manager_handle_get_surface_content_type(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_content_type_manager_v1 *manager = + manager_from_resource(manager_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + if (wlr_addon_find(&surface->addons, manager, &surface_addon_impl) != NULL) { + wl_resource_post_error(manager_resource, + WP_CONTENT_TYPE_MANAGER_V1_ERROR_ALREADY_CONSTRUCTED, + "wp_content_type_v1 already constructed for this surface"); + return; + } + + struct wlr_content_type_v1_surface *content_type_surface = + calloc(1, sizeof(*content_type_surface)); + if (content_type_surface == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + if (!wlr_surface_synced_init(&content_type_surface->synced, surface, + &surface_synced_impl, &content_type_surface->pending, + &content_type_surface->current)) { + free(content_type_surface); + wl_resource_post_no_memory(manager_resource); + return; + } + + uint32_t version = wl_resource_get_version(manager_resource); + content_type_surface->resource = wl_resource_create(client, + &wp_content_type_v1_interface, version, id); + if (content_type_surface->resource == NULL) { + wlr_surface_synced_finish(&content_type_surface->synced); + free(content_type_surface); + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(content_type_surface->resource, + &content_type_surface_impl, content_type_surface, + content_type_surface_handle_resource_destroy); + + wlr_addon_init(&content_type_surface->addon, &surface->addons, + manager, &surface_addon_impl); +} + +static const struct wp_content_type_manager_v1_interface manager_impl = { + .destroy = resource_handle_destroy, + .get_surface_content_type = manager_handle_get_surface_content_type, +}; + +static void manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_content_type_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_content_type_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_content_type_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + wl_global_destroy(manager->global); + wl_list_remove(&manager->display_destroy.link); + free(manager); +} + +struct wlr_content_type_manager_v1 *wlr_content_type_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= CONTENT_TYPE_VERSION); + + struct wlr_content_type_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &wp_content_type_manager_v1_interface, version, manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +enum wp_content_type_v1_type wlr_surface_get_content_type_v1( + struct wlr_content_type_manager_v1 *manager, struct wlr_surface *surface) { + struct wlr_addon *addon = + wlr_addon_find(&surface->addons, manager, &surface_addon_impl); + if (addon == NULL) { + return WP_CONTENT_TYPE_V1_TYPE_NONE; + } + + struct wlr_content_type_v1_surface *content_type_surface = + wl_container_of(addon, content_type_surface, addon); + return content_type_surface->current; +} diff --git a/types/wlr_cursor.c b/types/wlr_cursor.c new file mode 100644 index 0000000..77ab2fb --- /dev/null +++ b/types/wlr_cursor.c @@ -0,0 +1,1213 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_buffer.h" +#include "types/wlr_output.h" + +struct wlr_cursor_device { + struct wlr_cursor *cursor; + struct wlr_input_device *device; + struct wl_list link; + struct wlr_output *mapped_output; + struct wlr_box mapped_box; // empty if unset + + struct wl_listener motion; + struct wl_listener motion_absolute; + struct wl_listener button; + struct wl_listener axis; + struct wl_listener frame; + struct wl_listener swipe_begin; + struct wl_listener swipe_update; + struct wl_listener swipe_end; + struct wl_listener pinch_begin; + struct wl_listener pinch_update; + struct wl_listener pinch_end; + struct wl_listener hold_begin; + struct wl_listener hold_end; + + struct wl_listener touch_down; + struct wl_listener touch_up; + struct wl_listener touch_motion; + struct wl_listener touch_cancel; + struct wl_listener touch_frame; + + struct wl_listener tablet_tool_axis; + struct wl_listener tablet_tool_proximity; + struct wl_listener tablet_tool_tip; + struct wl_listener tablet_tool_button; + + struct wl_listener destroy; +}; + +struct wlr_cursor_output_cursor { + struct wlr_cursor *cursor; + struct wlr_output_cursor *output_cursor; + struct wl_list link; + + struct wl_listener layout_output_destroy; + + // only when using a surface as the cursor image + struct wl_listener output_commit; + + // only when using an XCursor as the cursor image + struct wlr_xcursor *xcursor; + size_t xcursor_index; + struct wl_event_source *xcursor_timer; +}; + +struct wlr_cursor_state { + struct wlr_cursor cursor; + + struct wl_list devices; // wlr_cursor_device.link + struct wl_list output_cursors; // wlr_cursor_output_cursor.link + struct wlr_output_layout *layout; + struct wlr_output *mapped_output; + struct wlr_box mapped_box; // empty if unset + + struct wl_listener layout_add; + struct wl_listener layout_change; + struct wl_listener layout_destroy; + + // only when using a buffer as the cursor image + struct wlr_buffer *buffer; + struct { + int32_t x, y; + } buffer_hotspot; + float buffer_scale; + + // only when using a surface as the cursor image + struct wlr_surface *surface; + struct { + int32_t x, y; + } surface_hotspot; + struct wl_listener surface_commit; + struct wl_listener surface_destroy; + + // only when using an XCursor as the cursor image + struct wlr_xcursor_manager *xcursor_manager; + char *xcursor_name; +}; + +struct wlr_cursor *wlr_cursor_create(void) { + struct wlr_cursor_state *state = calloc(1, sizeof(*state)); + if (!state) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_cursor_state"); + return NULL; + } + struct wlr_cursor *cur = &state->cursor; + + cur->state = state; + + wl_list_init(&cur->state->devices); + wl_list_init(&cur->state->output_cursors); + + // pointer signals + wl_signal_init(&cur->events.motion); + wl_signal_init(&cur->events.motion_absolute); + wl_signal_init(&cur->events.button); + wl_signal_init(&cur->events.axis); + wl_signal_init(&cur->events.frame); + wl_signal_init(&cur->events.swipe_begin); + wl_signal_init(&cur->events.swipe_update); + wl_signal_init(&cur->events.swipe_end); + wl_signal_init(&cur->events.pinch_begin); + wl_signal_init(&cur->events.pinch_update); + wl_signal_init(&cur->events.pinch_end); + wl_signal_init(&cur->events.hold_begin); + wl_signal_init(&cur->events.hold_end); + + // touch signals + wl_signal_init(&cur->events.touch_up); + wl_signal_init(&cur->events.touch_down); + wl_signal_init(&cur->events.touch_motion); + wl_signal_init(&cur->events.touch_cancel); + wl_signal_init(&cur->events.touch_frame); + + // tablet tool signals + wl_signal_init(&cur->events.tablet_tool_tip); + wl_signal_init(&cur->events.tablet_tool_axis); + wl_signal_init(&cur->events.tablet_tool_button); + wl_signal_init(&cur->events.tablet_tool_proximity); + + wl_list_init(&cur->state->surface_destroy.link); + wl_list_init(&cur->state->surface_commit.link); + + cur->x = 100; + cur->y = 100; + + return cur; +} + +static void cursor_output_cursor_reset_image(struct wlr_cursor_output_cursor *output_cursor); + +static void output_cursor_destroy(struct wlr_cursor_output_cursor *output_cursor) { + cursor_output_cursor_reset_image(output_cursor); + wl_list_remove(&output_cursor->layout_output_destroy.link); + wl_list_remove(&output_cursor->link); + wl_list_remove(&output_cursor->output_commit.link); + wlr_output_cursor_destroy(output_cursor->output_cursor); + free(output_cursor); +} + +static void cursor_detach_output_layout(struct wlr_cursor *cur) { + if (!cur->state->layout) { + return; + } + + struct wlr_cursor_output_cursor *output_cursor, *tmp; + wl_list_for_each_safe(output_cursor, tmp, &cur->state->output_cursors, + link) { + output_cursor_destroy(output_cursor); + } + + wl_list_remove(&cur->state->layout_destroy.link); + wl_list_remove(&cur->state->layout_change.link); + wl_list_remove(&cur->state->layout_add.link); + + cur->state->layout = NULL; +} + +static void cursor_device_destroy(struct wlr_cursor_device *c_device) { + struct wlr_input_device *dev = c_device->device; + switch (dev->type) { + case WLR_INPUT_DEVICE_POINTER: + wl_list_remove(&c_device->motion.link); + wl_list_remove(&c_device->motion_absolute.link); + wl_list_remove(&c_device->button.link); + wl_list_remove(&c_device->axis.link); + wl_list_remove(&c_device->frame.link); + wl_list_remove(&c_device->swipe_begin.link); + wl_list_remove(&c_device->swipe_update.link); + wl_list_remove(&c_device->swipe_end.link); + wl_list_remove(&c_device->pinch_begin.link); + wl_list_remove(&c_device->pinch_update.link); + wl_list_remove(&c_device->pinch_end.link); + wl_list_remove(&c_device->hold_begin.link); + wl_list_remove(&c_device->hold_end.link); + break; + case WLR_INPUT_DEVICE_TOUCH: + wl_list_remove(&c_device->touch_down.link); + wl_list_remove(&c_device->touch_up.link); + wl_list_remove(&c_device->touch_motion.link); + wl_list_remove(&c_device->touch_cancel.link); + wl_list_remove(&c_device->touch_frame.link); + break; + case WLR_INPUT_DEVICE_TABLET: + wl_list_remove(&c_device->tablet_tool_axis.link); + wl_list_remove(&c_device->tablet_tool_proximity.link); + wl_list_remove(&c_device->tablet_tool_tip.link); + wl_list_remove(&c_device->tablet_tool_button.link); + break; + default: + abort(); // unreachable + } + + wl_list_remove(&c_device->link); + wl_list_remove(&c_device->destroy.link); + free(c_device); +} + +static void cursor_reset_image(struct wlr_cursor *cur) { + wlr_buffer_unlock(cur->state->buffer); + cur->state->buffer = NULL; + + if (cur->state->surface != NULL) { + struct wlr_cursor_output_cursor *output_cursor; + wl_list_for_each(output_cursor, &cur->state->output_cursors, link) { + wlr_surface_send_leave(cur->state->surface, + output_cursor->output_cursor->output); + } + } + + wl_list_remove(&cur->state->surface_destroy.link); + wl_list_remove(&cur->state->surface_commit.link); + wl_list_init(&cur->state->surface_destroy.link); + wl_list_init(&cur->state->surface_commit.link); + cur->state->surface = NULL; + + cur->state->xcursor_manager = NULL; + free(cur->state->xcursor_name); + cur->state->xcursor_name = NULL; +} + +void wlr_cursor_destroy(struct wlr_cursor *cur) { + cursor_reset_image(cur); + cursor_detach_output_layout(cur); + + struct wlr_cursor_device *device, *device_tmp = NULL; + wl_list_for_each_safe(device, device_tmp, &cur->state->devices, link) { + cursor_device_destroy(device); + } + + free(cur->state); +} + +static struct wlr_cursor_device *get_cursor_device(struct wlr_cursor *cur, + struct wlr_input_device *device) { + struct wlr_cursor_device *c_device, *ret = NULL; + wl_list_for_each(c_device, &cur->state->devices, link) { + if (c_device->device == device) { + ret = c_device; + break; + } + } + + return ret; +} + +static void output_cursor_move(struct wlr_cursor_output_cursor *output_cursor) { + struct wlr_cursor *cur = output_cursor->cursor; + + double output_x = cur->x, output_y = cur->y; + wlr_output_layout_output_coords(cur->state->layout, + output_cursor->output_cursor->output, &output_x, &output_y); + wlr_output_cursor_move(output_cursor->output_cursor, + output_x, output_y); +} + +static void cursor_warp_unchecked(struct wlr_cursor *cur, + double lx, double ly) { + assert(cur->state->layout); + if (!isfinite(lx) || !isfinite(ly)) { + assert(false); + return; + } + + cur->x = lx; + cur->y = ly; + + struct wlr_cursor_output_cursor *output_cursor; + wl_list_for_each(output_cursor, &cur->state->output_cursors, link) { + output_cursor_move(output_cursor); + } +} + +/** + * Get the most specific mapping box for the device in this order: + * + * 1. device geometry mapping + * 2. device output mapping + * 3. cursor geometry mapping + * 4. cursor output mapping + * + * Absolute movement for touch and pen devices will be relative to this box and + * pointer movement will be constrained to this box. + * + * If none of these are set, the box is empty and absolute movement should be + * relative to the extents of the layout. + */ +static void get_mapping(struct wlr_cursor *cur, + struct wlr_input_device *dev, struct wlr_box *box) { + assert(cur->state->layout); + + *box = (struct wlr_box){0}; + + struct wlr_cursor_device *c_device = get_cursor_device(cur, dev); + if (c_device) { + if (!wlr_box_empty(&c_device->mapped_box)) { + *box = c_device->mapped_box; + return; + } + if (c_device->mapped_output) { + wlr_output_layout_get_box(cur->state->layout, + c_device->mapped_output, box); + return; + } + } + + if (!wlr_box_empty(&cur->state->mapped_box)) { + *box = cur->state->mapped_box; + return; + } + if (cur->state->mapped_output) { + wlr_output_layout_get_box(cur->state->layout, + cur->state->mapped_output, box); + return; + } +} + +bool wlr_cursor_warp(struct wlr_cursor *cur, struct wlr_input_device *dev, + double lx, double ly) { + assert(cur->state->layout); + + bool result = false; + struct wlr_box mapping; + get_mapping(cur, dev, &mapping); + if (!wlr_box_empty(&mapping)) { + result = wlr_box_contains_point(&mapping, lx, ly); + } else { + result = wlr_output_layout_contains_point(cur->state->layout, NULL, + lx, ly); + } + + if (result) { + cursor_warp_unchecked(cur, lx, ly); + } + + return result; +} + +void wlr_cursor_warp_closest(struct wlr_cursor *cur, + struct wlr_input_device *dev, double lx, double ly) { + struct wlr_box mapping; + get_mapping(cur, dev, &mapping); + if (!wlr_box_empty(&mapping)) { + wlr_box_closest_point(&mapping, lx, ly, &lx, &ly); + } else if (!wl_list_empty(&cur->state->layout->outputs)) { + wlr_output_layout_closest_point(cur->state->layout, NULL, lx, ly, + &lx, &ly); + } else { + /* + * There is no mapping box for the input device and the + * output layout is empty. This can happen for example + * when external monitors are turned off/disconnected. + * In this case, all (x,y) points are equally invalid, + * so leave the cursor in its current location (better + * from a user standpoint than warping it to (0,0)). + */ + return; + } + + cursor_warp_unchecked(cur, lx, ly); +} + +void wlr_cursor_absolute_to_layout_coords(struct wlr_cursor *cur, + struct wlr_input_device *dev, double x, double y, + double *lx, double *ly) { + assert(cur->state->layout); + + struct wlr_box mapping; + get_mapping(cur, dev, &mapping); + if (wlr_box_empty(&mapping)) { + wlr_output_layout_get_box(cur->state->layout, NULL, &mapping); + } + + *lx = !isnan(x) ? mapping.width * x + mapping.x : cur->x; + *ly = !isnan(y) ? mapping.height * y + mapping.y : cur->y; +} + +void wlr_cursor_warp_absolute(struct wlr_cursor *cur, + struct wlr_input_device *dev, double x, double y) { + assert(cur->state->layout); + + double lx, ly; + wlr_cursor_absolute_to_layout_coords(cur, dev, x, y, &lx, &ly); + + wlr_cursor_warp_closest(cur, dev, lx, ly); +} + +void wlr_cursor_move(struct wlr_cursor *cur, struct wlr_input_device *dev, + double delta_x, double delta_y) { + assert(cur->state->layout); + + double lx = !isnan(delta_x) ? cur->x + delta_x : cur->x; + double ly = !isnan(delta_y) ? cur->y + delta_y : cur->y; + + wlr_cursor_warp_closest(cur, dev, lx, ly); +} + +static void cursor_output_cursor_reset_image(struct wlr_cursor_output_cursor *output_cursor) { + output_cursor->xcursor = NULL; + output_cursor->xcursor_index = 0; + if (output_cursor->xcursor_timer != NULL) { + wl_event_source_remove(output_cursor->xcursor_timer); + } + output_cursor->xcursor_timer = NULL; +} + +static void cursor_update_outputs(struct wlr_cursor *cur); + +void wlr_cursor_set_buffer(struct wlr_cursor *cur, struct wlr_buffer *buffer, + int32_t hotspot_x, int32_t hotspot_y, float scale) { + if (buffer == cur->state->buffer && + hotspot_x == cur->state->buffer_hotspot.x && + hotspot_y == cur->state->buffer_hotspot.y && + scale == cur->state->buffer_scale) { + return; + } + + cursor_reset_image(cur); + + if (buffer != NULL) { + cur->state->buffer = wlr_buffer_lock(buffer); + cur->state->buffer_hotspot.x = hotspot_x; + cur->state->buffer_hotspot.y = hotspot_y; + cur->state->buffer_scale = scale; + } + + cursor_update_outputs(cur); +} + +void wlr_cursor_unset_image(struct wlr_cursor *cur) { + cursor_reset_image(cur); + cursor_update_outputs(cur); +} + +static void output_cursor_set_xcursor_image(struct wlr_cursor_output_cursor *output_cursor, size_t i); + +static int handle_xcursor_timer(void *data) { + struct wlr_cursor_output_cursor *output_cursor = data; + size_t i = (output_cursor->xcursor_index + 1) % output_cursor->xcursor->image_count; + output_cursor_set_xcursor_image(output_cursor, i); + return 0; +} + +static void output_cursor_set_xcursor_image(struct wlr_cursor_output_cursor *output_cursor, size_t i) { + struct wlr_xcursor_image *image = output_cursor->xcursor->images[i]; + + struct wlr_readonly_data_buffer *ro_buffer = readonly_data_buffer_create( + DRM_FORMAT_ARGB8888, 4 * image->width, image->width, image->height, image->buffer); + if (ro_buffer == NULL) { + return; + } + wlr_output_cursor_set_buffer(output_cursor->output_cursor, &ro_buffer->base, image->hotspot_x, image->hotspot_y); + wlr_buffer_drop(&ro_buffer->base); + + output_cursor->xcursor_index = i; + + if (output_cursor->xcursor->image_count == 1 || image->delay == 0) { + return; + } + + if (output_cursor->xcursor_timer == NULL) { + struct wl_event_loop *event_loop = output_cursor->output_cursor->output->event_loop; + output_cursor->xcursor_timer = + wl_event_loop_add_timer(event_loop, handle_xcursor_timer, output_cursor); + if (output_cursor->xcursor_timer == NULL) { + wlr_log(WLR_ERROR, "wl_event_loop_add_timer failed"); + return; + } + } + + wl_event_source_timer_update(output_cursor->xcursor_timer, image->delay); +} + +static void cursor_output_cursor_update(struct wlr_cursor_output_cursor *output_cursor) { + struct wlr_cursor *cur = output_cursor->cursor; + struct wlr_output *output = output_cursor->output_cursor->output; + + if (!output->enabled) { + return; + } + + cursor_output_cursor_reset_image(output_cursor); + + if (cur->state->buffer != NULL) { + struct wlr_renderer *renderer = output->renderer; + assert(renderer != NULL); + + struct wlr_buffer *buffer = cur->state->buffer; + int32_t hotspot_x = cur->state->buffer_hotspot.x; + int32_t hotspot_y = cur->state->buffer_hotspot.y; + float scale = cur->state->buffer_scale; + + struct wlr_texture *texture = NULL; + struct wlr_fbox src_box = {0}; + int dst_width = 0, dst_height = 0; + if (buffer != NULL) { + texture = wlr_texture_from_buffer(renderer, buffer); + if (texture) { + src_box = (struct wlr_fbox){ + .width = texture->width, + .height = texture->height, + }; + + dst_width = texture->width / scale; + dst_height = texture->height / scale; + } + } + + output_cursor_set_texture(output_cursor->output_cursor, texture, true, + &src_box, dst_width, dst_height, WL_OUTPUT_TRANSFORM_NORMAL, + hotspot_x, hotspot_y); + } else if (cur->state->surface != NULL) { + struct wlr_surface *surface = cur->state->surface; + + struct wlr_texture *texture = wlr_surface_get_texture(surface); + int32_t hotspot_x = cur->state->surface_hotspot.x; + int32_t hotspot_y = cur->state->surface_hotspot.y; + + struct wlr_fbox src_box; + wlr_surface_get_buffer_source_box(surface, &src_box); + int dst_width = surface->current.width; + int dst_height = surface->current.height; + + output_cursor_set_texture(output_cursor->output_cursor, texture, false, + &src_box, dst_width, dst_height, surface->current.transform, + hotspot_x, hotspot_y); + + if (output_cursor->output_cursor->visible) { + wlr_surface_send_enter(surface, output); + } else { + wlr_surface_send_leave(surface, output); + } + + float scale = 1; + struct wlr_surface_output *surface_output; + wl_list_for_each(surface_output, &surface->current_outputs, link) { + if (surface_output->output->scale > scale) { + scale = surface_output->output->scale; + } + } + wlr_fractional_scale_v1_notify_scale(surface, scale); + wlr_surface_set_preferred_buffer_scale(surface, ceil(scale)); + } else if (cur->state->xcursor_name != NULL) { + struct wlr_xcursor_manager *manager = cur->state->xcursor_manager; + const char *name = cur->state->xcursor_name; + + float scale = output->scale; + wlr_xcursor_manager_load(manager, scale); + struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(manager, name, scale); + if (xcursor == NULL) { + wlr_log(WLR_DEBUG, "XCursor theme is missing '%s' cursor", name); + wlr_output_cursor_set_buffer(output_cursor->output_cursor, NULL, 0, 0); + return; + } + + output_cursor->xcursor = xcursor; + output_cursor_set_xcursor_image(output_cursor, 0); + } else { + wlr_output_cursor_set_buffer(output_cursor->output_cursor, NULL, 0, 0); + } +} + +static void output_cursor_output_handle_output_commit( + struct wl_listener *listener, void *data) { + struct wlr_cursor_output_cursor *output_cursor = + wl_container_of(listener, output_cursor, output_commit); + const struct wlr_output_event_commit *event = data; + + if (event->state->committed & (WLR_OUTPUT_STATE_SCALE | WLR_OUTPUT_STATE_TRANSFORM + | WLR_OUTPUT_STATE_ENABLED)) { + cursor_output_cursor_update(output_cursor); + } + + struct wlr_surface *surface = output_cursor->cursor->state->surface; + if (surface && output_cursor->output_cursor->visible && + (event->state->committed & WLR_OUTPUT_STATE_BUFFER)) { + wlr_surface_send_frame_done(surface, event->when); + } +} + +static void cursor_update_outputs(struct wlr_cursor *cur) { + struct wlr_cursor_output_cursor *output_cursor; + wl_list_for_each(output_cursor, &cur->state->output_cursors, link) { + cursor_output_cursor_update(output_cursor); + } +} + +void wlr_cursor_set_xcursor(struct wlr_cursor *cur, + struct wlr_xcursor_manager *manager, const char *name) { + if (manager == cur->state->xcursor_manager && + cur->state->xcursor_name != NULL && + strcmp(name, cur->state->xcursor_name) == 0) { + return; + } + + cursor_reset_image(cur); + + cur->state->xcursor_manager = manager; + cur->state->xcursor_name = strdup(name); + + cursor_update_outputs(cur); +} + +static void cursor_handle_surface_destroy(struct wl_listener *listener, void *data) { + struct wlr_cursor_state *state = wl_container_of(listener, state, surface_destroy); + assert(state->surface != NULL); + wlr_cursor_unset_image(&state->cursor); +} + +static void cursor_handle_surface_commit(struct wl_listener *listener, void *data) { + struct wlr_cursor_state *state = wl_container_of(listener, state, surface_commit); + struct wlr_surface *surface = state->surface; + + state->surface_hotspot.x -= surface->current.dx; + state->surface_hotspot.y -= surface->current.dy; + + cursor_update_outputs(&state->cursor); +} + +void wlr_cursor_set_surface(struct wlr_cursor *cur, struct wlr_surface *surface, + int32_t hotspot_x, int32_t hotspot_y) { + if (surface == NULL) { + wlr_cursor_unset_image(cur); + return; + } + + if (surface == cur->state->surface && + hotspot_x == cur->state->surface_hotspot.x && + hotspot_y == cur->state->surface_hotspot.y) { + return; + } + + if (surface != cur->state->surface) { + // Only send wl_surface.leave if the surface changes + cursor_reset_image(cur); + + cur->state->surface = surface; + + wl_signal_add(&surface->events.destroy, &cur->state->surface_destroy); + cur->state->surface_destroy.notify = cursor_handle_surface_destroy; + wl_signal_add(&surface->events.commit, &cur->state->surface_commit); + cur->state->surface_commit.notify = cursor_handle_surface_commit; + } + + cur->state->surface_hotspot.x = hotspot_x; + cur->state->surface_hotspot.y = hotspot_y; + + cursor_update_outputs(cur); +} + +static void handle_pointer_motion(struct wl_listener *listener, void *data) { + struct wlr_pointer_motion_event *event = data; + struct wlr_cursor_device *device = + wl_container_of(listener, device, motion); + wl_signal_emit_mutable(&device->cursor->events.motion, event); +} + +static void apply_output_transform(double *x, double *y, + enum wl_output_transform transform) { + double dx = 0.0, dy = 0.0; + double width = 1.0, height = 1.0; + + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + dx = *x; + dy = *y; + break; + case WL_OUTPUT_TRANSFORM_90: + dx = height - *y; + dy = *x; + break; + case WL_OUTPUT_TRANSFORM_180: + dx = width - *x; + dy = height - *y; + break; + case WL_OUTPUT_TRANSFORM_270: + dx = *y; + dy = width - *x; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + dx = width - *x; + dy = *y; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + dx = *y; + dy = *x; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + dx = *x; + dy = height - *y; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + dx = height - *y; + dy = width - *x; + break; + } + *x = dx; + *y = dy; +} + + +static struct wlr_output *get_mapped_output(struct wlr_cursor_device *cursor_device) { + if (cursor_device->mapped_output) { + return cursor_device->mapped_output; + } + + struct wlr_cursor *cursor = cursor_device->cursor; + assert(cursor); + if (cursor->state->mapped_output) { + return cursor->state->mapped_output; + } + return NULL; +} + + +static void handle_pointer_motion_absolute(struct wl_listener *listener, + void *data) { + struct wlr_pointer_motion_absolute_event *event = data; + struct wlr_cursor_device *device = + wl_container_of(listener, device, motion_absolute); + + struct wlr_output *output = + get_mapped_output(device); + if (output) { + apply_output_transform(&event->x, &event->y, output->transform); + } + wl_signal_emit_mutable(&device->cursor->events.motion_absolute, event); +} + +static void handle_pointer_button(struct wl_listener *listener, void *data) { + struct wlr_pointer_button_event *event = data; + struct wlr_cursor_device *device = + wl_container_of(listener, device, button); + wl_signal_emit_mutable(&device->cursor->events.button, event); +} + +static void handle_pointer_axis(struct wl_listener *listener, void *data) { + struct wlr_pointer_axis_event *event = data; + struct wlr_cursor_device *device = wl_container_of(listener, device, axis); + wl_signal_emit_mutable(&device->cursor->events.axis, event); +} + +static void handle_pointer_frame(struct wl_listener *listener, void *data) { + struct wlr_cursor_device *device = wl_container_of(listener, device, frame); + wl_signal_emit_mutable(&device->cursor->events.frame, device->cursor); +} + +static void handle_pointer_swipe_begin(struct wl_listener *listener, void *data) { + struct wlr_pointer_swipe_begin_event *event = data; + struct wlr_cursor_device *device = wl_container_of(listener, device, swipe_begin); + wl_signal_emit_mutable(&device->cursor->events.swipe_begin, event); +} + +static void handle_pointer_swipe_update(struct wl_listener *listener, void *data) { + struct wlr_pointer_swipe_update_event *event = data; + struct wlr_cursor_device *device = wl_container_of(listener, device, swipe_update); + wl_signal_emit_mutable(&device->cursor->events.swipe_update, event); +} + +static void handle_pointer_swipe_end(struct wl_listener *listener, void *data) { + struct wlr_pointer_swipe_end_event *event = data; + struct wlr_cursor_device *device = wl_container_of(listener, device, swipe_end); + wl_signal_emit_mutable(&device->cursor->events.swipe_end, event); +} + +static void handle_pointer_pinch_begin(struct wl_listener *listener, void *data) { + struct wlr_pointer_pinch_begin_event *event = data; + struct wlr_cursor_device *device = wl_container_of(listener, device, pinch_begin); + wl_signal_emit_mutable(&device->cursor->events.pinch_begin, event); +} + +static void handle_pointer_pinch_update(struct wl_listener *listener, void *data) { + struct wlr_pointer_pinch_update_event *event = data; + struct wlr_cursor_device *device = wl_container_of(listener, device, pinch_update); + wl_signal_emit_mutable(&device->cursor->events.pinch_update, event); +} + +static void handle_pointer_pinch_end(struct wl_listener *listener, void *data) { + struct wlr_pointer_pinch_end_event *event = data; + struct wlr_cursor_device *device = wl_container_of(listener, device, pinch_end); + wl_signal_emit_mutable(&device->cursor->events.pinch_end, event); +} + +static void handle_pointer_hold_begin(struct wl_listener *listener, void *data) { + struct wlr_pointer_hold_begin_event *event = data; + struct wlr_cursor_device *device = wl_container_of(listener, device, hold_begin); + wl_signal_emit_mutable(&device->cursor->events.hold_begin, event); +} + +static void handle_pointer_hold_end(struct wl_listener *listener, void *data) { + struct wlr_pointer_hold_end_event *event = data; + struct wlr_cursor_device *device = wl_container_of(listener, device, hold_end); + wl_signal_emit_mutable(&device->cursor->events.hold_end, event); +} + +static void handle_touch_up(struct wl_listener *listener, void *data) { + struct wlr_touch_up_event *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, touch_up); + wl_signal_emit_mutable(&device->cursor->events.touch_up, event); +} + +static void handle_touch_down(struct wl_listener *listener, void *data) { + struct wlr_touch_down_event *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, touch_down); + + struct wlr_output *output = + get_mapped_output(device); + if (output) { + apply_output_transform(&event->x, &event->y, output->transform); + } + wl_signal_emit_mutable(&device->cursor->events.touch_down, event); +} + +static void handle_touch_motion(struct wl_listener *listener, void *data) { + struct wlr_touch_motion_event *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, touch_motion); + + struct wlr_output *output = + get_mapped_output(device); + if (output) { + apply_output_transform(&event->x, &event->y, output->transform); + } + wl_signal_emit_mutable(&device->cursor->events.touch_motion, event); +} + +static void handle_touch_cancel(struct wl_listener *listener, void *data) { + struct wlr_touch_cancel_event *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, touch_cancel); + wl_signal_emit_mutable(&device->cursor->events.touch_cancel, event); +} + +static void handle_touch_frame(struct wl_listener *listener, void *data) { + struct wlr_cursor_device *device = + wl_container_of(listener, device, touch_frame); + wl_signal_emit_mutable(&device->cursor->events.touch_frame, NULL); +} + +static void handle_tablet_tool_tip(struct wl_listener *listener, void *data) { + struct wlr_tablet_tool_tip_event *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, tablet_tool_tip); + + struct wlr_output *output = + get_mapped_output(device); + if (output) { + apply_output_transform(&event->x, &event->y, output->transform); + } + wl_signal_emit_mutable(&device->cursor->events.tablet_tool_tip, event); +} + +static void handle_tablet_tool_axis(struct wl_listener *listener, void *data) { + struct wlr_tablet_tool_axis_event *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, tablet_tool_axis); + + struct wlr_output *output = get_mapped_output(device); + if (output) { + // In the case that only one axis received an event, rotating the input can + // cause the change to actually happen on the other axis, as far as clients + // are concerned. + // + // Here, we feed apply_output_transform NAN on the axis that didn't change, + // and remap the axes flags based on whether it returns NAN itself. + double x = event->updated_axes & WLR_TABLET_TOOL_AXIS_X ? event->x : NAN; + double y = event->updated_axes & WLR_TABLET_TOOL_AXIS_Y ? event->y : NAN; + + apply_output_transform(&x, &y, output->transform); + + event->updated_axes &= ~(WLR_TABLET_TOOL_AXIS_X | WLR_TABLET_TOOL_AXIS_Y); + event->x = event->y = 0; + + if (!isnan(x)) { + event->updated_axes |= WLR_TABLET_TOOL_AXIS_X; + event->x = x; + } + + if (!isnan(y)) { + event->updated_axes |= WLR_TABLET_TOOL_AXIS_Y; + event->y = y; + } + } + + wl_signal_emit_mutable(&device->cursor->events.tablet_tool_axis, event); +} + +static void handle_tablet_tool_button(struct wl_listener *listener, + void *data) { + struct wlr_tablet_tool_button *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, tablet_tool_button); + wl_signal_emit_mutable(&device->cursor->events.tablet_tool_button, event); +} + +static void handle_tablet_tool_proximity(struct wl_listener *listener, + void *data) { + struct wlr_tablet_tool_proximity_event *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, tablet_tool_proximity); + + struct wlr_output *output = + get_mapped_output(device); + if (output) { + apply_output_transform(&event->x, &event->y, output->transform); + } + wl_signal_emit_mutable(&device->cursor->events.tablet_tool_proximity, event); +} + +static void handle_device_destroy(struct wl_listener *listener, void *data) { + struct wlr_cursor_device *c_device; + c_device = wl_container_of(listener, c_device, destroy); + wlr_cursor_detach_input_device(c_device->cursor, c_device->device); +} + +static struct wlr_cursor_device *cursor_device_create( + struct wlr_cursor *cursor, struct wlr_input_device *device) { + struct wlr_cursor_device *c_device = calloc(1, sizeof(*c_device)); + if (!c_device) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_cursor_device"); + return NULL; + } + + c_device->cursor = cursor; + c_device->device = device; + + // listen to events + wl_signal_add(&device->events.destroy, &c_device->destroy); + c_device->destroy.notify = handle_device_destroy; + + switch (device->type) { + case WLR_INPUT_DEVICE_POINTER:; + struct wlr_pointer *pointer = wlr_pointer_from_input_device(device); + + wl_signal_add(&pointer->events.motion, &c_device->motion); + c_device->motion.notify = handle_pointer_motion; + + wl_signal_add(&pointer->events.motion_absolute, + &c_device->motion_absolute); + c_device->motion_absolute.notify = handle_pointer_motion_absolute; + + wl_signal_add(&pointer->events.button, &c_device->button); + c_device->button.notify = handle_pointer_button; + + wl_signal_add(&pointer->events.axis, &c_device->axis); + c_device->axis.notify = handle_pointer_axis; + + wl_signal_add(&pointer->events.frame, &c_device->frame); + c_device->frame.notify = handle_pointer_frame; + + wl_signal_add(&pointer->events.swipe_begin, &c_device->swipe_begin); + c_device->swipe_begin.notify = handle_pointer_swipe_begin; + + wl_signal_add(&pointer->events.swipe_update, &c_device->swipe_update); + c_device->swipe_update.notify = handle_pointer_swipe_update; + + wl_signal_add(&pointer->events.swipe_end, &c_device->swipe_end); + c_device->swipe_end.notify = handle_pointer_swipe_end; + + wl_signal_add(&pointer->events.pinch_begin, &c_device->pinch_begin); + c_device->pinch_begin.notify = handle_pointer_pinch_begin; + + wl_signal_add(&pointer->events.pinch_update, &c_device->pinch_update); + c_device->pinch_update.notify = handle_pointer_pinch_update; + + wl_signal_add(&pointer->events.pinch_end, &c_device->pinch_end); + c_device->pinch_end.notify = handle_pointer_pinch_end; + + wl_signal_add(&pointer->events.hold_begin, &c_device->hold_begin); + c_device->hold_begin.notify = handle_pointer_hold_begin; + + wl_signal_add(&pointer->events.hold_end, &c_device->hold_end); + c_device->hold_end.notify = handle_pointer_hold_end; + + break; + case WLR_INPUT_DEVICE_TOUCH:; + struct wlr_touch *touch = wlr_touch_from_input_device(device); + + wl_signal_add(&touch->events.motion, &c_device->touch_motion); + c_device->touch_motion.notify = handle_touch_motion; + + wl_signal_add(&touch->events.down, &c_device->touch_down); + c_device->touch_down.notify = handle_touch_down; + + wl_signal_add(&touch->events.up, &c_device->touch_up); + c_device->touch_up.notify = handle_touch_up; + + wl_signal_add(&touch->events.cancel, &c_device->touch_cancel); + c_device->touch_cancel.notify = handle_touch_cancel; + + wl_signal_add(&touch->events.frame, &c_device->touch_frame); + c_device->touch_frame.notify = handle_touch_frame; + + break; + case WLR_INPUT_DEVICE_TABLET:; + struct wlr_tablet *tablet = wlr_tablet_from_input_device(device); + + wl_signal_add(&tablet->events.tip, &c_device->tablet_tool_tip); + c_device->tablet_tool_tip.notify = handle_tablet_tool_tip; + + wl_signal_add(&tablet->events.proximity, + &c_device->tablet_tool_proximity); + c_device->tablet_tool_proximity.notify = handle_tablet_tool_proximity; + + wl_signal_add(&tablet->events.axis, &c_device->tablet_tool_axis); + c_device->tablet_tool_axis.notify = handle_tablet_tool_axis; + + wl_signal_add(&tablet->events.button, &c_device->tablet_tool_button); + c_device->tablet_tool_button.notify = handle_tablet_tool_button; + + break; + + default: + abort(); // unreachable + } + + wl_list_insert(&cursor->state->devices, &c_device->link); + + return c_device; +} + +void wlr_cursor_attach_input_device(struct wlr_cursor *cur, + struct wlr_input_device *dev) { + switch (dev->type) { + case WLR_INPUT_DEVICE_POINTER: + case WLR_INPUT_DEVICE_TOUCH: + case WLR_INPUT_DEVICE_TABLET: + break; + default: + wlr_log(WLR_ERROR, "only device types of pointer, touch or tablet tool" + "are supported"); + return; + } + + // make sure it is not already attached + struct wlr_cursor_device *_dev; + wl_list_for_each(_dev, &cur->state->devices, link) { + if (_dev->device == dev) { + return; + } + } + + cursor_device_create(cur, dev); +} + +void wlr_cursor_detach_input_device(struct wlr_cursor *cur, + struct wlr_input_device *dev) { + struct wlr_cursor_device *c_device, *tmp = NULL; + wl_list_for_each_safe(c_device, tmp, &cur->state->devices, link) { + if (c_device->device == dev) { + cursor_device_destroy(c_device); + } + } +} + +static void handle_layout_destroy(struct wl_listener *listener, void *data) { + struct wlr_cursor_state *state = + wl_container_of(listener, state, layout_destroy); + cursor_detach_output_layout(&state->cursor); +} + +static void handle_layout_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_cursor_output_cursor *output_cursor = + wl_container_of(listener, output_cursor, layout_output_destroy); + output_cursor_destroy(output_cursor); +} + +static void layout_add(struct wlr_cursor_state *state, + struct wlr_output_layout_output *l_output) { + struct wlr_cursor_output_cursor *output_cursor; + wl_list_for_each(output_cursor, &state->output_cursors, link) { + if (output_cursor->output_cursor->output == l_output->output) { + return; // already added + } + } + + output_cursor = calloc(1, sizeof(*output_cursor)); + if (output_cursor == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_cursor_output_cursor"); + return; + } + output_cursor->cursor = &state->cursor; + + output_cursor->output_cursor = wlr_output_cursor_create(l_output->output); + if (output_cursor->output_cursor == NULL) { + wlr_log(WLR_ERROR, "Failed to create wlr_output_cursor"); + free(output_cursor); + return; + } + + output_cursor->layout_output_destroy.notify = handle_layout_output_destroy; + wl_signal_add(&l_output->events.destroy, + &output_cursor->layout_output_destroy); + + wl_list_insert(&state->output_cursors, &output_cursor->link); + + wl_signal_add(&output_cursor->output_cursor->output->events.commit, + &output_cursor->output_commit); + output_cursor->output_commit.notify = output_cursor_output_handle_output_commit; + + output_cursor_move(output_cursor); + cursor_output_cursor_update(output_cursor); +} + +static void handle_layout_add(struct wl_listener *listener, void *data) { + struct wlr_cursor_state *state = + wl_container_of(listener, state, layout_add); + struct wlr_output_layout_output *l_output = data; + layout_add(state, l_output); +} + +static void handle_layout_change(struct wl_listener *listener, void *data) { + struct wlr_cursor_state *state = + wl_container_of(listener, state, layout_change); + struct wlr_output_layout *layout = data; + + if (!wlr_output_layout_contains_point(layout, NULL, state->cursor.x, + state->cursor.y) && !wl_list_empty(&layout->outputs)) { + // the output we were on has gone away so go to the closest boundary + // point (unless the layout is empty; compare warp_closest()) + double x, y; + wlr_output_layout_closest_point(layout, NULL, state->cursor.x, + state->cursor.y, &x, &y); + + cursor_warp_unchecked(&state->cursor, x, y); + } +} + +void wlr_cursor_attach_output_layout(struct wlr_cursor *cur, + struct wlr_output_layout *l) { + cursor_detach_output_layout(cur); + + if (l == NULL) { + return; + } + + wl_signal_add(&l->events.add, &cur->state->layout_add); + cur->state->layout_add.notify = handle_layout_add; + wl_signal_add(&l->events.change, &cur->state->layout_change); + cur->state->layout_change.notify = handle_layout_change; + wl_signal_add(&l->events.destroy, &cur->state->layout_destroy); + cur->state->layout_destroy.notify = handle_layout_destroy; + + cur->state->layout = l; + + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &l->outputs, link) { + layout_add(cur->state, l_output); + } +} + +void wlr_cursor_map_to_output(struct wlr_cursor *cur, + struct wlr_output *output) { + cur->state->mapped_output = output; +} + +void wlr_cursor_map_input_to_output(struct wlr_cursor *cur, + struct wlr_input_device *dev, struct wlr_output *output) { + struct wlr_cursor_device *c_device = get_cursor_device(cur, dev); + if (!c_device) { + wlr_log(WLR_ERROR, "Cannot map device \"%s\" to output" + " (not found in this cursor)", dev->name); + return; + } + + c_device->mapped_output = output; +} + +void wlr_cursor_map_to_region(struct wlr_cursor *cur, + const struct wlr_box *box) { + cur->state->mapped_box = wlr_box_empty(box) ? (struct wlr_box){0} : *box; +} + +void wlr_cursor_map_input_to_region(struct wlr_cursor *cur, + struct wlr_input_device *dev, const struct wlr_box *box) { + struct wlr_cursor_device *c_device = get_cursor_device(cur, dev); + if (!c_device) { + wlr_log(WLR_ERROR, "Cannot map device \"%s\" to geometry (not found in" + "this cursor)", dev->name); + return; + } + + c_device->mapped_box = wlr_box_empty(box) ? (struct wlr_box){0} : *box; +} diff --git a/types/wlr_cursor_shape_v1.c b/types/wlr_cursor_shape_v1.c new file mode 100644 index 0000000..1bd1126 --- /dev/null +++ b/types/wlr_cursor_shape_v1.c @@ -0,0 +1,262 @@ +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_tablet_v2.h" + +#define CURSOR_SHAPE_MANAGER_V1_VERSION 1 + +struct wlr_cursor_shape_device_v1 { + struct wl_resource *resource; + struct wlr_cursor_shape_manager_v1 *manager; + enum wlr_cursor_shape_manager_v1_device_type type; + struct wlr_seat_client *seat_client; + // NULL if device_type is not TABLET_TOOL + struct wlr_tablet_v2_tablet_tool *tablet_tool; + + struct wl_listener seat_client_destroy; + struct wl_listener tablet_tool_destroy; +}; + +static const struct wp_cursor_shape_device_v1_interface device_impl; +static const struct wp_cursor_shape_manager_v1_interface manager_impl; + +// Returns NULL if the resource is inert +static struct wlr_cursor_shape_device_v1 *device_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_cursor_shape_device_v1_interface, &device_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_cursor_shape_manager_v1 *manager_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_cursor_shape_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void resource_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void device_handle_set_shape(struct wl_client *client, struct wl_resource *device_resource, + uint32_t serial, uint32_t shape) { + struct wlr_cursor_shape_device_v1 *device = device_from_resource(device_resource); + if (device == NULL) { + return; + } + + if (shape == 0 || shape > WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT) { + wl_resource_post_error(device_resource, WP_CURSOR_SHAPE_DEVICE_V1_ERROR_INVALID_SHAPE, + "Invalid shape %"PRIu32, shape); + return; + } + + struct wlr_cursor_shape_manager_v1_request_set_shape_event event = { + .seat_client = device->seat_client, + .device_type = device->type, + .tablet_tool = device->tablet_tool, + .serial = serial, + .shape = shape, + }; + wl_signal_emit_mutable(&device->manager->events.request_set_shape, &event); +} + +static const struct wp_cursor_shape_device_v1_interface device_impl = { + .destroy = resource_handle_destroy, + .set_shape = device_handle_set_shape, +}; + +static void device_destroy(struct wlr_cursor_shape_device_v1 *device) { + if (device == NULL) { + return; + } + wl_list_remove(&device->seat_client_destroy.link); + wl_list_remove(&device->tablet_tool_destroy.link); + wl_resource_set_user_data(device->resource, NULL); // make inert + free(device); +} + +static void device_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_cursor_shape_device_v1 *device = device_from_resource(resource); + device_destroy(device); +} + +static void device_handle_seat_client_destroy(struct wl_listener *listener, void *data) { + struct wlr_cursor_shape_device_v1 *device = wl_container_of(listener, device, seat_client_destroy); + device_destroy(device); +} + +static void device_handle_tablet_tool_destroy(struct wl_listener *listener, void *data) { + struct wlr_cursor_shape_device_v1 *device = wl_container_of(listener, device, tablet_tool_destroy); + device_destroy(device); +} + +static void create_device(struct wl_resource *manager_resource, uint32_t id, + struct wlr_seat_client *seat_client, + enum wlr_cursor_shape_manager_v1_device_type type, + struct wlr_tablet_v2_tablet_tool *tablet_tool) { + struct wlr_cursor_shape_manager_v1 *manager = manager_from_resource(manager_resource); + + struct wl_client *client = wl_resource_get_client(manager_resource); + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *device_resource = wl_resource_create(client, + &wp_cursor_shape_device_v1_interface, version, id); + if (device_resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(device_resource, + &device_impl, NULL, device_handle_resource_destroy); + + if (seat_client == NULL) { + return; // leave the resource inert + } + + struct wlr_cursor_shape_device_v1 *device = calloc(1, sizeof(*device)); + if (device == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + assert((type == WLR_CURSOR_SHAPE_MANAGER_V1_DEVICE_TYPE_TABLET_TOOL) == + (tablet_tool != NULL)); + + device->resource = device_resource; + device->manager = manager; + device->type = type; + device->tablet_tool = tablet_tool; + device->seat_client = seat_client; + + device->seat_client_destroy.notify = device_handle_seat_client_destroy; + wl_signal_add(&seat_client->events.destroy, &device->seat_client_destroy); + + if (tablet_tool != NULL) { + device->tablet_tool_destroy.notify = device_handle_tablet_tool_destroy; + wl_signal_add(&tablet_tool->wlr_tool->events.destroy, &device->tablet_tool_destroy); + } else { + wl_list_init(&device->tablet_tool_destroy.link); + } + + wl_resource_set_user_data(device_resource, device); +} + +static void manager_handle_get_pointer(struct wl_client *client, struct wl_resource *manager_resource, + uint32_t id, struct wl_resource *pointer_resource) { + struct wlr_seat_client *seat_client = wlr_seat_client_from_pointer_resource(pointer_resource); + create_device(manager_resource, id, seat_client, + WLR_CURSOR_SHAPE_MANAGER_V1_DEVICE_TYPE_POINTER, NULL); +} + +static void manager_handle_get_tablet_tool_v2(struct wl_client *client, struct wl_resource *manager_resource, + uint32_t id, struct wl_resource *tablet_tool_resource) { + struct wlr_tablet_tool_client_v2 *tablet_tool_client = tablet_tool_client_from_resource(tablet_tool_resource); + + struct wlr_seat_client *seat_client = NULL; + struct wlr_tablet_v2_tablet_tool *tablet_tool = NULL; + if (tablet_tool_client != NULL && tablet_tool_client->tool != NULL) { + seat_client = tablet_tool_client->seat->seat_client; + tablet_tool = tablet_tool_client->tool; + } + + create_device(manager_resource, id, seat_client, + WLR_CURSOR_SHAPE_MANAGER_V1_DEVICE_TYPE_TABLET_TOOL, tablet_tool); +} + +static const struct wp_cursor_shape_manager_v1_interface manager_impl = { + .destroy = resource_handle_destroy, + .get_pointer = manager_handle_get_pointer, + .get_tablet_tool_v2 = manager_handle_get_tablet_tool_v2, +}; + +static void manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_cursor_shape_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_cursor_shape_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_cursor_shape_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + wl_global_destroy(manager->global); + wl_list_remove(&manager->display_destroy.link); + free(manager); +} + +struct wlr_cursor_shape_manager_v1 *wlr_cursor_shape_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= CURSOR_SHAPE_MANAGER_V1_VERSION); + + struct wlr_cursor_shape_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &wp_cursor_shape_manager_v1_interface, version, manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.request_set_shape); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +static const char *const shape_names[] = { + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT] = "default", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU] = "context-menu", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP] = "help", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER] = "pointer", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS] = "progress", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT] = "wait", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL] = "cell", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR] = "crosshair", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT] = "text", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT] = "vertical-text", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS] = "alias", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY] = "copy", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE] = "move", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP] = "no-drop", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED] = "not-allowed", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB] = "grab", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING] = "grabbing", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE] = "e-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE] = "n-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE] = "ne-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE] = "nw-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE] = "s-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE] = "se-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE] = "sw-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE] = "w-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE] = "ew-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE] = "ns-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE] = "nesw-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE] = "nwse-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE] = "col-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE] = "row-resize", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL] = "all-scroll", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN] = "zoom-in", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT] = "zoom-out", +}; + +const char *wlr_cursor_shape_v1_name(enum wp_cursor_shape_device_v1_shape shape) { + assert(shape < sizeof(shape_names) / sizeof(shape_names[0])); + return shape_names[shape]; +} diff --git a/types/wlr_damage_ring.c b/types/wlr_damage_ring.c new file mode 100644 index 0000000..2934de8 --- /dev/null +++ b/types/wlr_damage_ring.c @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include +#include +#include + +#define WLR_DAMAGE_RING_MAX_RECTS 20 + +void wlr_damage_ring_init(struct wlr_damage_ring *ring) { + *ring = (struct wlr_damage_ring){ + .width = INT_MAX, + .height = INT_MAX, + }; + + pixman_region32_init(&ring->current); + for (size_t i = 0; i < WLR_DAMAGE_RING_PREVIOUS_LEN; ++i) { + pixman_region32_init(&ring->previous[i]); + } + + wl_list_init(&ring->buffers); +} + +static void buffer_destroy(struct wlr_damage_ring_buffer *entry) { + wl_list_remove(&entry->destroy.link); + wl_list_remove(&entry->link); + pixman_region32_fini(&entry->damage); + free(entry); +} + +void wlr_damage_ring_finish(struct wlr_damage_ring *ring) { + pixman_region32_fini(&ring->current); + for (size_t i = 0; i < WLR_DAMAGE_RING_PREVIOUS_LEN; ++i) { + pixman_region32_fini(&ring->previous[i]); + } + struct wlr_damage_ring_buffer *entry, *tmp_entry; + wl_list_for_each_safe(entry, tmp_entry, &ring->buffers, link) { + buffer_destroy(entry); + } +} + +void wlr_damage_ring_set_bounds(struct wlr_damage_ring *ring, + int32_t width, int32_t height) { + if (width == 0 || height == 0) { + width = INT_MAX; + height = INT_MAX; + } + + if (ring->width == width && ring->height == height) { + return; + } + + ring->width = width; + ring->height = height; + wlr_damage_ring_add_whole(ring); +} + +bool wlr_damage_ring_add(struct wlr_damage_ring *ring, + const pixman_region32_t *damage) { + pixman_region32_t clipped; + pixman_region32_init(&clipped); + pixman_region32_intersect_rect(&clipped, damage, + 0, 0, ring->width, ring->height); + bool intersects = pixman_region32_not_empty(&clipped); + if (intersects) { + pixman_region32_union(&ring->current, &ring->current, &clipped); + } + pixman_region32_fini(&clipped); + return intersects; +} + +bool wlr_damage_ring_add_box(struct wlr_damage_ring *ring, + const struct wlr_box *box) { + struct wlr_box clipped = { + .x = 0, + .y = 0, + .width = ring->width, + .height = ring->height, + }; + if (wlr_box_intersection(&clipped, &clipped, box)) { + pixman_region32_union_rect(&ring->current, + &ring->current, clipped.x, clipped.y, + clipped.width, clipped.height); + return true; + } + return false; +} + +void wlr_damage_ring_add_whole(struct wlr_damage_ring *ring) { + pixman_region32_union_rect(&ring->current, + &ring->current, 0, 0, ring->width, ring->height); +} + +void wlr_damage_ring_rotate(struct wlr_damage_ring *ring) { + // modular decrement + ring->previous_idx = ring->previous_idx + + WLR_DAMAGE_RING_PREVIOUS_LEN - 1; + ring->previous_idx %= WLR_DAMAGE_RING_PREVIOUS_LEN; + + pixman_region32_copy(&ring->previous[ring->previous_idx], &ring->current); + pixman_region32_clear(&ring->current); +} + +void wlr_damage_ring_get_buffer_damage(struct wlr_damage_ring *ring, + int buffer_age, pixman_region32_t *damage) { + if (buffer_age <= 0 || buffer_age - 1 > WLR_DAMAGE_RING_PREVIOUS_LEN) { + pixman_region32_clear(damage); + pixman_region32_union_rect(damage, damage, + 0, 0, ring->width, ring->height); + } else { + pixman_region32_copy(damage, &ring->current); + + // Accumulate damage from old buffers + for (int i = 0; i < buffer_age - 1; ++i) { + int j = (ring->previous_idx + i) % WLR_DAMAGE_RING_PREVIOUS_LEN; + pixman_region32_union(damage, damage, &ring->previous[j]); + } + + // Check the number of rectangles + int n_rects = pixman_region32_n_rects(damage); + if (n_rects > WLR_DAMAGE_RING_MAX_RECTS) { + pixman_box32_t *extents = pixman_region32_extents(damage); + pixman_region32_union_rect(damage, damage, + extents->x1, extents->y1, + extents->x2 - extents->x1, + extents->y2 - extents->y1); + } + } +} + +static void entry_squash_damage(struct wlr_damage_ring_buffer *entry) { + pixman_region32_t *prev; + if (entry->link.prev == &entry->ring->buffers) { + // this entry is the first in the list + prev = &entry->ring->current; + } else { + struct wlr_damage_ring_buffer *last = + wl_container_of(entry->link.prev, last, link); + prev = &last->damage; + } + + pixman_region32_union(prev, prev, &entry->damage); +} + +static void buffer_handle_destroy(struct wl_listener *listener, void *data) { + struct wlr_damage_ring_buffer *entry = wl_container_of(listener, entry, destroy); + entry_squash_damage(entry); + buffer_destroy(entry); +} + +void wlr_damage_ring_rotate_buffer(struct wlr_damage_ring *ring, + struct wlr_buffer *buffer, pixman_region32_t *damage) { + pixman_region32_copy(damage, &ring->current); + + struct wlr_damage_ring_buffer *entry; + wl_list_for_each(entry, &ring->buffers, link) { + if (entry->buffer != buffer) { + pixman_region32_union(damage, damage, &entry->damage); + continue; + } + + // Check the number of rectangles + int n_rects = pixman_region32_n_rects(damage); + if (n_rects > WLR_DAMAGE_RING_MAX_RECTS) { + pixman_box32_t *extents = pixman_region32_extents(damage); + pixman_region32_union_rect(damage, damage, + extents->x1, extents->y1, + extents->x2 - extents->x1, + extents->y2 - extents->y1); + } + + // rotate + entry_squash_damage(entry); + pixman_region32_copy(&entry->damage, &ring->current); + pixman_region32_clear(&ring->current); + + wl_list_remove(&entry->link); + wl_list_insert(&ring->buffers, &entry->link); + return; + } + + pixman_region32_clear(damage); + pixman_region32_union_rect(damage, damage, + 0, 0, ring->width, ring->height); + + entry = calloc(1, sizeof(*entry)); + if (!entry) { + return; + } + + pixman_region32_init(&entry->damage); + pixman_region32_copy(&entry->damage, &ring->current); + pixman_region32_clear(&ring->current); + + wl_list_insert(&ring->buffers, &entry->link); + entry->buffer = buffer; + entry->ring = ring; + + entry->destroy.notify = buffer_handle_destroy; + wl_signal_add(&buffer->events.destroy, &entry->destroy); +} diff --git a/types/wlr_data_control_v1.c b/types/wlr_data_control_v1.c new file mode 100644 index 0000000..88f23ae --- /dev/null +++ b/types/wlr_data_control_v1.c @@ -0,0 +1,696 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "wlr-data-control-unstable-v1-protocol.h" + +#define DATA_CONTROL_MANAGER_VERSION 2 + +struct data_control_source { + struct wl_resource *resource; + struct wl_array mime_types; + bool finalized; + + // Only one of these is non-NULL. + struct wlr_data_source *active_source; + struct wlr_primary_selection_source *active_primary_source; +}; + +static const struct zwlr_data_control_source_v1_interface source_impl; + +static struct data_control_source *source_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_data_control_source_v1_interface, &source_impl)); + return wl_resource_get_user_data(resource); +} + +static void source_handle_offer(struct wl_client *client, + struct wl_resource *resource, const char *mime_type) { + struct data_control_source *source = source_from_resource(resource); + if (source == NULL) { + return; + } + + if (source->finalized) { + wl_resource_post_error(resource, + ZWLR_DATA_CONTROL_SOURCE_V1_ERROR_INVALID_OFFER, + "cannot mutate offer after set_selection or " + "set_primary_selection"); + return; + } + + const char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, &source->mime_types) { + if (strcmp(*mime_type_ptr, mime_type) == 0) { + wlr_log(WLR_DEBUG, "Ignoring duplicate MIME type offer %s", + mime_type); + return; + } + } + + char *dup_mime_type = strdup(mime_type); + if (dup_mime_type == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + char **p = wl_array_add(&source->mime_types, sizeof(char *)); + if (p == NULL) { + free(dup_mime_type); + wl_resource_post_no_memory(resource); + return; + } + + *p = dup_mime_type; +} + +static void source_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_data_control_source_v1_interface source_impl = { + .offer = source_handle_offer, + .destroy = source_handle_destroy, +}; + +static void data_control_source_destroy(struct data_control_source *source) { + if (source == NULL) { + return; + } + + char **p; + wl_array_for_each(p, &source->mime_types) { + free(*p); + } + wl_array_release(&source->mime_types); + + // Prevent destructors below from calling this recursively. + wl_resource_set_user_data(source->resource, NULL); + + if (source->active_source != NULL) { + wlr_data_source_destroy(source->active_source); + } else if (source->active_primary_source != NULL) { + wlr_primary_selection_source_destroy( + source->active_primary_source); + } + + free(source); +} + +static void source_handle_resource_destroy(struct wl_resource *resource) { + struct data_control_source *source = source_from_resource(resource); + data_control_source_destroy(source); +} + + +struct client_data_source { + struct wlr_data_source source; + struct wl_resource *resource; +}; + +static const struct wlr_data_source_impl client_source_impl; + +static struct client_data_source * + client_data_source_from_source(struct wlr_data_source *wlr_source) { + assert(wlr_source->impl == &client_source_impl); + struct client_data_source *source = wl_container_of(wlr_source, source, source); + return source; +} + +static void client_source_send(struct wlr_data_source *wlr_source, + const char *mime_type, int fd) { + struct client_data_source *source = + client_data_source_from_source(wlr_source); + zwlr_data_control_source_v1_send_send(source->resource, mime_type, fd); + close(fd); +} + +static void client_source_destroy(struct wlr_data_source *wlr_source) { + struct client_data_source *client_source = + client_data_source_from_source(wlr_source); + struct data_control_source *source = + source_from_resource(client_source->resource); + free(client_source); + + if (source == NULL) { + return; + } + + source->active_source = NULL; + + zwlr_data_control_source_v1_send_cancelled(source->resource); + data_control_source_destroy(source); +} + +static const struct wlr_data_source_impl client_source_impl = { + .send = client_source_send, + .destroy = client_source_destroy, +}; + + +struct client_primary_selection_source { + struct wlr_primary_selection_source source; + struct wl_resource *resource; +}; + +static const struct wlr_primary_selection_source_impl +client_primary_selection_source_impl; + +static struct client_primary_selection_source * + client_primary_selection_source_from_source( + struct wlr_primary_selection_source *wlr_source) { + assert(wlr_source->impl == &client_primary_selection_source_impl); + struct client_primary_selection_source *source = wl_container_of(wlr_source, source, source); + return source; +} + +static void client_primary_selection_source_send( + struct wlr_primary_selection_source *wlr_source, + const char *mime_type, int fd) { + struct client_primary_selection_source *source = + client_primary_selection_source_from_source(wlr_source); + zwlr_data_control_source_v1_send_send(source->resource, mime_type, fd); + close(fd); +} + +static void client_primary_selection_source_destroy( + struct wlr_primary_selection_source *wlr_source) { + struct client_primary_selection_source *client_source = + client_primary_selection_source_from_source(wlr_source); + struct data_control_source *source = + source_from_resource(client_source->resource); + free(client_source); + + if (source == NULL) { + return; + } + + source->active_primary_source = NULL; + + zwlr_data_control_source_v1_send_cancelled(source->resource); + data_control_source_destroy(source); +} + +static const struct wlr_primary_selection_source_impl +client_primary_selection_source_impl = { + .send = client_primary_selection_source_send, + .destroy = client_primary_selection_source_destroy, +}; + + +struct data_offer { + struct wl_resource *resource; + struct wlr_data_control_device_v1 *device; + bool is_primary; +}; + +static void data_offer_destroy(struct data_offer *offer) { + if (offer == NULL) { + return; + } + + struct wlr_data_control_device_v1 *device = offer->device; + if (device != NULL) { + if (offer->is_primary) { + device->primary_selection_offer_resource = NULL; + } else { + device->selection_offer_resource = NULL; + } + } + + wl_resource_set_user_data(offer->resource, NULL); + free(offer); +} + +static const struct zwlr_data_control_offer_v1_interface offer_impl; + +static struct data_offer *data_offer_from_offer_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_data_control_offer_v1_interface, &offer_impl)); + return wl_resource_get_user_data(resource); +} + +static void offer_handle_receive(struct wl_client *client, + struct wl_resource *resource, const char *mime_type, int fd) { + struct data_offer *offer = data_offer_from_offer_resource(resource); + if (offer == NULL) { + close(fd); + return; + } + + struct wlr_data_control_device_v1 *device = offer->device; + if (device == NULL) { + close(fd); + return; + } + + if (offer->is_primary) { + if (device->seat->primary_selection_source == NULL) { + close(fd); + return; + } + wlr_primary_selection_source_send( + device->seat->primary_selection_source, + mime_type, fd); + } else { + if (device->seat->selection_source == NULL) { + close(fd); + return; + } + wlr_data_source_send(device->seat->selection_source, mime_type, fd); + } +} + +static void offer_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_data_control_offer_v1_interface offer_impl = { + .receive = offer_handle_receive, + .destroy = offer_handle_destroy, +}; + +static void offer_handle_resource_destroy(struct wl_resource *resource) { + struct data_offer *offer = data_offer_from_offer_resource(resource); + data_offer_destroy(offer); +} + +static struct wl_resource *create_offer(struct wlr_data_control_device_v1 *device, + struct wl_array *mime_types, bool is_primary) { + struct wl_client *client = wl_resource_get_client(device->resource); + + struct data_offer *offer = calloc(1, sizeof(*offer)); + if (offer == NULL) { + wl_client_post_no_memory(client); + return NULL; + } + + offer->device = device; + offer->is_primary = is_primary; + + uint32_t version = wl_resource_get_version(device->resource); + struct wl_resource *resource = wl_resource_create(client, + &zwlr_data_control_offer_v1_interface, version, 0); + if (resource == NULL) { + free(offer); + return NULL; + } + + offer->resource = resource; + + wl_resource_set_implementation(resource, &offer_impl, offer, + offer_handle_resource_destroy); + + zwlr_data_control_device_v1_send_data_offer(device->resource, resource); + + char **p; + wl_array_for_each(p, mime_types) { + zwlr_data_control_offer_v1_send_offer(resource, *p); + } + + return resource; +} + + +static const struct zwlr_data_control_device_v1_interface control_impl; + +static struct wlr_data_control_device_v1 *control_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_data_control_device_v1_interface, &control_impl)); + return wl_resource_get_user_data(resource); +} + +static void control_handle_set_selection(struct wl_client *client, + struct wl_resource *control_resource, + struct wl_resource *source_resource) { + struct wlr_data_control_device_v1 *device = + control_from_resource(control_resource); + if (device == NULL) { + return; + } + + struct data_control_source *source = NULL; + if (source_resource != NULL) { + source = source_from_resource(source_resource); + } + + if (source == NULL) { + wlr_seat_request_set_selection(device->seat, NULL, NULL, + wl_display_next_serial(device->seat->display)); + + return; + } + + if (source->active_source != NULL || + source->active_primary_source != NULL) { + wl_resource_post_error(control_resource, + ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_USED_SOURCE, + "cannot use a data source in set_selection or " + "set_primary_selection more than once"); + + return; + } + + struct client_data_source *client_source = calloc(1, sizeof(*client_source)); + if (client_source == NULL) { + wl_client_post_no_memory(client); + return; + } + client_source->resource = source_resource; + + struct wlr_data_source *wlr_source = &client_source->source; + wlr_data_source_init(wlr_source, &client_source_impl); + source->active_source = wlr_source; + + wl_array_release(&wlr_source->mime_types); + wlr_source->mime_types = source->mime_types; + wl_array_init(&source->mime_types); + + source->finalized = true; + + wlr_seat_request_set_selection(device->seat, NULL, wlr_source, + wl_display_next_serial(device->seat->display)); +} + +static void control_handle_set_primary_selection(struct wl_client *client, + struct wl_resource *control_resource, + struct wl_resource *source_resource) { + struct wlr_data_control_device_v1 *device = + control_from_resource(control_resource); + if (device == NULL) { + return; + } + + struct data_control_source *source = NULL; + if (source_resource != NULL) { + source = source_from_resource(source_resource); + } + + if (source == NULL) { + wlr_seat_request_set_primary_selection(device->seat, NULL, NULL, + wl_display_next_serial(device->seat->display)); + + return; + } + + if (source->active_source != NULL || + source->active_primary_source != NULL) { + wl_resource_post_error(control_resource, + ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_USED_SOURCE, + "cannot use a data source in set_selection or " + "set_primary_selection more than once"); + + return; + } + + struct client_primary_selection_source *client_source = calloc(1, sizeof(*client_source)); + if (client_source == NULL) { + wl_client_post_no_memory(client); + return; + } + client_source->resource = source_resource; + + struct wlr_primary_selection_source *wlr_source = &client_source->source; + wlr_primary_selection_source_init(wlr_source, &client_primary_selection_source_impl); + source->active_primary_source = wlr_source; + + wl_array_release(&wlr_source->mime_types); + wlr_source->mime_types = source->mime_types; + wl_array_init(&source->mime_types); + + source->finalized = true; + + wlr_seat_request_set_primary_selection(device->seat, NULL, wlr_source, + wl_display_next_serial(device->seat->display)); +} + +static void control_handle_destroy(struct wl_client *client, + struct wl_resource *control_resource) { + wl_resource_destroy(control_resource); +} + +static const struct zwlr_data_control_device_v1_interface control_impl = { + .set_selection = control_handle_set_selection, + .set_primary_selection = control_handle_set_primary_selection, + .destroy = control_handle_destroy, +}; + +static void control_send_selection(struct wlr_data_control_device_v1 *device) { + struct wlr_data_source *source = device->seat->selection_source; + + if (device->selection_offer_resource != NULL) { + // Make the offer inert + struct data_offer *offer = data_offer_from_offer_resource( + device->selection_offer_resource); + data_offer_destroy(offer); + } + + device->selection_offer_resource = NULL; + if (source != NULL) { + device->selection_offer_resource = + create_offer(device, &source->mime_types, false); + if (device->selection_offer_resource == NULL) { + wl_resource_post_no_memory(device->resource); + return; + } + } + + zwlr_data_control_device_v1_send_selection(device->resource, + device->selection_offer_resource); +} + +static void control_send_primary_selection( + struct wlr_data_control_device_v1 *device) { + uint32_t version = wl_resource_get_version(device->resource); + if (version < ZWLR_DATA_CONTROL_DEVICE_V1_PRIMARY_SELECTION_SINCE_VERSION) { + return; + } + + struct wlr_primary_selection_source *source = + device->seat->primary_selection_source; + + if (device->primary_selection_offer_resource != NULL) { + // Make the offer inert + struct data_offer *offer = data_offer_from_offer_resource( + device->primary_selection_offer_resource); + data_offer_destroy(offer); + } + + device->primary_selection_offer_resource = NULL; + if (source != NULL) { + device->primary_selection_offer_resource = + create_offer(device, &source->mime_types, true); + if (device->primary_selection_offer_resource == NULL) { + wl_resource_post_no_memory(device->resource); + return; + } + } + + zwlr_data_control_device_v1_send_primary_selection(device->resource, + device->primary_selection_offer_resource); +} + +static void control_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_data_control_device_v1 *device = control_from_resource(resource); + wlr_data_control_device_v1_destroy(device); +} + +static void control_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_data_control_device_v1 *device = + wl_container_of(listener, device, seat_destroy); + wlr_data_control_device_v1_destroy(device); +} + +static void control_handle_seat_set_selection(struct wl_listener *listener, + void *data) { + struct wlr_data_control_device_v1 *device = + wl_container_of(listener, device, seat_set_selection); + control_send_selection(device); +} + +static void control_handle_seat_set_primary_selection( + struct wl_listener *listener, + void *data) { + struct wlr_data_control_device_v1 *device = + wl_container_of(listener, device, seat_set_primary_selection); + control_send_primary_selection(device); +} + +void wlr_data_control_device_v1_destroy(struct wlr_data_control_device_v1 *device) { + if (device == NULL) { + return; + } + zwlr_data_control_device_v1_send_finished(device->resource); + // Make the resources inert + wl_resource_set_user_data(device->resource, NULL); + if (device->selection_offer_resource != NULL) { + struct data_offer *offer = data_offer_from_offer_resource( + device->selection_offer_resource); + data_offer_destroy(offer); + } + if (device->primary_selection_offer_resource != NULL) { + struct data_offer *offer = data_offer_from_offer_resource( + device->primary_selection_offer_resource); + data_offer_destroy(offer); + } + wl_list_remove(&device->seat_destroy.link); + wl_list_remove(&device->seat_set_selection.link); + wl_list_remove(&device->seat_set_primary_selection.link); + wl_list_remove(&device->link); + free(device); +} + + +static const struct zwlr_data_control_manager_v1_interface manager_impl; + +static struct wlr_data_control_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_data_control_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void manager_handle_create_data_source(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id) { + struct data_control_source *source = calloc(1, sizeof(*source)); + if (source == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + wl_array_init(&source->mime_types); + + uint32_t version = wl_resource_get_version(manager_resource); + source->resource = wl_resource_create(client, + &zwlr_data_control_source_v1_interface, version, id); + if (source->resource == NULL) { + wl_resource_post_no_memory(manager_resource); + wl_array_release(&source->mime_types); + free(source); + return; + } + wl_resource_set_implementation(source->resource, &source_impl, source, + source_handle_resource_destroy); +} + +static void manager_handle_get_data_device(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *seat_resource) { + struct wlr_data_control_manager_v1 *manager = + manager_from_resource(manager_resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwlr_data_control_device_v1_interface, version, id); + if (resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(resource, &control_impl, NULL, + control_handle_resource_destroy); + if (seat_client == NULL) { + return; + } + + struct wlr_data_control_device_v1 *device = calloc(1, sizeof(*device)); + if (device == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + device->manager = manager; + device->seat = seat_client->seat; + device->resource = resource; + wl_resource_set_user_data(resource, device); + + device->seat_destroy.notify = control_handle_seat_destroy; + wl_signal_add(&device->seat->events.destroy, &device->seat_destroy); + + device->seat_set_selection.notify = control_handle_seat_set_selection; + wl_signal_add(&device->seat->events.set_selection, + &device->seat_set_selection); + + device->seat_set_primary_selection.notify = + control_handle_seat_set_primary_selection; + wl_signal_add(&device->seat->events.set_primary_selection, + &device->seat_set_primary_selection); + + wl_list_insert(&manager->devices, &device->link); + wl_signal_emit_mutable(&manager->events.new_device, device); + + // At this point maybe the compositor decided to destroy the device. If + // it's the case then the resource will be inert. + device = control_from_resource(resource); + if (device != NULL) { + control_send_selection(device); + control_send_primary_selection(device); + } +} + +static void manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwlr_data_control_manager_v1_interface manager_impl = { + .create_data_source = manager_handle_create_data_source, + .get_data_device = manager_handle_get_data_device, + .destroy = manager_handle_destroy, +}; + +static void manager_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_data_control_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwlr_data_control_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_data_control_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_data_control_manager_v1 *wlr_data_control_manager_v1_create( + struct wl_display *display) { + struct wlr_data_control_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + wl_list_init(&manager->devices); + wl_signal_init(&manager->events.destroy); + wl_signal_init(&manager->events.new_device); + + manager->global = wl_global_create(display, + &zwlr_data_control_manager_v1_interface, + DATA_CONTROL_MANAGER_VERSION, manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_drm.c b/types/wlr_drm.c new file mode 100644 index 0000000..9cbc4ce --- /dev/null +++ b/types/wlr_drm.c @@ -0,0 +1,266 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "drm-protocol.h" +#include "render/drm_format_set.h" + +#define WLR_DRM_VERSION 2 + +static void buffer_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_buffer_interface wl_buffer_impl = { + .destroy = buffer_handle_destroy, +}; + +static const struct wlr_buffer_impl buffer_impl; + +static struct wlr_drm_buffer *drm_buffer_from_buffer( + struct wlr_buffer *wlr_buffer) { + assert(wlr_buffer->impl == &buffer_impl); + struct wlr_drm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + return buffer; +} + +static void buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct wlr_drm_buffer *buffer = drm_buffer_from_buffer(wlr_buffer); + if (buffer->resource != NULL) { + wl_resource_set_user_data(buffer->resource, NULL); + } + wlr_dmabuf_attributes_finish(&buffer->dmabuf); + wl_list_remove(&buffer->release.link); + free(buffer); +} + +static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buffer, + struct wlr_dmabuf_attributes *dmabuf) { + struct wlr_drm_buffer *buffer = drm_buffer_from_buffer(wlr_buffer); + *dmabuf = buffer->dmabuf; + return true; +} + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = buffer_destroy, + .get_dmabuf = buffer_get_dmabuf, +}; + +static bool buffer_resource_is_instance(struct wl_resource *resource) { + return wl_resource_instance_of(resource, &wl_buffer_interface, + &wl_buffer_impl); +} + +struct wlr_drm_buffer *wlr_drm_buffer_try_from_resource( + struct wl_resource *resource) { + if (!buffer_resource_is_instance(resource)) { + return NULL; + } + return wl_resource_get_user_data(resource); +} + +static void buffer_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_drm_buffer *buffer = wlr_drm_buffer_try_from_resource(resource); + assert(buffer != NULL); + buffer->resource = NULL; + wlr_buffer_drop(&buffer->base); +} + +static void buffer_handle_release(struct wl_listener *listener, void *data) { + struct wlr_drm_buffer *buffer = wl_container_of(listener, buffer, release); + if (buffer->resource != NULL) { + wl_buffer_send_release(buffer->resource); + } +} + +static void drm_handle_authenticate(struct wl_client *client, + struct wl_resource *resource, uint32_t id) { + // We only use render nodes, which don't need authentication + wl_drm_send_authenticated(resource); +} + +static void drm_handle_create_buffer(struct wl_client *client, + struct wl_resource *resource, uint32_t id, uint32_t name, int32_t width, + int32_t height, uint32_t stride, uint32_t format) { + wl_resource_post_error(resource, WL_DRM_ERROR_INVALID_NAME, + "Flink handles are not supported, use DMA-BUF instead"); +} + +static void drm_handle_create_planar_buffer(struct wl_client *client, + struct wl_resource *resource, uint32_t id, uint32_t name, int32_t width, + int32_t height, uint32_t format, int32_t offset0, int32_t stride0, + int32_t offset1, int32_t stride1, int32_t offset2, int32_t stride2) { + wl_resource_post_error(resource, WL_DRM_ERROR_INVALID_NAME, + "Flink handles are not supported, use DMA-BUF instead"); +} + +static void drm_handle_create_prime_buffer(struct wl_client *client, + struct wl_resource *resource, uint32_t id, int fd, int32_t width, + int32_t height, uint32_t format, int32_t offset0, int32_t stride0, + int32_t offset1, int32_t stride1, int32_t offset2, int32_t stride2) { + struct wlr_dmabuf_attributes dmabuf = { + .width = width, + .height = height, + .format = format, + .modifier = DRM_FORMAT_MOD_INVALID, + .n_planes = 1, + .offset[0] = offset0, + .stride[0] = stride0, + .fd[0] = fd, + }; + + struct wlr_drm_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + close(fd); + wl_resource_post_no_memory(resource); + return; + } + wlr_buffer_init(&buffer->base, &buffer_impl, width, height); + + buffer->resource = wl_resource_create(client, &wl_buffer_interface, 1, id); + if (buffer->resource == NULL) { + free(buffer); + close(fd); + wl_resource_post_no_memory(resource); + return; + } + wl_resource_set_implementation(buffer->resource, &wl_buffer_impl, buffer, + buffer_handle_resource_destroy); + + buffer->dmabuf = dmabuf; + + buffer->release.notify = buffer_handle_release; + wl_signal_add(&buffer->base.events.release, &buffer->release); +} + +static const struct wl_drm_interface drm_impl = { + .authenticate = drm_handle_authenticate, + .create_buffer = drm_handle_create_buffer, + .create_planar_buffer = drm_handle_create_planar_buffer, + .create_prime_buffer = drm_handle_create_prime_buffer, +}; + +static void drm_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_drm *drm = data; + + struct wl_resource *resource = wl_resource_create(client, + &wl_drm_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &drm_impl, drm, NULL); + + wl_drm_send_device(resource, drm->node_name); + wl_drm_send_capabilities(resource, WL_DRM_CAPABILITY_PRIME); + + for (size_t i = 0; i < drm->formats.len; i++) { + const struct wlr_drm_format *fmt = &drm->formats.formats[i]; + if (wlr_drm_format_has(fmt, DRM_FORMAT_MOD_INVALID)) { + wl_drm_send_format(resource, fmt->format); + } + } +} + +static struct wlr_buffer *buffer_from_resource(struct wl_resource *resource) { + struct wlr_drm_buffer *buffer = wlr_drm_buffer_try_from_resource(resource); + assert(buffer != NULL); + return &buffer->base; +} + +static const struct wlr_buffer_resource_interface buffer_resource_interface = { + .name = "wlr_drm_buffer", + .is_instance = buffer_resource_is_instance, + .from_resource = buffer_from_resource, +}; + +static void drm_destroy(struct wlr_drm *drm) { + wl_signal_emit_mutable(&drm->events.destroy, NULL); + + wl_list_remove(&drm->display_destroy.link); + + wlr_drm_format_set_finish(&drm->formats); + free(drm->node_name); + wl_global_destroy(drm->global); + free(drm); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm *drm = wl_container_of(listener, drm, display_destroy); + drm_destroy(drm); +} + +struct wlr_drm *wlr_drm_create(struct wl_display *display, + struct wlr_renderer *renderer) { + int drm_fd = wlr_renderer_get_drm_fd(renderer); + if (drm_fd < 0) { + wlr_log(WLR_ERROR, "Failed to get DRM FD from renderer"); + return NULL; + } + + drmDevice *dev = NULL; + if (drmGetDevice2(drm_fd, 0, &dev) != 0) { + wlr_log(WLR_ERROR, "drmGetDevice2 failed"); + return NULL; + } + + char *node_name = NULL; + if (dev->available_nodes & (1 << DRM_NODE_RENDER)) { + node_name = strdup(dev->nodes[DRM_NODE_RENDER]); + } else { + assert(dev->available_nodes & (1 << DRM_NODE_PRIMARY)); + wlr_log(WLR_DEBUG, "No DRM render node available, " + "falling back to primary node '%s'", dev->nodes[DRM_NODE_PRIMARY]); + node_name = strdup(dev->nodes[DRM_NODE_PRIMARY]); + } + drmFreeDevice(&dev); + if (node_name == NULL) { + return NULL; + } + + struct wlr_drm *drm = calloc(1, sizeof(*drm)); + if (drm == NULL) { + free(node_name); + return NULL; + } + + drm->node_name = node_name; + wl_signal_init(&drm->events.destroy); + + const struct wlr_drm_format_set *formats = wlr_renderer_get_dmabuf_texture_formats(renderer); + if (formats == NULL) { + goto error; + } + + if (!wlr_drm_format_set_copy(&drm->formats, formats)) { + goto error; + } + + drm->global = wl_global_create(display, &wl_drm_interface, WLR_DRM_VERSION, + drm, drm_bind); + if (drm->global == NULL) { + goto error; + } + + drm->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &drm->display_destroy); + + wlr_buffer_register_resource_interface(&buffer_resource_interface); + + return drm; + +error: + wlr_drm_format_set_finish(&drm->formats); + free(drm->node_name); + free(drm); + return NULL; +} diff --git a/types/wlr_drm_lease_v1.c b/types/wlr_drm_lease_v1.c new file mode 100644 index 0000000..d3c9ba1 --- /dev/null +++ b/types/wlr_drm_lease_v1.c @@ -0,0 +1,724 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/drm/drm.h" +#include "drm-lease-v1-protocol.h" +#include "util/global.h" + +#define DRM_LEASE_DEVICE_V1_VERSION 1 + +static struct wp_drm_lease_device_v1_interface lease_device_impl; +static struct wp_drm_lease_connector_v1_interface lease_connector_impl; +static struct wp_drm_lease_request_v1_interface lease_request_impl; +static struct wp_drm_lease_v1_interface lease_impl; + +static struct wlr_drm_lease_device_v1 *drm_lease_device_v1_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_drm_lease_device_v1_interface, &lease_device_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_drm_lease_connector_v1 * +drm_lease_connector_v1_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_drm_lease_connector_v1_interface, &lease_connector_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_drm_lease_request_v1 *drm_lease_request_v1_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_drm_lease_request_v1_interface, &lease_request_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_drm_lease_v1 *drm_lease_v1_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_drm_lease_v1_interface, &lease_impl)); + return wl_resource_get_user_data(resource); +} + +static void drm_lease_request_v1_destroy( + struct wlr_drm_lease_request_v1 *request) { + if (!request) { + return; + } + + wlr_log(WLR_DEBUG, "Destroying request %p", request); + + wl_list_remove(&request->link); + wl_resource_set_user_data(request->resource, NULL); + + free(request->connectors); + free(request); +} + +static void drm_lease_connector_v1_destroy( + struct wlr_drm_lease_connector_v1 *connector) { + if (!connector) { + return; + } + + wlr_log(WLR_DEBUG, "Destroying connector %s", connector->output->name); + + if (connector->active_lease) { + wlr_drm_lease_terminate(connector->active_lease->drm_lease); + } + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &connector->resources) { + wp_drm_lease_connector_v1_send_withdrawn(resource); + + wl_resource_set_user_data(resource, NULL); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } + + struct wl_resource *device_resource; + wl_resource_for_each(device_resource, &connector->device->resources) { + wp_drm_lease_device_v1_send_done(device_resource); + } + + wl_list_remove(&connector->link); + wl_list_remove(&connector->destroy.link); + free(connector); +} + +static void drm_lease_device_v1_destroy( + struct wlr_drm_lease_device_v1 *device) { + if (!device) { + return; + } + + struct wlr_drm_backend *backend = + get_drm_backend_from_backend(device->backend); + wlr_log(WLR_DEBUG, "Destroying wlr_drm_lease_device_v1 for %s", + backend->name); + + struct wl_resource *resource, *tmp_resource; + wl_resource_for_each_safe(resource, tmp_resource, &device->resources) { + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + wl_resource_set_user_data(resource, NULL); + } + + struct wlr_drm_lease_request_v1 *request, *tmp_request; + wl_list_for_each_safe(request, tmp_request, &device->requests, link) { + drm_lease_request_v1_destroy(request); + } + + struct wlr_drm_lease_v1 *lease, *tmp_lease; + wl_list_for_each_safe(lease, tmp_lease, &device->leases, link) { + wlr_drm_lease_terminate(lease->drm_lease); + } + + struct wlr_drm_lease_connector_v1 *connector, *tmp_connector; + wl_list_for_each_safe(connector, tmp_connector, &device->connectors, link) { + drm_lease_connector_v1_destroy(connector); + } + + wl_list_remove(&device->link); + wl_list_remove(&device->backend_destroy.link); + wlr_global_destroy_safe(device->global); + + free(device); +} + +static void lease_handle_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm_lease_v1 *lease = wl_container_of(listener, lease, destroy); + + wlr_log(WLR_DEBUG, "Destroying lease %"PRIu32, lease->drm_lease->lessee_id); + + wp_drm_lease_v1_send_finished(lease->resource); + + wl_list_remove(&lease->destroy.link); + + for (size_t i = 0; i < lease->n_connectors; ++i) { + lease->connectors[i]->active_lease = NULL; + } + + wl_list_remove(&lease->link); + wl_resource_set_user_data(lease->resource, NULL); + + free(lease->connectors); + free(lease); +} + +struct wlr_drm_lease_v1 *wlr_drm_lease_request_v1_grant( + struct wlr_drm_lease_request_v1 *request) { + assert(!request->invalid); + wlr_log(WLR_DEBUG, "Attempting to grant request %p", request); + + struct wlr_drm_lease_v1 *lease = calloc(1, sizeof(*lease)); + if (!lease) { + wl_resource_post_no_memory(request->resource); + return NULL; + } + + lease->device = request->device; + lease->resource = request->lease_resource; + + /* Transform connectors list into wlr_output for leasing */ + struct wlr_output *outputs[request->n_connectors + 1]; + for(size_t i = 0; i < request->n_connectors; ++i) { + outputs[i] = request->connectors[i]->output; + } + + int fd; + lease->drm_lease = wlr_drm_create_lease(outputs, request->n_connectors, &fd); + if (!lease->drm_lease) { + wlr_log(WLR_ERROR, "wlr_drm_create_lease failed"); + wp_drm_lease_v1_send_finished(lease->resource); + free(lease); + return NULL; + } + + lease->connectors = calloc(request->n_connectors, sizeof(*lease->connectors)); + if (!lease->connectors) { + wlr_log(WLR_ERROR, "Failed to allocate lease connectors list"); + close(fd); + wp_drm_lease_v1_send_finished(lease->resource); + free(lease); + return NULL; + } + lease->n_connectors = request->n_connectors; + for (size_t i = 0; i < request->n_connectors; ++i) { + lease->connectors[i] = request->connectors[i]; + lease->connectors[i]->active_lease = lease; + } + + lease->destroy.notify = lease_handle_destroy; + wl_signal_add(&lease->drm_lease->events.destroy, &lease->destroy); + + wl_list_insert(&lease->device->leases, &lease->link); + wl_resource_set_user_data(lease->resource, lease); + + wlr_log(WLR_DEBUG, "Granting request %p", request); + + wp_drm_lease_v1_send_lease_fd(lease->resource, fd); + close(fd); + + return lease; +} + +void wlr_drm_lease_request_v1_reject( + struct wlr_drm_lease_request_v1 *request) { + assert(request); + + wlr_log(WLR_DEBUG, "Rejecting request %p", request); + + request->invalid = true; + wp_drm_lease_v1_send_finished(request->lease_resource); +} + +void wlr_drm_lease_v1_revoke(struct wlr_drm_lease_v1 *lease) { + assert(lease); + wlr_log(WLR_DEBUG, "Revoking lease %"PRIu32, lease->drm_lease->lessee_id); + wlr_drm_lease_terminate(lease->drm_lease); +} + +static void drm_lease_v1_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_drm_lease_v1 *lease = drm_lease_v1_from_resource(resource); + if (lease != NULL) { + wlr_drm_lease_terminate(lease->drm_lease); + } +} + +static void drm_lease_v1_handle_destroy( + struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct wp_drm_lease_v1_interface lease_impl = { + .destroy = drm_lease_v1_handle_destroy, +}; + +static void drm_lease_request_v1_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_drm_lease_request_v1 *request = + drm_lease_request_v1_from_resource(resource); + drm_lease_request_v1_destroy(request); +} + +static void drm_lease_request_v1_handle_request_connector( + struct wl_client *client, struct wl_resource *request_resource, + struct wl_resource *connector_resource) { + struct wlr_drm_lease_request_v1 *request = + drm_lease_request_v1_from_resource(request_resource); + if (!request) { + wlr_log(WLR_ERROR, "Request has been destroyed"); + return; + } + + struct wlr_drm_lease_connector_v1 *connector = + drm_lease_connector_v1_from_resource(connector_resource); + + if (!connector) { + /* This connector offer has been withdrawn or is leased */ + wlr_log(WLR_ERROR, "Failed to request connector"); + request->invalid = true; + return; + } + + wlr_log(WLR_DEBUG, "Requesting connector %s", connector->output->name); + + if (request->device != connector->device) { + wlr_log(WLR_ERROR, "The connector belongs to another device"); + wl_resource_post_error(request_resource, + WP_DRM_LEASE_REQUEST_V1_ERROR_WRONG_DEVICE, + "The requested connector belongs to another device"); + return; + } + + for (size_t i = 0; i < request->n_connectors; ++i) { + struct wlr_drm_lease_connector_v1 *tmp = request->connectors[i]; + + if (connector == tmp) { + wlr_log(WLR_ERROR, "The connector has already been requested"); + wl_resource_post_error(request_resource, + WP_DRM_LEASE_REQUEST_V1_ERROR_DUPLICATE_CONNECTOR, + "The connector has already been requested"); + return; + } + } + + size_t n_connectors = request->n_connectors + 1; + + struct wlr_drm_lease_connector_v1 **tmp_connectors = + realloc(request->connectors, + n_connectors * sizeof(struct wlr_drm_lease_connector_v1 *)); + if (!tmp_connectors) { + wlr_log(WLR_ERROR, "Failed to grow connectors request array"); + return; + } + + request->connectors = tmp_connectors; + request->connectors[request->n_connectors] = connector; + request->n_connectors = n_connectors; +} + +static void drm_lease_request_v1_handle_submit( + struct wl_client *client, struct wl_resource *resource, uint32_t id) { + uint32_t version = wl_resource_get_version(resource); + struct wl_resource *lease_resource = wl_resource_create(client, + &wp_drm_lease_v1_interface, version, id); + if (!lease_resource) { + wlr_log(WLR_ERROR, "Failed to allocate wl_resource"); + wl_resource_post_no_memory(resource); + return; + } + + wl_resource_set_implementation(lease_resource, &lease_impl, NULL, + drm_lease_v1_handle_resource_destroy); + + struct wlr_drm_lease_request_v1 *request = + drm_lease_request_v1_from_resource(resource); + if (!request) { + wlr_log(WLR_DEBUG, "Request has been destroyed"); + wp_drm_lease_v1_send_finished(lease_resource); + return; + } + + /* Pre-emptively reject invalid lease requests */ + if (request->invalid) { + wlr_log(WLR_ERROR, "Invalid request"); + wp_drm_lease_v1_send_finished(lease_resource); + return; + } else if (request->n_connectors == 0) { + wl_resource_post_error(lease_resource, + WP_DRM_LEASE_REQUEST_V1_ERROR_EMPTY_LEASE, + "Lease request has no connectors"); + return; + } + + for (size_t i = 0; i < request->n_connectors; ++i) { + struct wlr_drm_lease_connector_v1 *conn = request->connectors[i]; + if (conn->active_lease) { + wlr_log(WLR_ERROR, "Failed to create lease, connector %s has " + "already been leased", conn->output->name); + wp_drm_lease_v1_send_finished(lease_resource); + return; + } + } + + request->lease_resource = lease_resource; + + wl_signal_emit_mutable(&request->device->manager->events.request, + request); + + /* If the compositor didn't act upon the request, reject it */ + if (!request->invalid && wl_resource_get_user_data(lease_resource) == NULL) { + wlr_drm_lease_request_v1_reject(request); + } + + /* Request is done */ + wl_resource_destroy(resource); +} + +static struct wp_drm_lease_request_v1_interface lease_request_impl = { + .request_connector = drm_lease_request_v1_handle_request_connector, + .submit = drm_lease_request_v1_handle_submit, +}; + +static void drm_lease_device_v1_handle_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void drm_lease_device_v1_handle_release( + struct wl_client *client, struct wl_resource *resource) { + wp_drm_lease_device_v1_send_released(resource); + wl_resource_destroy(resource); +} + +static void drm_lease_device_v1_handle_create_lease_request( + struct wl_client *client, struct wl_resource *resource, uint32_t id) { + uint32_t version = wl_resource_get_version(resource); + struct wl_resource *request_resource = wl_resource_create(client, + &wp_drm_lease_request_v1_interface, version, id); + if (!request_resource) { + wlr_log(WLR_ERROR, "Failed to allocate wl_resource"); + return; + } + + wl_resource_set_implementation(request_resource, &lease_request_impl, + NULL, drm_lease_request_v1_handle_resource_destroy); + + struct wlr_drm_lease_device_v1 *device = + drm_lease_device_v1_from_resource(resource); + if (!device) { + wlr_log(WLR_DEBUG, "Failed to create lease request, " + "wlr_drm_lease_device_v1 has been destroyed"); + return; + } + + struct wlr_drm_lease_request_v1 *req = calloc(1, sizeof(*req)); + if (!req) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_drm_lease_request_v1"); + wl_resource_post_no_memory(resource); + return; + } + + wlr_log(WLR_DEBUG, "Created request %p", req); + + req->device = device; + req->resource = request_resource; + req->connectors = NULL; + req->n_connectors = 0; + + wl_resource_set_user_data(request_resource, req); + + wl_list_insert(&device->requests, &req->link); +} + +static struct wp_drm_lease_device_v1_interface lease_device_impl = { + .release = drm_lease_device_v1_handle_release, + .create_lease_request = drm_lease_device_v1_handle_create_lease_request, +}; + +static void drm_connector_v1_handle_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void drm_connector_v1_handle_destroy( + struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct wp_drm_lease_connector_v1_interface lease_connector_impl = { + .destroy = drm_connector_v1_handle_destroy, +}; + +static void drm_lease_connector_v1_send_to_client( + struct wlr_drm_lease_connector_v1 *connector, + struct wl_resource *resource) { + if (connector->active_lease) { + return; + } + + struct wl_client *client = wl_resource_get_client(resource); + + uint32_t version = wl_resource_get_version(resource); + struct wl_resource *connector_resource = wl_resource_create(client, + &wp_drm_lease_connector_v1_interface, version, 0); + if (!connector_resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(connector_resource, &lease_connector_impl, + connector, drm_connector_v1_handle_resource_destroy); + wp_drm_lease_device_v1_send_connector(resource, connector_resource); + + struct wlr_output *output = connector->output; + wp_drm_lease_connector_v1_send_name(connector_resource, output->name); + + // TODO: re-send the description when it's updated + wp_drm_lease_connector_v1_send_description(connector_resource, + output->description); + + wp_drm_lease_connector_v1_send_connector_id(connector_resource, + wlr_drm_connector_get_id(output)); + + wp_drm_lease_connector_v1_send_done(connector_resource); + + wl_list_insert(&connector->resources, + wl_resource_get_link(connector_resource)); +} + +static void lease_device_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wl_resource *device_resource = wl_resource_create(wl_client, + &wp_drm_lease_device_v1_interface, version, id); + if (!device_resource) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_resource_set_implementation(device_resource, &lease_device_impl, NULL, + drm_lease_device_v1_handle_resource_destroy); + + struct wlr_drm_lease_device_v1 *device = data; + if (!device) { + wlr_log(WLR_DEBUG, "Failed to bind lease device, " + "the wlr_drm_lease_device_v1 has been destroyed"); + return; + } + + wl_resource_set_user_data(device_resource, device); + + int fd = wlr_drm_backend_get_non_master_fd(device->backend); + if (fd < 0) { + wlr_log(WLR_ERROR, "Unable to get read only DRM fd for leasing"); + wl_client_post_no_memory(wl_client); + return; + } + + wp_drm_lease_device_v1_send_drm_fd(device_resource, fd); + close(fd); + + wl_list_insert(&device->resources, wl_resource_get_link(device_resource)); + + struct wlr_drm_lease_connector_v1 *connector; + wl_list_for_each(connector, &device->connectors, link) { + drm_lease_connector_v1_send_to_client(connector, device_resource); + } + + wp_drm_lease_device_v1_send_done(device_resource); +} + +static void handle_output_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm_lease_connector_v1 *conn = wl_container_of(listener, conn, + destroy); + wlr_log(WLR_DEBUG, "Handle destruction of output %s", conn->output->name); + wlr_drm_lease_v1_manager_withdraw_output(conn->device->manager, conn->output); +} + +bool wlr_drm_lease_v1_manager_offer_output( + struct wlr_drm_lease_v1_manager *manager, struct wlr_output *output) { + assert(manager && output); + assert(wlr_output_is_drm(output)); + + wlr_log(WLR_DEBUG, "Offering output %s", output->name); + + struct wlr_drm_lease_device_v1 *device = NULL, *tmp_device; + wl_list_for_each(tmp_device, &manager->devices, link) { + if (tmp_device->backend == output->backend) { + device = tmp_device; + break; + } + } + + if (!device) { + wlr_log(WLR_ERROR, "No wlr_drm_lease_device_v1 associated with the " + "offered output"); + return false; + } + + struct wlr_drm_lease_connector_v1 *tmp_connector; + wl_list_for_each(tmp_connector, &device->connectors, link) { + if (tmp_connector->output == output) { + wlr_log(WLR_ERROR, "Output %s has already been offered", + output->name); + return false; + } + } + + struct wlr_drm_lease_connector_v1 *connector = calloc(1, sizeof(*connector)); + if (!connector) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_drm_lease_connector_v1"); + return false; + } + + connector->output = output; + connector->device = device; + + connector->destroy.notify = handle_output_destroy; + wl_signal_add(&output->events.destroy, &connector->destroy); + + wl_list_init(&connector->resources); + wl_list_insert(&device->connectors, &connector->link); + + struct wl_resource *resource; + wl_resource_for_each(resource, &device->resources) { + drm_lease_connector_v1_send_to_client(connector, resource); + wp_drm_lease_device_v1_send_done(resource); + } + + return true; +} + +void wlr_drm_lease_v1_manager_withdraw_output( + struct wlr_drm_lease_v1_manager *manager, struct wlr_output *output) { + assert(manager && output); + + wlr_log(WLR_DEBUG, "Withdrawing output %s", output->name); + + struct wlr_drm_lease_device_v1 *device = NULL, *tmp_device; + wl_list_for_each(tmp_device, &manager->devices, link) { + if (tmp_device->backend == output->backend) { + device = tmp_device; + break; + } + } + + if (!device) { + wlr_log(WLR_ERROR, "No wlr_drm_lease_device_v1 associated with the " + "given output"); + return; + } + + struct wlr_drm_lease_connector_v1 *connector = NULL, *tmp_conn; + wl_list_for_each(tmp_conn, &device->connectors, link) { + if (tmp_conn->output == output) { + connector = tmp_conn; + break; + } + } + + if (!connector) { + wlr_log(WLR_DEBUG, "No wlr_drm_connector_v1 associated with the given " + "output"); + return; + } + + drm_lease_connector_v1_destroy(connector); +} + +static void handle_backend_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm_lease_device_v1 *device = + wl_container_of(listener, device, backend_destroy); + drm_lease_device_v1_destroy(device); +} + +static void drm_lease_device_v1_create(struct wlr_drm_lease_v1_manager *manager, + struct wlr_backend *backend) { + struct wlr_drm_backend *drm_backend = get_drm_backend_from_backend(backend); + + // Make sure we can get a non-master FD for the DRM backend. On some setups + // we don't have the permission for this. + int fd = wlr_drm_backend_get_non_master_fd(backend); + if (fd < 0) { + wlr_log(WLR_INFO, "Skipping %s: failed to get read-only DRM FD", + drm_backend->name); + return; + } + close(fd); + + wlr_log(WLR_DEBUG, "Creating wlr_drm_lease_device_v1 for %s", + drm_backend->name); + + struct wlr_drm_lease_device_v1 *lease_device = calloc(1, sizeof(*lease_device)); + + if (!lease_device) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_drm_lease_device_v1"); + return; + } + + lease_device->manager = manager; + lease_device->backend = backend; + wl_list_init(&lease_device->resources); + wl_list_init(&lease_device->connectors); + wl_list_init(&lease_device->requests); + wl_list_init(&lease_device->leases); + wl_list_init(&lease_device->link); + + lease_device->global = wl_global_create(manager->display, + &wp_drm_lease_device_v1_interface, DRM_LEASE_DEVICE_V1_VERSION, + lease_device, lease_device_bind); + + if (!lease_device->global) { + wlr_log(WLR_ERROR, "Failed to allocate wp_drm_lease_device_v1 global"); + free(lease_device); + return; + } + + lease_device->backend_destroy.notify = handle_backend_destroy; + wl_signal_add(&backend->events.destroy, &lease_device->backend_destroy); + + wl_list_insert(&manager->devices, &lease_device->link); +} + +static void multi_backend_cb(struct wlr_backend *backend, void *data) { + if (!wlr_backend_is_drm(backend)) { + return; + } + + struct wlr_drm_lease_v1_manager *manager = data; + drm_lease_device_v1_create(manager, backend); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm_lease_v1_manager *manager = wl_container_of(listener, manager, + display_destroy); + wlr_log(WLR_DEBUG, "Destroying wlr_drm_lease_v1_manager"); + + struct wlr_drm_lease_device_v1 *device, *tmp; + wl_list_for_each_safe(device, tmp, &manager->devices, link) { + drm_lease_device_v1_destroy(device); + } + + free(manager); +} + +struct wlr_drm_lease_v1_manager *wlr_drm_lease_v1_manager_create( + struct wl_display *display, struct wlr_backend *backend) { + struct wlr_drm_lease_v1_manager *manager = calloc(1, sizeof(*manager)); + if (!manager) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_drm_lease_v1_manager"); + return NULL; + } + + wl_list_init(&manager->devices); + manager->display = display; + + if (wlr_backend_is_multi(backend)) { + /* TODO: handle backends added after the manager is created */ + wlr_multi_for_each_backend(backend, multi_backend_cb, manager); + } else if (wlr_backend_is_drm(backend)) { + drm_lease_device_v1_create(manager, backend); + } + + if (wl_list_empty(&manager->devices)) { + wlr_log(WLR_DEBUG, "No DRM backend supplied, failed to create " + "wlr_drm_lease_v1_manager"); + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + wl_signal_init(&manager->events.request); + + return manager; +} diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c new file mode 100644 index 0000000..69245e3 --- /dev/null +++ b/types/wlr_export_dmabuf_v1.c @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include +#include +#include +#include "wlr-export-dmabuf-unstable-v1-protocol.h" + +#define EXPORT_DMABUF_MANAGER_VERSION 1 + + +static const struct zwlr_export_dmabuf_frame_v1_interface frame_impl; + +static struct wlr_export_dmabuf_frame_v1 *frame_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_export_dmabuf_frame_v1_interface, &frame_impl)); + return wl_resource_get_user_data(resource); +} + +static void frame_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_export_dmabuf_frame_v1_interface frame_impl = { + .destroy = frame_handle_destroy, +}; + +static void frame_destroy(struct wlr_export_dmabuf_frame_v1 *frame) { + if (frame == NULL) { + return; + } + if (frame->output != NULL) { + wlr_output_lock_attach_render(frame->output, false); + if (frame->cursor_locked) { + wlr_output_lock_software_cursors(frame->output, false); + } + } + wl_list_remove(&frame->link); + wl_list_remove(&frame->output_commit.link); + wl_list_remove(&frame->output_destroy.link); + // Make the frame resource inert + wl_resource_set_user_data(frame->resource, NULL); + free(frame); +} + +static void frame_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_export_dmabuf_frame_v1 *frame = frame_from_resource(resource); + frame_destroy(frame); +} + +static void frame_output_handle_commit(struct wl_listener *listener, + void *data) { + struct wlr_export_dmabuf_frame_v1 *frame = + wl_container_of(listener, frame, output_commit); + struct wlr_output_event_commit *event = data; + + if (!(event->state->committed & WLR_OUTPUT_STATE_BUFFER)) { + return; + } + + wl_list_remove(&frame->output_commit.link); + wl_list_init(&frame->output_commit.link); + + struct wlr_dmabuf_attributes attribs = {0}; + if (!wlr_buffer_get_dmabuf(event->state->buffer, &attribs)) { + zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource, + ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_TEMPORARY); + frame_destroy(frame); + return; + } + + uint32_t frame_flags = ZWLR_EXPORT_DMABUF_FRAME_V1_FLAGS_TRANSIENT; + uint32_t mod_high = attribs.modifier >> 32; + uint32_t mod_low = attribs.modifier & 0xFFFFFFFF; + zwlr_export_dmabuf_frame_v1_send_frame(frame->resource, + attribs.width, attribs.height, 0, 0, 0, frame_flags, + attribs.format, mod_high, mod_low, attribs.n_planes); + + for (int i = 0; i < attribs.n_planes; ++i) { + off_t size = lseek(attribs.fd[i], 0, SEEK_END); + zwlr_export_dmabuf_frame_v1_send_object(frame->resource, i, + attribs.fd[i], size, attribs.offset[i], attribs.stride[i], i); + } + + time_t tv_sec = event->when->tv_sec; + uint32_t tv_sec_hi = (sizeof(tv_sec) > 4) ? tv_sec >> 32 : 0; + uint32_t tv_sec_lo = tv_sec & 0xFFFFFFFF; + zwlr_export_dmabuf_frame_v1_send_ready(frame->resource, + tv_sec_hi, tv_sec_lo, event->when->tv_nsec); + frame_destroy(frame); +} + +static void frame_output_handle_destroy(struct wl_listener *listener, void *data) { + struct wlr_export_dmabuf_frame_v1 *frame = wl_container_of(listener, frame, output_destroy); + frame_destroy(frame); +} + + +static const struct zwlr_export_dmabuf_manager_v1_interface manager_impl; + +static struct wlr_export_dmabuf_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_export_dmabuf_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void manager_handle_capture_output(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + int32_t overlay_cursor, struct wl_resource *output_resource) { + struct wlr_export_dmabuf_manager_v1 *manager = + manager_from_resource(manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + struct wlr_export_dmabuf_frame_v1 *frame = calloc(1, sizeof(*frame)); + if (frame == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + frame->manager = manager; + wl_list_init(&frame->output_commit.link); + wl_list_init(&frame->output_destroy.link); + + uint32_t version = wl_resource_get_version(manager_resource); + frame->resource = wl_resource_create(client, + &zwlr_export_dmabuf_frame_v1_interface, version, id); + if (frame->resource == NULL) { + wl_client_post_no_memory(client); + free(frame); + return; + } + wl_resource_set_implementation(frame->resource, &frame_impl, frame, + frame_handle_resource_destroy); + + wl_list_insert(&manager->frames, &frame->link); + + if (output == NULL || !output->enabled) { + zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource, + ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT); + frame_destroy(frame); + return; + } + + frame->output = output; + + wlr_output_lock_attach_render(frame->output, true); + if (overlay_cursor) { + wlr_output_lock_software_cursors(frame->output, true); + frame->cursor_locked = true; + } + + wl_list_remove(&frame->output_commit.link); + wl_signal_add(&output->events.commit, &frame->output_commit); + frame->output_commit.notify = frame_output_handle_commit; + wl_signal_add(&output->events.destroy, &frame->output_destroy); + frame->output_destroy.notify = frame_output_handle_destroy; + + // Request a frame because we can't assume that the current front buffer is still usable. It may + // have been released already, and we shouldn't lock it here because compositors want to render + // into the least damaged buffer. + wlr_output_update_needs_frame(output); +} + +static void manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwlr_export_dmabuf_manager_v1_interface manager_impl = { + .capture_output = manager_handle_capture_output, + .destroy = manager_handle_destroy, +}; + +static void manager_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_export_dmabuf_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwlr_export_dmabuf_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, + NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_export_dmabuf_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_export_dmabuf_manager_v1 *wlr_export_dmabuf_manager_v1_create( + struct wl_display *display) { + struct wlr_export_dmabuf_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + wl_list_init(&manager->frames); + wl_signal_init(&manager->events.destroy); + + manager->global = wl_global_create(display, + &zwlr_export_dmabuf_manager_v1_interface, EXPORT_DMABUF_MANAGER_VERSION, + manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_ext_foreign_toplevel_list_v1.c b/types/wlr_ext_foreign_toplevel_list_v1.c new file mode 100644 index 0000000..6c059e6 --- /dev/null +++ b/types/wlr_ext_foreign_toplevel_list_v1.c @@ -0,0 +1,265 @@ + +#include +#include +#include +#include +#include +#include +#include +#include "ext-foreign-toplevel-list-v1-protocol.h" + +#include "util/token.h" + +#define FOREIGN_TOPLEVEL_LIST_V1_VERSION 1 + +static const struct ext_foreign_toplevel_list_v1_interface toplevel_handle_impl; + +static void foreign_toplevel_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct ext_foreign_toplevel_list_v1_interface toplevel_handle_impl = { + .destroy = foreign_toplevel_handle_destroy, +}; + +// Returns true if clients need to be notified about the update +static bool update_string(struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + char **dst, const char *src) { + if (src == NULL) { + if (*dst == NULL) { + return false; + } + } else if (*dst != NULL && strcmp(*dst, src) == 0) { + return false; + } + + free(*dst); + if (src != NULL) { + *dst = strdup(src); + if (*dst == NULL) { + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + wl_resource_post_no_memory(resource); + } + return false; + } + } else { + *dst = NULL; + } + return true; +} + +void wlr_ext_foreign_toplevel_handle_v1_update_state( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + const struct wlr_ext_foreign_toplevel_handle_v1_state *state) { + bool changed_app_id = update_string(toplevel, &toplevel->app_id, state->app_id); + bool changed_title = update_string(toplevel, &toplevel->title, state->title); + + if (!changed_app_id && !changed_title) { + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + if (changed_app_id) { + ext_foreign_toplevel_handle_v1_send_app_id(resource, state->app_id ? state->app_id : ""); + } + if (changed_title) { + ext_foreign_toplevel_handle_v1_send_title(resource, state->title ? state->title : ""); + } + ext_foreign_toplevel_handle_v1_send_done(resource); + } +} + +void wlr_ext_foreign_toplevel_handle_v1_destroy( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel) { + if (!toplevel) { + return; + } + + wl_signal_emit_mutable(&toplevel->events.destroy, NULL); + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &toplevel->resources) { + ext_foreign_toplevel_handle_v1_send_closed(resource); + wl_resource_set_user_data(resource, NULL); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } + + wl_list_remove(&toplevel->link); + + free(toplevel->title); + free(toplevel->app_id); + free(toplevel->identifier); + free(toplevel); +} + +static void foreign_toplevel_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static struct wl_resource *create_toplevel_resource_for_resource( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + struct wl_resource *list_resource) { + struct wl_client *client = wl_resource_get_client(list_resource); + struct wl_resource *resource = wl_resource_create(client, + &ext_foreign_toplevel_handle_v1_interface, + wl_resource_get_version(list_resource), 0); + if (!resource) { + wl_client_post_no_memory(client); + return NULL; + } + + wl_resource_set_implementation(resource, &toplevel_handle_impl, toplevel, + foreign_toplevel_resource_destroy); + + wl_list_insert(&toplevel->resources, wl_resource_get_link(resource)); + ext_foreign_toplevel_list_v1_send_toplevel(list_resource, resource); + return resource; +} + +static void toplevel_send_details_to_toplevel_resource( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + struct wl_resource *resource) { + if (toplevel->title) { + ext_foreign_toplevel_handle_v1_send_title(resource, toplevel->title); + } + if (toplevel->app_id) { + ext_foreign_toplevel_handle_v1_send_app_id(resource, toplevel->app_id); + } + assert(toplevel->identifier); // Required to exist by protocol. + ext_foreign_toplevel_handle_v1_send_identifier(resource, toplevel->identifier); + ext_foreign_toplevel_handle_v1_send_done(resource); +} + +struct wlr_ext_foreign_toplevel_handle_v1 * +wlr_ext_foreign_toplevel_handle_v1_create(struct wlr_ext_foreign_toplevel_list_v1 *list, + const struct wlr_ext_foreign_toplevel_handle_v1_state *state) { + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel = calloc(1, sizeof(*toplevel)); + if (!toplevel) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel handle"); + return NULL; + } + + toplevel->identifier = calloc(TOKEN_SIZE, sizeof(char)); + if (toplevel->identifier == NULL) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel identifier"); + free(toplevel); + return NULL; + } + + if (!generate_token(toplevel->identifier)) { + free(toplevel->identifier); + free(toplevel); + return NULL; + } + + wl_list_insert(&list->toplevels, &toplevel->link); + toplevel->list = list; + if (state->app_id) { + toplevel->app_id = strdup(state->app_id); + } + if (state->title) { + toplevel->title = strdup(state->title); + } + + wl_list_init(&toplevel->resources); + + wl_signal_init(&toplevel->events.destroy); + + struct wl_resource *list_resource, *toplevel_resource; + wl_resource_for_each(list_resource, &list->resources) { + toplevel_resource = create_toplevel_resource_for_resource(toplevel, list_resource); + if (!toplevel_resource) { + continue; + } + toplevel_send_details_to_toplevel_resource(toplevel, toplevel_resource); + } + + return toplevel; +} + +static const struct ext_foreign_toplevel_list_v1_interface + foreign_toplevel_list_impl; + +static void foreign_toplevel_list_handle_stop(struct wl_client *client, + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_foreign_toplevel_list_v1_interface, + &foreign_toplevel_list_impl)); + + ext_foreign_toplevel_list_v1_send_finished(resource); + wl_resource_destroy(resource); +} + +static const struct ext_foreign_toplevel_list_v1_interface + foreign_toplevel_list_impl = { + .stop = foreign_toplevel_list_handle_stop +}; + +static void foreign_toplevel_list_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void foreign_toplevel_list_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_ext_foreign_toplevel_list_v1 *list = data; + struct wl_resource *resource = wl_resource_create(client, + &ext_foreign_toplevel_list_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &foreign_toplevel_list_impl, + list, foreign_toplevel_list_resource_destroy); + + wl_list_insert(&list->resources, wl_resource_get_link(resource)); + + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel; + wl_list_for_each(toplevel, &list->toplevels, link) { + struct wl_resource *toplevel_resource = + create_toplevel_resource_for_resource(toplevel, resource); + toplevel_send_details_to_toplevel_resource(toplevel, + toplevel_resource); + } +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_foreign_toplevel_list_v1 *list = + wl_container_of(listener, list, display_destroy); + wl_signal_emit_mutable(&list->events.destroy, NULL); + wl_list_remove(&list->display_destroy.link); + wl_global_destroy(list->global); + free(list); +} + +struct wlr_ext_foreign_toplevel_list_v1 *wlr_ext_foreign_toplevel_list_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= FOREIGN_TOPLEVEL_LIST_V1_VERSION); + + struct wlr_ext_foreign_toplevel_list_v1 *list = calloc(1, sizeof(*list)); + if (!list) { + return NULL; + } + + list->global = wl_global_create(display, + &ext_foreign_toplevel_list_v1_interface, + version, list, + foreign_toplevel_list_bind); + if (!list->global) { + free(list); + return NULL; + } + + wl_signal_init(&list->events.destroy); + wl_list_init(&list->resources); + wl_list_init(&list->toplevels); + + list->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &list->display_destroy); + + return list; +} diff --git a/types/wlr_foreign_toplevel_management_v1.c b/types/wlr_foreign_toplevel_management_v1.c new file mode 100644 index 0000000..23935d4 --- /dev/null +++ b/types/wlr_foreign_toplevel_management_v1.c @@ -0,0 +1,708 @@ + +#include +#include +#include +#include +#include +#include +#include +#include "wlr-foreign-toplevel-management-unstable-v1-protocol.h" + +#define FOREIGN_TOPLEVEL_MANAGEMENT_V1_VERSION 3 + +static const struct zwlr_foreign_toplevel_handle_v1_interface toplevel_handle_impl; + +static struct wlr_foreign_toplevel_handle_v1 *toplevel_handle_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_foreign_toplevel_handle_v1_interface, + &toplevel_handle_impl)); + return wl_resource_get_user_data(resource); +} + +static void toplevel_handle_send_maximized_event(struct wl_resource *resource, + bool state) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + struct wlr_foreign_toplevel_handle_v1_maximized_event event = { + .toplevel = toplevel, + .maximized = state, + }; + wl_signal_emit_mutable(&toplevel->events.request_maximize, &event); +} + +static void foreign_toplevel_handle_set_maximized(struct wl_client *client, + struct wl_resource *resource) { + toplevel_handle_send_maximized_event(resource, true); +} + +static void foreign_toplevel_handle_unset_maximized(struct wl_client *client, + struct wl_resource *resource) { + toplevel_handle_send_maximized_event(resource, false); +} + +static void toplevel_send_minimized_event(struct wl_resource *resource, + bool state) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + + struct wlr_foreign_toplevel_handle_v1_minimized_event event = { + .toplevel = toplevel, + .minimized = state, + }; + wl_signal_emit_mutable(&toplevel->events.request_minimize, &event); +} + +static void foreign_toplevel_handle_set_minimized(struct wl_client *client, + struct wl_resource *resource) { + toplevel_send_minimized_event(resource, true); +} + +static void foreign_toplevel_handle_unset_minimized(struct wl_client *client, + struct wl_resource *resource) { + toplevel_send_minimized_event(resource, false); +} + +static void toplevel_send_fullscreen_event(struct wl_resource *resource, + bool state, struct wl_resource *output_resource) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + + struct wlr_output *output = NULL; + if (output_resource) { + output = wlr_output_from_resource(output_resource); + } + struct wlr_foreign_toplevel_handle_v1_fullscreen_event event = { + .toplevel = toplevel, + .fullscreen = state, + .output = output, + }; + wl_signal_emit_mutable(&toplevel->events.request_fullscreen, &event); +} + +static void foreign_toplevel_handle_set_fullscreen(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *output) { + toplevel_send_fullscreen_event(resource, true, output); +} + +static void foreign_toplevel_handle_unset_fullscreen(struct wl_client *client, + struct wl_resource *resource) { + toplevel_send_fullscreen_event(resource, false, NULL); +} + +static void foreign_toplevel_handle_activate(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); + if (!seat_client) { + return; + } + + struct wlr_foreign_toplevel_handle_v1_activated_event event = { + .toplevel = toplevel, + .seat = seat_client->seat, + }; + wl_signal_emit_mutable(&toplevel->events.request_activate, &event); +} + +static void foreign_toplevel_handle_close(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + wl_signal_emit_mutable(&toplevel->events.request_close, toplevel); +} + +static void foreign_toplevel_handle_set_rectangle(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *surface, + int32_t x, int32_t y, int32_t width, int32_t height) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + + if (width < 0 || height < 0) { + wl_resource_post_error(resource, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ERROR_INVALID_RECTANGLE, + "invalid rectangle passed to set_rectangle: width/height < 0"); + return; + } + + struct wlr_foreign_toplevel_handle_v1_set_rectangle_event event = { + .toplevel = toplevel, + .surface = wlr_surface_from_resource(surface), + .x = x, + .y = y, + .width = width, + .height = height, + }; + wl_signal_emit_mutable(&toplevel->events.set_rectangle, &event); +} + +static void foreign_toplevel_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_foreign_toplevel_handle_v1_interface toplevel_handle_impl = { + .set_maximized = foreign_toplevel_handle_set_maximized, + .unset_maximized = foreign_toplevel_handle_unset_maximized, + .set_minimized = foreign_toplevel_handle_set_minimized, + .unset_minimized = foreign_toplevel_handle_unset_minimized, + .activate = foreign_toplevel_handle_activate, + .close = foreign_toplevel_handle_close, + .set_rectangle = foreign_toplevel_handle_set_rectangle, + .destroy = foreign_toplevel_handle_destroy, + .set_fullscreen = foreign_toplevel_handle_set_fullscreen, + .unset_fullscreen = foreign_toplevel_handle_unset_fullscreen, +}; + +static void toplevel_idle_send_done(void *data) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = data; + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_done(resource); + } + + toplevel->idle_source = NULL; +} + +static void toplevel_update_idle_source( + struct wlr_foreign_toplevel_handle_v1 *toplevel) { + if (toplevel->idle_source) { + return; + } + + toplevel->idle_source = wl_event_loop_add_idle(toplevel->manager->event_loop, + toplevel_idle_send_done, toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_title( + struct wlr_foreign_toplevel_handle_v1 *toplevel, const char *title) { + free(toplevel->title); + toplevel->title = strdup(title); + if (toplevel->title == NULL) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel title"); + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_title(resource, title); + } + + toplevel_update_idle_source(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_app_id( + struct wlr_foreign_toplevel_handle_v1 *toplevel, const char *app_id) { + free(toplevel->app_id); + toplevel->app_id = strdup(app_id); + if (toplevel->app_id == NULL) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel app_id"); + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_app_id(resource, app_id); + } + + toplevel_update_idle_source(toplevel); +} + +static void send_output_to_resource(struct wl_resource *resource, + struct wlr_output *output, bool enter) { + struct wl_client *client = wl_resource_get_client(resource); + struct wl_resource *output_resource; + + wl_resource_for_each(output_resource, &output->resources) { + if (wl_resource_get_client(output_resource) == client) { + if (enter) { + zwlr_foreign_toplevel_handle_v1_send_output_enter(resource, + output_resource); + } else { + zwlr_foreign_toplevel_handle_v1_send_output_leave(resource, + output_resource); + } + } + } +} + +static void toplevel_send_output(struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wlr_output *output, bool enter) { + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + send_output_to_resource(resource, output, enter); + } + + toplevel_update_idle_source(toplevel); +} + +static void toplevel_handle_output_bind(struct wl_listener *listener, + void *data) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output = + wl_container_of(listener, toplevel_output, output_bind); + struct wlr_output_event_bind *event = data; + struct wl_client *client = wl_resource_get_client(event->resource); + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel_output->toplevel->resources) { + if (wl_resource_get_client(resource) == client) { + send_output_to_resource(resource, toplevel_output->output, true); + } + } + + toplevel_update_idle_source(toplevel_output->toplevel); +} + +static void toplevel_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output = + wl_container_of(listener, toplevel_output, output_destroy); + wlr_foreign_toplevel_handle_v1_output_leave(toplevel_output->toplevel, + toplevel_output->output); +} + +void wlr_foreign_toplevel_handle_v1_output_enter( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wlr_output *output) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output; + wl_list_for_each(toplevel_output, &toplevel->outputs, link) { + if (toplevel_output->output == output) { + return; // we have already sent output_enter event + } + } + + toplevel_output = calloc(1, sizeof(*toplevel_output)); + if (!toplevel_output) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel output"); + return; + } + + toplevel_output->output = output; + toplevel_output->toplevel = toplevel; + wl_list_insert(&toplevel->outputs, &toplevel_output->link); + + toplevel_output->output_bind.notify = toplevel_handle_output_bind; + wl_signal_add(&output->events.bind, &toplevel_output->output_bind); + + toplevel_output->output_destroy.notify = toplevel_handle_output_destroy; + wl_signal_add(&output->events.destroy, &toplevel_output->output_destroy); + + toplevel_send_output(toplevel, output, true); +} + +static void toplevel_output_destroy( + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output) { + wl_list_remove(&toplevel_output->link); + wl_list_remove(&toplevel_output->output_bind.link); + wl_list_remove(&toplevel_output->output_destroy.link); + free(toplevel_output); +} + +void wlr_foreign_toplevel_handle_v1_output_leave( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wlr_output *output) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output_iterator; + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output = NULL; + + wl_list_for_each(toplevel_output_iterator, &toplevel->outputs, link) { + if (toplevel_output_iterator->output == output) { + toplevel_output = toplevel_output_iterator; + break; + } + } + + if (toplevel_output) { + toplevel_send_output(toplevel, output, false); + toplevel_output_destroy(toplevel_output); + } else { + // XXX: log an error? crash? + } +} + +static bool fill_array_from_toplevel_state(struct wl_array *array, + uint32_t state) { + if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) { + uint32_t *index = wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; + } + if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED) { + uint32_t *index = wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; + } + if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) { + uint32_t *index = wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; + } + if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) { + uint32_t *index = wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN; + } + + return true; +} + +static void toplevel_send_state(struct wlr_foreign_toplevel_handle_v1 *toplevel) { + struct wl_array states; + wl_array_init(&states); + bool r = fill_array_from_toplevel_state(&states, toplevel->state); + if (!r) { + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + wl_resource_post_no_memory(resource); + } + + wl_array_release(&states); + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_state(resource, &states); + } + + wl_array_release(&states); + toplevel_update_idle_source(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_maximized( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool maximized) { + if (maximized == !!(toplevel->state & + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED)) { + return; + } + if (maximized) { + toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; + } else { + toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; + } + toplevel_send_state(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_minimized( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool minimized) { + if (minimized == !!(toplevel->state & + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED)) { + return; + } + if (minimized) { + toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; + } else { + toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; + } + toplevel_send_state(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_activated( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool activated) { + if (activated == !!(toplevel->state & + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED)) { + return; + } + if (activated) { + toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; + } else { + toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; + } + toplevel_send_state(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_fullscreen( + struct wlr_foreign_toplevel_handle_v1 * toplevel, bool fullscreen) { + if (fullscreen == !!(toplevel->state & + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN)) { + return; + } + if (fullscreen) { + toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN; + } else { + toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN; + } + toplevel_send_state(toplevel); +} + +static void toplevel_resource_send_parent( + struct wl_resource *toplevel_resource, + struct wlr_foreign_toplevel_handle_v1 *parent) { + if (wl_resource_get_version(toplevel_resource) < + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_PARENT_SINCE_VERSION) { + return; + } + struct wl_client *client = wl_resource_get_client(toplevel_resource); + struct wl_resource *parent_resource = NULL; + if (parent) { + parent_resource = wl_resource_find_for_client(&parent->resources, client); + if (!parent_resource) { + /* don't send an event if this client destroyed the parent handle */ + return; + } + } + zwlr_foreign_toplevel_handle_v1_send_parent(toplevel_resource, + parent_resource); +} + +void wlr_foreign_toplevel_handle_v1_set_parent( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wlr_foreign_toplevel_handle_v1 *parent) { + if (parent == toplevel->parent) { + /* only send parent event to the clients if there was a change */ + return; + } + struct wl_resource *toplevel_resource, *tmp; + wl_resource_for_each_safe(toplevel_resource, tmp, &toplevel->resources) { + toplevel_resource_send_parent(toplevel_resource, parent); + } + toplevel->parent = parent; + toplevel_update_idle_source(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_destroy( + struct wlr_foreign_toplevel_handle_v1 *toplevel) { + if (!toplevel) { + return; + } + + wl_signal_emit_mutable(&toplevel->events.destroy, toplevel); + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_closed(resource); + wl_resource_set_user_data(resource, NULL); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } + + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output, *tmp2; + wl_list_for_each_safe(toplevel_output, tmp2, &toplevel->outputs, link) { + toplevel_output_destroy(toplevel_output); + } + + if (toplevel->idle_source) { + wl_event_source_remove(toplevel->idle_source); + } + + wl_list_remove(&toplevel->link); + + /* need to ensure no other toplevels hold a pointer to this one as + * a parent, so that a later call to foreign_toplevel_manager_bind() + * will not result in a segfault */ + struct wlr_foreign_toplevel_handle_v1 *tl, *tmp3; + wl_list_for_each_safe(tl, tmp3, &toplevel->manager->toplevels, link) { + if (tl->parent == toplevel) { + /* Note: we send a parent signal to all clients in this case; + * the caller should first destroy the child handles if it + * wishes to avoid this behavior. */ + wlr_foreign_toplevel_handle_v1_set_parent(tl, NULL); + } + } + + free(toplevel->title); + free(toplevel->app_id); + free(toplevel); +} + +static void foreign_toplevel_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static struct wl_resource *create_toplevel_resource_for_resource( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wl_resource *manager_resource) { + struct wl_client *client = wl_resource_get_client(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwlr_foreign_toplevel_handle_v1_interface, + wl_resource_get_version(manager_resource), 0); + if (!resource) { + wl_client_post_no_memory(client); + return NULL; + } + + wl_resource_set_implementation(resource, &toplevel_handle_impl, toplevel, + foreign_toplevel_resource_destroy); + + wl_list_insert(&toplevel->resources, wl_resource_get_link(resource)); + zwlr_foreign_toplevel_manager_v1_send_toplevel(manager_resource, resource); + return resource; +} + +struct wlr_foreign_toplevel_handle_v1 * +wlr_foreign_toplevel_handle_v1_create( + struct wlr_foreign_toplevel_manager_v1 *manager) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = calloc(1, sizeof(*toplevel)); + if (!toplevel) { + return NULL; + } + + wl_list_insert(&manager->toplevels, &toplevel->link); + toplevel->manager = manager; + + wl_list_init(&toplevel->resources); + wl_list_init(&toplevel->outputs); + + wl_signal_init(&toplevel->events.request_maximize); + wl_signal_init(&toplevel->events.request_minimize); + wl_signal_init(&toplevel->events.request_activate); + wl_signal_init(&toplevel->events.request_fullscreen); + wl_signal_init(&toplevel->events.request_close); + wl_signal_init(&toplevel->events.set_rectangle); + wl_signal_init(&toplevel->events.destroy); + + struct wl_resource *manager_resource, *tmp; + wl_resource_for_each_safe(manager_resource, tmp, &manager->resources) { + create_toplevel_resource_for_resource(toplevel, manager_resource); + } + + return toplevel; +} + +static const struct zwlr_foreign_toplevel_manager_v1_interface + foreign_toplevel_manager_impl; + +static void foreign_toplevel_manager_handle_stop(struct wl_client *client, + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_foreign_toplevel_manager_v1_interface, + &foreign_toplevel_manager_impl)); + + zwlr_foreign_toplevel_manager_v1_send_finished(resource); + wl_resource_destroy(resource); +} + +static const struct zwlr_foreign_toplevel_manager_v1_interface + foreign_toplevel_manager_impl = { + .stop = foreign_toplevel_manager_handle_stop +}; + +static void foreign_toplevel_manager_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void toplevel_send_details_to_toplevel_resource( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wl_resource *resource) { + if (toplevel->title) { + zwlr_foreign_toplevel_handle_v1_send_title(resource, toplevel->title); + } + if (toplevel->app_id) { + zwlr_foreign_toplevel_handle_v1_send_app_id(resource, toplevel->app_id); + } + + struct wlr_foreign_toplevel_handle_v1_output *output; + wl_list_for_each(output, &toplevel->outputs, link) { + send_output_to_resource(resource, output->output, true); + } + + struct wl_array states; + wl_array_init(&states); + bool r = fill_array_from_toplevel_state(&states, toplevel->state); + if (!r) { + wl_resource_post_no_memory(resource); + wl_array_release(&states); + return; + } + + zwlr_foreign_toplevel_handle_v1_send_state(resource, &states); + wl_array_release(&states); + + toplevel_resource_send_parent(resource, toplevel->parent); + + zwlr_foreign_toplevel_handle_v1_send_done(resource); +} + +static void foreign_toplevel_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_foreign_toplevel_manager_v1 *manager = data; + struct wl_resource *resource = wl_resource_create(client, + &zwlr_foreign_toplevel_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &foreign_toplevel_manager_impl, + manager, foreign_toplevel_manager_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); + + struct wlr_foreign_toplevel_handle_v1 *toplevel, *tmp; + /* First loop: create a handle for all toplevels for all clients. + * Separation into two loops avoid the case where a child handle + * is created before a parent handle, so the parent relationship + * could not be sent to a client. */ + wl_list_for_each_safe(toplevel, tmp, &manager->toplevels, link) { + create_toplevel_resource_for_resource(toplevel, resource); + } + /* Second loop: send details about each toplevel. */ + wl_list_for_each_safe(toplevel, tmp, &manager->toplevels, link) { + struct wl_resource *toplevel_resource = + wl_resource_find_for_client(&toplevel->resources, client); + toplevel_send_details_to_toplevel_resource(toplevel, + toplevel_resource); + } +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_foreign_toplevel_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_foreign_toplevel_manager_v1 *wlr_foreign_toplevel_manager_v1_create( + struct wl_display *display) { + struct wlr_foreign_toplevel_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + return NULL; + } + + manager->event_loop = wl_display_get_event_loop(display); + manager->global = wl_global_create(display, + &zwlr_foreign_toplevel_manager_v1_interface, + FOREIGN_TOPLEVEL_MANAGEMENT_V1_VERSION, manager, + foreign_toplevel_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.destroy); + wl_list_init(&manager->resources); + wl_list_init(&manager->toplevels); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_fractional_scale_v1.c b/types/wlr_fractional_scale_v1.c new file mode 100644 index 0000000..6079746 --- /dev/null +++ b/types/wlr_fractional_scale_v1.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include +#include + +#include "fractional-scale-v1-protocol.h" + +#define FRACTIONAL_SCALE_VERSION 1 + +struct wlr_fractional_scale_v1 { + struct wl_resource *resource; + struct wlr_addon addon; + + // Used for dummy objects to store scale + double scale; +}; + +static const struct wp_fractional_scale_v1_interface fractional_scale_interface; + +static struct wlr_fractional_scale_v1 *fractional_scale_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_fractional_scale_v1_interface, &fractional_scale_interface)); + return wl_resource_get_user_data(resource); +} + +static const struct wp_fractional_scale_manager_v1_interface scale_manager_interface; + +static void fractional_scale_destroy(struct wlr_fractional_scale_v1 *info) { + if (info == NULL) { + return; + } + + // If this is a dummy object then we do not have a wl_resource + if (info->resource != NULL) { + wl_resource_set_user_data(info->resource, NULL); + } + wlr_addon_finish(&info->addon); + free(info); +} + +static void fractional_scale_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_fractional_scale_v1 *info = fractional_scale_from_resource(resource); + fractional_scale_destroy(info); +} + +static void fractional_scale_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wp_fractional_scale_v1_interface fractional_scale_interface = { + .destroy = fractional_scale_handle_destroy, +}; + +static void fractional_scale_addon_destroy(struct wlr_addon *addon) { + struct wlr_fractional_scale_v1 *info = wl_container_of(addon, info, addon); + fractional_scale_destroy(info); +} + +static const struct wlr_addon_interface fractional_scale_addon_impl = { + .name = "wlr_fractional_scale_v1", + .destroy = fractional_scale_addon_destroy, +}; + +static uint32_t double_to_v120(double d) { + return round(d * 120); +} + +void wlr_fractional_scale_v1_notify_scale(struct wlr_surface *surface, double scale) { + struct wlr_addon *addon = wlr_addon_find(&surface->addons, + NULL, &fractional_scale_addon_impl); + + if (addon == NULL) { + // Create a dummy object to store the scale + struct wlr_fractional_scale_v1 *info = calloc(1, sizeof(*info)); + if (info == NULL) { + return; + } + + wlr_addon_init(&info->addon, &surface->addons, NULL, &fractional_scale_addon_impl); + info->scale = scale; + return; + } + + struct wlr_fractional_scale_v1 *info = wl_container_of(addon, info, addon); + if (info->scale == scale) { + return; + } + + info->scale = scale; + + if (!info->resource) { + // Update existing dummy object + return; + } + + wp_fractional_scale_v1_send_preferred_scale(info->resource, double_to_v120(scale)); +} + +static void handle_get_fractional_scale(struct wl_client *client, + struct wl_resource *mgr_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + struct wlr_fractional_scale_v1 *info = NULL; + + // If no fractional_scale object had been created but scale had been set, then + // there will be a dummy object for us to fill out with a resource. Check if + // that's the case. + struct wlr_addon *addon = wlr_addon_find(&surface->addons, NULL, &fractional_scale_addon_impl); + + if (addon != NULL) { + info = wl_container_of(addon, info, addon); + if (info->resource != NULL) { + wl_resource_post_error(mgr_resource, + WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_FRACTIONAL_SCALE_EXISTS, + "a surface scale object for that surface already exists"); + return; + } + } + + if (info == NULL) { + info = calloc(1, sizeof(*info)); + if (info == NULL) { + wl_client_post_no_memory(client); + return; + } + + wlr_addon_init(&info->addon, &surface->addons, NULL, &fractional_scale_addon_impl); + } + + uint32_t version = wl_resource_get_version(mgr_resource); + info->resource = wl_resource_create(client, &wp_fractional_scale_v1_interface, version, id); + if (info->resource == NULL) { + wl_client_post_no_memory(client); + fractional_scale_destroy(info); + return; + } + wl_resource_set_implementation(info->resource, + &fractional_scale_interface, info, + fractional_scale_handle_resource_destroy); + + if (info->scale != 0) { + wp_fractional_scale_v1_send_preferred_scale(info->resource, double_to_v120(info->scale)); + } +} + +static void fractional_scale_manager_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wp_fractional_scale_manager_v1_interface scale_manager_interface = { + .destroy = fractional_scale_manager_handle_destroy, + .get_fractional_scale = handle_get_fractional_scale, +}; + +static void fractional_scale_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_fractional_scale_manager_v1 *mgr = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_fractional_scale_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &scale_manager_interface, mgr, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_fractional_scale_manager_v1 *mgr = + wl_container_of(listener, mgr, display_destroy); + wl_signal_emit_mutable(&mgr->events.destroy, NULL); + assert(wl_list_empty(&mgr->events.destroy.listener_list)); + wl_list_remove(&mgr->display_destroy.link); + free(mgr); +} + +struct wlr_fractional_scale_manager_v1 *wlr_fractional_scale_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= FRACTIONAL_SCALE_VERSION); + + struct wlr_fractional_scale_manager_v1 *mgr = calloc(1, sizeof(*mgr)); + if (mgr == NULL) { + return NULL; + } + + mgr->global = wl_global_create(display, + &wp_fractional_scale_manager_v1_interface, + version, mgr, fractional_scale_manager_bind); + if (mgr->global == NULL) { + free(mgr); + return NULL; + } + + mgr->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &mgr->display_destroy); + + wl_signal_init(&mgr->events.destroy); + + return mgr; +} + diff --git a/types/wlr_fullscreen_shell_v1.c b/types/wlr_fullscreen_shell_v1.c new file mode 100644 index 0000000..d59e94a --- /dev/null +++ b/types/wlr_fullscreen_shell_v1.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include + +#define FULLSCREEN_SHELL_VERSION 1 + +static const struct zwp_fullscreen_shell_v1_interface shell_impl; + +static void shell_handle_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct wlr_fullscreen_shell_v1 *shell_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_fullscreen_shell_v1_interface, &shell_impl)); + return wl_resource_get_user_data(resource); +} + +static void fullscreen_shell_surface_handle_commit(struct wlr_surface *surface) { + if (wlr_surface_has_buffer(surface)) { + wlr_surface_map(surface); + } +} + +static const struct wlr_surface_role fullscreen_shell_surface_role = { + .name = "zwp_fullscreen_shell_v1-surface", + .no_object = true, + .commit = fullscreen_shell_surface_handle_commit, +}; + +static void shell_handle_present_surface(struct wl_client *client, + struct wl_resource *shell_resource, + struct wl_resource *surface_resource, uint32_t method, + struct wl_resource *output_resource) { + struct wlr_fullscreen_shell_v1 *shell = shell_from_resource(shell_resource); + struct wlr_surface *surface = NULL; + if (surface_resource != NULL) { + surface = wlr_surface_from_resource(surface_resource); + } + struct wlr_output *output = NULL; + if (output_resource != NULL) { + output = wlr_output_from_resource(output_resource); + } + + if (!wlr_surface_set_role(surface, &fullscreen_shell_surface_role, + shell_resource, ZWP_FULLSCREEN_SHELL_V1_ERROR_ROLE)) { + return; + } + + struct wlr_fullscreen_shell_v1_present_surface_event event = { + .client = client, + .surface = surface, + .method = method, + .output = output, + }; + wl_signal_emit_mutable(&shell->events.present_surface, &event); +} + +static void shell_handle_present_surface_for_mode(struct wl_client *client, + struct wl_resource *shell_resource, + struct wl_resource *surface_resource, + struct wl_resource *output_resource, int32_t framerate, + uint32_t feedback_id) { + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + if (!wlr_surface_set_role(surface, &fullscreen_shell_surface_role, + shell_resource, ZWP_FULLSCREEN_SHELL_V1_ERROR_ROLE)) { + return; + } + + uint32_t version = wl_resource_get_version(shell_resource); + struct wl_resource *feedback_resource = + wl_resource_create(client, NULL, version, feedback_id); + if (feedback_resource == NULL) { + wl_resource_post_no_memory(shell_resource); + return; + } + + // TODO: add support for mode switch + zwp_fullscreen_shell_mode_feedback_v1_send_mode_failed(feedback_resource); + wl_resource_destroy(feedback_resource); +} + +static const struct zwp_fullscreen_shell_v1_interface shell_impl = { + .release = shell_handle_release, + .present_surface = shell_handle_present_surface, + .present_surface_for_mode = shell_handle_present_surface_for_mode, +}; + +static void shell_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_fullscreen_shell_v1 *shell = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwp_fullscreen_shell_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &shell_impl, shell, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_fullscreen_shell_v1 *shell = + wl_container_of(listener, shell, display_destroy); + wl_signal_emit_mutable(&shell->events.destroy, shell); + wl_list_remove(&shell->display_destroy.link); + wl_global_destroy(shell->global); + free(shell); +} + +struct wlr_fullscreen_shell_v1 *wlr_fullscreen_shell_v1_create( + struct wl_display *display) { + struct wlr_fullscreen_shell_v1 *shell = calloc(1, sizeof(*shell)); + if (shell == NULL) { + return NULL; + } + wl_signal_init(&shell->events.destroy); + wl_signal_init(&shell->events.present_surface); + + shell->global = wl_global_create(display, + &zwp_fullscreen_shell_v1_interface, FULLSCREEN_SHELL_VERSION, + shell, shell_bind); + if (shell->global == NULL) { + free(shell); + return NULL; + } + + shell->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &shell->display_destroy); + + return shell; +} diff --git a/types/wlr_gamma_control_v1.c b/types/wlr_gamma_control_v1.c new file mode 100644 index 0000000..fbf117f --- /dev/null +++ b/types/wlr_gamma_control_v1.c @@ -0,0 +1,279 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wlr-gamma-control-unstable-v1-protocol.h" + +#define GAMMA_CONTROL_MANAGER_V1_VERSION 1 + +static void gamma_control_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void gamma_control_destroy(struct wlr_gamma_control_v1 *gamma_control) { + if (gamma_control == NULL) { + return; + } + + struct wlr_gamma_control_manager_v1 *manager = gamma_control->manager; + struct wlr_output *output = gamma_control->output; + + wl_resource_set_user_data(gamma_control->resource, NULL); + wl_list_remove(&gamma_control->output_destroy_listener.link); + wl_list_remove(&gamma_control->link); + free(gamma_control->table); + free(gamma_control); + + struct wlr_gamma_control_manager_v1_set_gamma_event event = { + .output = output, + }; + wl_signal_emit_mutable(&manager->events.set_gamma, &event); +} + +static const struct zwlr_gamma_control_v1_interface gamma_control_impl; + +static struct wlr_gamma_control_v1 *gamma_control_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwlr_gamma_control_v1_interface, + &gamma_control_impl)); + return wl_resource_get_user_data(resource); +} + +static void gamma_control_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_gamma_control_v1 *gamma_control = + gamma_control_from_resource(resource); + gamma_control_destroy(gamma_control); +} + +static void gamma_control_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_gamma_control_v1 *gamma_control = + wl_container_of(listener, gamma_control, output_destroy_listener); + gamma_control_destroy(gamma_control); +} + +static void gamma_control_handle_set_gamma(struct wl_client *client, + struct wl_resource *gamma_control_resource, int fd) { + struct wlr_gamma_control_v1 *gamma_control = + gamma_control_from_resource(gamma_control_resource); + if (gamma_control == NULL) { + goto error_fd; + } + + uint32_t ramp_size = wlr_output_get_gamma_size(gamma_control->output); + size_t table_size = ramp_size * 3 * sizeof(uint16_t); + + // Refuse to block when reading + int fd_flags = fcntl(fd, F_GETFL, 0); + if (fd_flags == -1) { + wlr_log_errno(WLR_ERROR, "failed to get FD flags"); + wlr_gamma_control_v1_send_failed_and_destroy(gamma_control); + goto error_fd; + } + if (fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK) == -1) { + wlr_log_errno(WLR_ERROR, "failed to set FD flags"); + wlr_gamma_control_v1_send_failed_and_destroy(gamma_control); + goto error_fd; + } + + // Use the heap since gamma tables can be large + uint16_t *table = malloc(table_size); + if (table == NULL) { + wl_resource_post_no_memory(gamma_control_resource); + goto error_fd; + } + + ssize_t n_read = pread(fd, table, table_size, 0); + if (n_read < 0) { + wlr_log_errno(WLR_ERROR, "failed to read gamma table"); + wlr_gamma_control_v1_send_failed_and_destroy(gamma_control); + goto error_table; + } else if ((size_t)n_read != table_size) { + wl_resource_post_error(gamma_control_resource, + ZWLR_GAMMA_CONTROL_V1_ERROR_INVALID_GAMMA, + "The gamma ramps don't have the correct size"); + goto error_table; + } + close(fd); + + free(gamma_control->table); + gamma_control->table = table; + gamma_control->ramp_size = ramp_size; + + struct wlr_gamma_control_manager_v1_set_gamma_event event = { + .output = gamma_control->output, + .control = gamma_control, + }; + wl_signal_emit_mutable(&gamma_control->manager->events.set_gamma, &event); + + return; + +error_table: + free(table); +error_fd: + close(fd); +} + +static const struct zwlr_gamma_control_v1_interface gamma_control_impl = { + .destroy = gamma_control_handle_destroy, + .set_gamma = gamma_control_handle_set_gamma, +}; + +static const struct zwlr_gamma_control_manager_v1_interface + gamma_control_manager_impl; + +static struct wlr_gamma_control_manager_v1 *gamma_control_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_gamma_control_manager_v1_interface, &gamma_control_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void gamma_control_manager_get_gamma_control(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *output_resource) { + struct wlr_gamma_control_manager_v1 *manager = + gamma_control_manager_from_resource(manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwlr_gamma_control_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &gamma_control_impl, + NULL, gamma_control_handle_resource_destroy); + + if (output == NULL) { + zwlr_gamma_control_v1_send_failed(resource); + return; + } + + size_t gamma_size = wlr_output_get_gamma_size(output); + if (gamma_size == 0) { + zwlr_gamma_control_v1_send_failed(resource); + return; + } + + if (wlr_gamma_control_manager_v1_get_control(manager, output) != NULL) { + zwlr_gamma_control_v1_send_failed(resource); + return; + } + + struct wlr_gamma_control_v1 *gamma_control = calloc(1, sizeof(*gamma_control)); + if (gamma_control == NULL) { + wl_client_post_no_memory(client); + return; + } + gamma_control->output = output; + gamma_control->manager = manager; + gamma_control->resource = resource; + wl_resource_set_user_data(resource, gamma_control); + + wl_signal_add(&output->events.destroy, + &gamma_control->output_destroy_listener); + gamma_control->output_destroy_listener.notify = + gamma_control_handle_output_destroy; + + wl_list_insert(&manager->controls, &gamma_control->link); + zwlr_gamma_control_v1_send_gamma_size(gamma_control->resource, gamma_size); +} + +static void gamma_control_manager_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwlr_gamma_control_manager_v1_interface + gamma_control_manager_impl = { + .get_gamma_control = gamma_control_manager_get_gamma_control, + .destroy = gamma_control_manager_destroy, +}; + +static void gamma_control_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_gamma_control_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwlr_gamma_control_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &gamma_control_manager_impl, + manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_gamma_control_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_gamma_control_manager_v1 *wlr_gamma_control_manager_v1_create( + struct wl_display *display) { + struct wlr_gamma_control_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + return NULL; + } + + manager->global = wl_global_create(display, + &zwlr_gamma_control_manager_v1_interface, + GAMMA_CONTROL_MANAGER_V1_VERSION, manager, gamma_control_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.destroy); + wl_signal_init(&manager->events.set_gamma); + wl_list_init(&manager->controls); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +struct wlr_gamma_control_v1 *wlr_gamma_control_manager_v1_get_control( + struct wlr_gamma_control_manager_v1 *manager, struct wlr_output *output) { + struct wlr_gamma_control_v1 *gamma_control; + wl_list_for_each(gamma_control, &manager->controls, link) { + if (gamma_control->output == output) { + return gamma_control; + } + } + return NULL; +} + +bool wlr_gamma_control_v1_apply(struct wlr_gamma_control_v1 *gamma_control, + struct wlr_output_state *output_state) { + if (gamma_control == NULL || gamma_control->table == NULL) { + return wlr_output_state_set_gamma_lut(output_state, 0, NULL, NULL, NULL); + } + + const uint16_t *r = gamma_control->table; + const uint16_t *g = gamma_control->table + gamma_control->ramp_size; + const uint16_t *b = gamma_control->table + 2 * gamma_control->ramp_size; + return wlr_output_state_set_gamma_lut(output_state, + gamma_control->ramp_size, r, g, b); +} + +void wlr_gamma_control_v1_send_failed_and_destroy(struct wlr_gamma_control_v1 *gamma_control) { + if (gamma_control == NULL) { + return; + } + zwlr_gamma_control_v1_send_failed(gamma_control->resource); + gamma_control_destroy(gamma_control); +} diff --git a/types/wlr_idle_inhibit_v1.c b/types/wlr_idle_inhibit_v1.c new file mode 100644 index 0000000..93e8e85 --- /dev/null +++ b/types/wlr_idle_inhibit_v1.c @@ -0,0 +1,158 @@ +#include +#include +#include +#include +#include +#include +#include +#include "idle-inhibit-unstable-v1-protocol.h" + +static const struct zwp_idle_inhibit_manager_v1_interface idle_inhibit_impl; + +static const struct zwp_idle_inhibitor_v1_interface idle_inhibitor_impl; + +static struct wlr_idle_inhibit_manager_v1 * +wlr_idle_inhibit_manager_v1_from_resource(struct wl_resource *manager_resource) { + assert(wl_resource_instance_of(manager_resource, + &zwp_idle_inhibit_manager_v1_interface, + &idle_inhibit_impl)); + return wl_resource_get_user_data(manager_resource); +} + +static struct wlr_idle_inhibitor_v1 * +wlr_idle_inhibitor_v1_from_resource(struct wl_resource *inhibitor_resource) { + assert(wl_resource_instance_of(inhibitor_resource, + &zwp_idle_inhibitor_v1_interface, + &idle_inhibitor_impl)); + return wl_resource_get_user_data(inhibitor_resource); +} + +static void idle_inhibitor_v1_destroy(struct wlr_idle_inhibitor_v1 *inhibitor) { + if (!inhibitor) { + return; + } + + wl_signal_emit_mutable(&inhibitor->events.destroy, inhibitor->surface); + + wl_resource_set_user_data(inhibitor->resource, NULL); + wl_list_remove(&inhibitor->link); + wl_list_remove(&inhibitor->surface_destroy.link); + free(inhibitor); +} + +static void idle_inhibitor_v1_handle_resource_destroy( + struct wl_resource *inhibitor_resource) { + struct wlr_idle_inhibitor_v1 *inhibitor = + wlr_idle_inhibitor_v1_from_resource(inhibitor_resource); + idle_inhibitor_v1_destroy(inhibitor); +} + +static void idle_inhibitor_handle_surface_destroy( + struct wl_listener *listener, void *data) { + struct wlr_idle_inhibitor_v1 *inhibitor = + wl_container_of(listener, inhibitor, surface_destroy); + idle_inhibitor_v1_destroy(inhibitor); +} + +static void idle_inhibitor_v1_handle_destroy(struct wl_client *client, + struct wl_resource *inhibitor_resource) { + wl_resource_destroy(inhibitor_resource); +} + +static const struct zwp_idle_inhibitor_v1_interface idle_inhibitor_impl = { + .destroy = idle_inhibitor_v1_handle_destroy, +}; + +static void manager_handle_create_inhibitor(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + struct wlr_idle_inhibit_manager_v1 *manager = + wlr_idle_inhibit_manager_v1_from_resource(manager_resource); + + struct wlr_idle_inhibitor_v1 *inhibitor = calloc(1, sizeof(*inhibitor)); + if (!inhibitor) { + wl_client_post_no_memory(client); + return; + } + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *inhibitor_resource = wl_resource_create(client, + &zwp_idle_inhibitor_v1_interface, version, id); + if (!inhibitor_resource) { + wl_client_post_no_memory(client); + free(inhibitor); + return; + } + + inhibitor->resource = inhibitor_resource; + inhibitor->surface = surface; + wl_signal_init(&inhibitor->events.destroy); + + inhibitor->surface_destroy.notify = idle_inhibitor_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &inhibitor->surface_destroy); + + wl_resource_set_implementation(inhibitor_resource, &idle_inhibitor_impl, + inhibitor, idle_inhibitor_v1_handle_resource_destroy); + + wl_list_insert(&manager->inhibitors, &inhibitor->link); + wl_signal_emit_mutable(&manager->events.new_inhibitor, inhibitor); +} + +static void manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwp_idle_inhibit_manager_v1_interface idle_inhibit_impl = { + .destroy = manager_handle_destroy, + .create_inhibitor = manager_handle_create_inhibitor, +}; + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_idle_inhibit_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +static void idle_inhibit_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_idle_inhibit_manager_v1 *manager = data; + + struct wl_resource *manager_resource = wl_resource_create(wl_client, + &zwp_idle_inhibit_manager_v1_interface, version, id); + if (!manager_resource) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_resource_set_implementation(manager_resource, &idle_inhibit_impl, + manager, NULL); +} + +struct wlr_idle_inhibit_manager_v1 *wlr_idle_inhibit_v1_create(struct wl_display *display) { + struct wlr_idle_inhibit_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + return NULL; + } + + wl_list_init(&manager->inhibitors); + wl_signal_init(&manager->events.new_inhibitor); + wl_signal_init(&manager->events.destroy); + + manager->global = wl_global_create(display, + &zwp_idle_inhibit_manager_v1_interface, 1, + manager, idle_inhibit_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_idle_notify_v1.c b/types/wlr_idle_notify_v1.c new file mode 100644 index 0000000..6283dc6 --- /dev/null +++ b/types/wlr_idle_notify_v1.c @@ -0,0 +1,255 @@ +#include +#include +#include +#include +#include "ext-idle-notify-v1-protocol.h" + +#define IDLE_NOTIFIER_VERSION 1 + +struct wlr_idle_notifier_v1 { + struct wl_global *global; + + bool inhibited; + struct wl_list notifications; // wlr_idle_notification_v1.link + + struct wl_listener display_destroy; +}; + +struct wlr_idle_notification_v1 { + struct wl_resource *resource; + struct wl_list link; // wlr_idle_notifier_v1.notifications + struct wlr_idle_notifier_v1 *notifier; + struct wlr_seat *seat; + + uint32_t timeout_ms; + struct wl_event_source *timer; + + bool idle; + + struct wl_listener seat_destroy; +}; + +static void resource_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct ext_idle_notifier_v1_interface notifier_impl; + +static const struct ext_idle_notification_v1_interface notification_impl = { + .destroy = resource_handle_destroy, +}; + +// Returns NULL if the resource is inert +static struct wlr_idle_notification_v1 *notification_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_idle_notification_v1_interface, ¬ification_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_idle_notifier_v1 *notifier_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_idle_notifier_v1_interface, ¬ifier_impl)); + return wl_resource_get_user_data(resource); +} + +static void notification_set_idle(struct wlr_idle_notification_v1 *notification, + bool idle) { + if (notification->idle == idle) { + return; + } + + if (idle) { + ext_idle_notification_v1_send_idled(notification->resource); + } else { + ext_idle_notification_v1_send_resumed(notification->resource); + } + + notification->idle = idle; +} + +static int notification_handle_timer(void *data) { + struct wlr_idle_notification_v1 *notification = data; + notification_set_idle(notification, true); + return 0; +} + +static void notification_destroy(struct wlr_idle_notification_v1 *notification) { + if (notification == NULL) { + return; + } + wl_list_remove(¬ification->link); + wl_list_remove(¬ification->seat_destroy.link); + if (notification->timer != NULL) { + wl_event_source_remove(notification->timer); + } + wl_resource_set_user_data(notification->resource, NULL); // make inert + free(notification); +} + +static void notification_reset_timer(struct wlr_idle_notification_v1 *notification) { + if (notification->notifier->inhibited) { + notification_set_idle(notification, false); + if (notification->timer != NULL) { + wl_event_source_timer_update(notification->timer, 0); + } + return; + } + + if (notification->timer != NULL) { + wl_event_source_timer_update(notification->timer, + notification->timeout_ms); + } else { + notification_set_idle(notification, true); + } +} + +static void notification_handle_activity(struct wlr_idle_notification_v1 *notification) { + notification_set_idle(notification, false); + notification_reset_timer(notification); +} + +static void notification_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_idle_notification_v1 *notification = + wl_container_of(listener, notification, seat_destroy); + notification_destroy(notification); +} + +static void notification_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_idle_notification_v1 *notification = + notification_from_resource(resource); + notification_destroy(notification); +} + +static void notifier_handle_get_idle_notification(struct wl_client *client, + struct wl_resource *notifier_resource, uint32_t id, uint32_t timeout, + struct wl_resource *seat_resource) { + struct wlr_idle_notifier_v1 *notifier = + notifier_from_resource(notifier_resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + + uint32_t version = wl_resource_get_version(notifier_resource); + struct wl_resource *resource = wl_resource_create(client, + &ext_idle_notification_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, ¬ification_impl, NULL, + notification_handle_resource_destroy); + + if (seat_client == NULL) { + return; // leave the resource inert + } + + struct wlr_idle_notification_v1 *notification = + calloc(1, sizeof(*notification)); + if (notification == NULL) { + wl_client_post_no_memory(client); + return; + } + + notification->notifier = notifier; + notification->resource = resource; + notification->timeout_ms = timeout; + notification->seat = seat_client->seat; + + if (timeout > 0) { + struct wl_display *display = wl_client_get_display(client); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + notification->timer = wl_event_loop_add_timer(loop, + notification_handle_timer, notification); + if (notification->timer == NULL) { + free(notification); + wl_client_post_no_memory(client); + return; + } + } + + notification->seat_destroy.notify = notification_handle_seat_destroy; + wl_signal_add(&seat_client->seat->events.destroy, ¬ification->seat_destroy); + + wl_resource_set_user_data(resource, notification); + wl_list_insert(¬ifier->notifications, ¬ification->link); + + notification_reset_timer(notification); +} + +static const struct ext_idle_notifier_v1_interface notifier_impl = { + .destroy = resource_handle_destroy, + .get_idle_notification = notifier_handle_get_idle_notification, +}; + +static void notifier_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_idle_notifier_v1 *notifier = data; + + struct wl_resource *resource = wl_resource_create(client, + &ext_idle_notifier_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, ¬ifier_impl, notifier, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_idle_notifier_v1 *notifier = + wl_container_of(listener, notifier, display_destroy); + wl_global_destroy(notifier->global); + free(notifier); +} + +struct wlr_idle_notifier_v1 *wlr_idle_notifier_v1_create(struct wl_display *display) { + struct wlr_idle_notifier_v1 *notifier = calloc(1, sizeof(*notifier)); + if (notifier == NULL) { + return NULL; + } + + notifier->global = wl_global_create(display, + &ext_idle_notifier_v1_interface, IDLE_NOTIFIER_VERSION, notifier, + notifier_bind); + if (notifier->global == NULL) { + free(notifier); + return NULL; + } + + wl_list_init(¬ifier->notifications); + + notifier->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, ¬ifier->display_destroy); + + return notifier; +} + +void wlr_idle_notifier_v1_set_inhibited(struct wlr_idle_notifier_v1 *notifier, + bool inhibited) { + if (notifier->inhibited == inhibited) { + return; + } + + notifier->inhibited = inhibited; + + struct wlr_idle_notification_v1 *notification; + wl_list_for_each(notification, ¬ifier->notifications, link) { + notification_reset_timer(notification); + } +} + +void wlr_idle_notifier_v1_notify_activity(struct wlr_idle_notifier_v1 *notifier, + struct wlr_seat *seat) { + if (notifier->inhibited) { + return; + } + + struct wlr_idle_notification_v1 *notification; + wl_list_for_each(notification, ¬ifier->notifications, link) { + if (notification->seat == seat) { + notification_handle_activity(notification); + } + } +} diff --git a/types/wlr_input_device.c b/types/wlr_input_device.c new file mode 100644 index 0000000..ede2e04 --- /dev/null +++ b/types/wlr_input_device.c @@ -0,0 +1,26 @@ + +#include +#include +#include "interfaces/wlr_input_device.h" + +void wlr_input_device_init(struct wlr_input_device *dev, + enum wlr_input_device_type type, const char *name) { + *dev = (struct wlr_input_device){ + .type = type, + .name = strdup(name), + }; + + wl_signal_init(&dev->events.destroy); +} + +void wlr_input_device_finish(struct wlr_input_device *wlr_device) { + if (!wlr_device) { + return; + } + + wl_signal_emit_mutable(&wlr_device->events.destroy, wlr_device); + + wl_list_remove(&wlr_device->events.destroy.listener_list); + + free(wlr_device->name); +} diff --git a/types/wlr_input_method_v2.c b/types/wlr_input_method_v2.c new file mode 100644 index 0000000..e50b02a --- /dev/null +++ b/types/wlr_input_method_v2.c @@ -0,0 +1,625 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "input-method-unstable-v2-protocol.h" +#include "util/shm.h" + +// Note: zwp_input_popup_surface_v2 and zwp_input_method_keyboard_grab_v2 objects +// become inert when the corresponding zwp_input_method_v2 is destroyed + +static const struct zwp_input_method_v2_interface input_method_impl; +static const struct zwp_input_method_keyboard_grab_v2_interface keyboard_grab_impl; + +static void input_state_reset(struct wlr_input_method_v2_state *state) { + free(state->commit_text); + free(state->preedit.text); + *state = (struct wlr_input_method_v2_state){0}; +} + +static void popup_surface_destroy(struct wlr_input_popup_surface_v2 *popup_surface) { + wlr_surface_unmap(popup_surface->surface); + + wl_signal_emit_mutable(&popup_surface->events.destroy, NULL); + wl_list_remove(&popup_surface->link); + wl_resource_set_user_data(popup_surface->resource, NULL); + free(popup_surface); +} + +static struct wlr_input_method_v2 *input_method_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_input_method_v2_interface, &input_method_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_input_method_keyboard_grab_v2 *keyboard_grab_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_input_method_keyboard_grab_v2_interface, + &keyboard_grab_impl)); + return wl_resource_get_user_data(resource); +} + +static void input_method_destroy(struct wlr_input_method_v2 *input_method) { + struct wlr_input_popup_surface_v2 *popup_surface, *tmp; + wl_list_for_each_safe( + popup_surface, tmp, &input_method->popup_surfaces, link) { + popup_surface_destroy(popup_surface); + } + wl_signal_emit_mutable(&input_method->events.destroy, input_method); + wl_list_remove(wl_resource_get_link(input_method->resource)); + wl_list_remove(&input_method->seat_client_destroy.link); + wlr_input_method_keyboard_grab_v2_destroy(input_method->keyboard_grab); + input_state_reset(&input_method->pending); + input_state_reset(&input_method->current); + free(input_method); +} + +static void input_method_resource_destroy(struct wl_resource *resource) { + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + input_method_destroy(input_method); +} + +static void im_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void im_commit(struct wl_client *client, struct wl_resource *resource, + uint32_t serial) { + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + if (serial != input_method->current_serial) { + input_state_reset(&input_method->pending); + return; + } + input_state_reset(&input_method->current); + + // This transfers ownership of the current commit_text and + // preedit.text from pending to current: + input_method->current = input_method->pending; + input_method->pending = (struct wlr_input_method_v2_state){0}; + + wl_signal_emit_mutable(&input_method->events.commit, input_method); +} + +static void im_commit_string(struct wl_client *client, + struct wl_resource *resource, const char *text) { + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + free(input_method->pending.commit_text); + input_method->pending.commit_text = strdup(text); + if (input_method->pending.commit_text == NULL) { + wl_client_post_no_memory(client); + return; + } +} + +static void im_set_preedit_string(struct wl_client *client, + struct wl_resource *resource, const char *text, int32_t cursor_begin, + int32_t cursor_end) { + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + input_method->pending.preedit.cursor_begin = cursor_begin; + input_method->pending.preedit.cursor_end = cursor_end; + free(input_method->pending.preedit.text); + input_method->pending.preedit.text = strdup(text); + if (input_method->pending.preedit.text == NULL) { + wl_client_post_no_memory(client); + return; + } +} + +static void im_delete_surrounding_text(struct wl_client *client, + struct wl_resource *resource, + uint32_t before_length, uint32_t after_length) { + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + input_method->pending.delete.before_length = before_length; + input_method->pending.delete.after_length = after_length; +} + +void wlr_input_popup_surface_v2_send_text_input_rectangle( + struct wlr_input_popup_surface_v2 *popup_surface, + struct wlr_box *sbox) { + zwp_input_popup_surface_v2_send_text_input_rectangle( + popup_surface->resource, sbox->x, sbox->y, sbox->width, sbox->height); +} + +static void popup_surface_consider_map(struct wlr_input_popup_surface_v2 *popup_surface) { + if (wlr_surface_has_buffer(popup_surface->surface) && + popup_surface->input_method->client_active) { + wlr_surface_map(popup_surface->surface); + } +} + +static void popup_surface_surface_role_commit(struct wlr_surface *surface) { + struct wlr_input_popup_surface_v2 *popup_surface = + wlr_input_popup_surface_v2_try_from_wlr_surface(surface); + if (popup_surface == NULL) { + return; + } + + popup_surface_consider_map(popup_surface); +} + +static void popup_surface_surface_role_destroy(struct wlr_surface *surface) { + struct wlr_input_popup_surface_v2 *popup_surface = + wlr_input_popup_surface_v2_try_from_wlr_surface(surface); + if (popup_surface == NULL) { + return; + } + + popup_surface_destroy(popup_surface); +} + +static const struct wlr_surface_role input_popup_surface_v2_role = { + .name = "zwp_input_popup_surface_v2", + .commit = popup_surface_surface_role_commit, + .destroy = popup_surface_surface_role_destroy, +}; + +static const struct zwp_input_popup_surface_v2_interface input_popup_impl; + +static struct wlr_input_popup_surface_v2 *popup_surface_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_input_popup_surface_v2_interface, + &input_popup_impl)); + return wl_resource_get_user_data(resource); +} + +struct wlr_input_popup_surface_v2 *wlr_input_popup_surface_v2_try_from_wlr_surface( + struct wlr_surface *surface) { + if (surface->role != &input_popup_surface_v2_role || surface->role_resource == NULL) { + return NULL; + } + return popup_surface_from_resource(surface->role_resource); +} + +static void popup_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_input_popup_surface_v2_interface input_popup_impl = { + .destroy = popup_destroy, +}; + +static void im_get_input_popup_surface(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + + struct wlr_input_popup_surface_v2 *popup_surface = calloc(1, sizeof(*popup_surface)); + if (!popup_surface) { + wl_client_post_no_memory(client); + return; + } + + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + if (!wlr_surface_set_role(surface, &input_popup_surface_v2_role, + resource, ZWP_INPUT_METHOD_V2_ERROR_ROLE)) { + free(popup_surface); + return; + } + + struct wl_resource *popup_resource = wl_resource_create( + client, &zwp_input_popup_surface_v2_interface, + wl_resource_get_version(resource), id); + if (!popup_resource) { + free(popup_surface); + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(popup_resource, &input_popup_impl, + popup_surface, NULL); + + wlr_surface_set_role_object(surface, popup_resource); + + popup_surface->resource = popup_resource; + popup_surface->input_method = input_method; + popup_surface->surface = surface; + + wl_signal_init(&popup_surface->events.destroy); + + popup_surface_consider_map(popup_surface); + + wl_list_insert(&input_method->popup_surfaces, &popup_surface->link); + wl_signal_emit_mutable(&input_method->events.new_popup_surface, popup_surface); +} + +void wlr_input_method_keyboard_grab_v2_destroy( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab) { + if (!keyboard_grab) { + return; + } + wl_signal_emit_mutable(&keyboard_grab->events.destroy, keyboard_grab); + keyboard_grab->input_method->keyboard_grab = NULL; + if (keyboard_grab->keyboard) { + wl_list_remove(&keyboard_grab->keyboard_keymap.link); + wl_list_remove(&keyboard_grab->keyboard_repeat_info.link); + wl_list_remove(&keyboard_grab->keyboard_destroy.link); + } + wl_resource_set_user_data(keyboard_grab->resource, NULL); + free(keyboard_grab); +} + +static void keyboard_grab_resource_destroy(struct wl_resource *resource) { + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + keyboard_grab_from_resource(resource); + wlr_input_method_keyboard_grab_v2_destroy(keyboard_grab); +} + +static void keyboard_grab_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_input_method_keyboard_grab_v2_interface keyboard_grab_impl = { + .release = keyboard_grab_release, +}; + +void wlr_input_method_keyboard_grab_v2_send_key( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + uint32_t time, uint32_t key, uint32_t state) { + zwp_input_method_keyboard_grab_v2_send_key( + keyboard_grab->resource, + wlr_seat_client_next_serial(keyboard_grab->input_method->seat_client), + time, key, state); +} + +void wlr_input_method_keyboard_grab_v2_send_modifiers( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + struct wlr_keyboard_modifiers *modifiers) { + zwp_input_method_keyboard_grab_v2_send_modifiers( + keyboard_grab->resource, + wlr_seat_client_next_serial(keyboard_grab->input_method->seat_client), + modifiers->depressed, modifiers->latched, + modifiers->locked, modifiers->group); +} + +static bool keyboard_grab_send_keymap( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + struct wlr_keyboard *keyboard) { + int keymap_fd = allocate_shm_file(keyboard->keymap_size); + if (keymap_fd < 0) { + wlr_log(WLR_ERROR, "creating a keymap file for %zu bytes failed", + keyboard->keymap_size); + return false; + } + + void *ptr = mmap(NULL, keyboard->keymap_size, PROT_READ | PROT_WRITE, + MAP_SHARED, keymap_fd, 0); + if (ptr == MAP_FAILED) { + wlr_log(WLR_ERROR, "failed to mmap() %zu bytes", + keyboard->keymap_size); + close(keymap_fd); + return false; + } + + memcpy(ptr, keyboard->keymap_string, keyboard->keymap_size); + munmap(ptr, keyboard->keymap_size); + + zwp_input_method_keyboard_grab_v2_send_keymap(keyboard_grab->resource, + WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, keymap_fd, + keyboard->keymap_size); + + close(keymap_fd); + return true; +} + +static void keyboard_grab_send_repeat_info( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + struct wlr_keyboard *keyboard) { + zwp_input_method_keyboard_grab_v2_send_repeat_info( + keyboard_grab->resource, keyboard->repeat_info.rate, + keyboard->repeat_info.delay); +} + +static void handle_keyboard_keymap(struct wl_listener *listener, void *data) { + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + wl_container_of(listener, keyboard_grab, keyboard_keymap); + keyboard_grab_send_keymap(keyboard_grab, data); +} + +static void handle_keyboard_repeat_info(struct wl_listener *listener, + void *data) { + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + wl_container_of(listener, keyboard_grab, keyboard_repeat_info); + keyboard_grab_send_repeat_info(keyboard_grab, data); +} + +static void handle_keyboard_destroy(struct wl_listener *listener, + void *data) { + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + wl_container_of(listener, keyboard_grab, keyboard_destroy); + wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, NULL); +} + +void wlr_input_method_keyboard_grab_v2_set_keyboard( + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, + struct wlr_keyboard *keyboard) { + if (keyboard == keyboard_grab->keyboard) { + return; + } + + if (keyboard_grab->keyboard) { + wl_list_remove(&keyboard_grab->keyboard_keymap.link); + wl_list_remove(&keyboard_grab->keyboard_repeat_info.link); + wl_list_remove(&keyboard_grab->keyboard_destroy.link); + } + + if (keyboard) { + if (keyboard_grab->keyboard == NULL || + strcmp(keyboard_grab->keyboard->keymap_string, + keyboard->keymap_string) != 0) { + // send keymap only if it is changed, or if input method is not + // aware that it did not change and blindly send it back with + // virtual keyboard, it may cause an infinite recursion. + if (!keyboard_grab_send_keymap(keyboard_grab, keyboard)) { + wlr_log(WLR_ERROR, "Failed to send keymap for input-method keyboard grab"); + return; + } + } + keyboard_grab_send_repeat_info(keyboard_grab, keyboard); + keyboard_grab->keyboard_keymap.notify = handle_keyboard_keymap; + wl_signal_add(&keyboard->events.keymap, + &keyboard_grab->keyboard_keymap); + keyboard_grab->keyboard_repeat_info.notify = + handle_keyboard_repeat_info; + wl_signal_add(&keyboard->events.repeat_info, + &keyboard_grab->keyboard_repeat_info); + keyboard_grab->keyboard_destroy.notify = + handle_keyboard_destroy; + wl_signal_add(&keyboard->base.events.destroy, + &keyboard_grab->keyboard_destroy); + + wlr_input_method_keyboard_grab_v2_send_modifiers(keyboard_grab, + &keyboard->modifiers); + } + + keyboard_grab->keyboard = keyboard; +}; + +static void im_grab_keyboard(struct wl_client *client, + struct wl_resource *resource, uint32_t keyboard) { + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + if (input_method->keyboard_grab) { + // Already grabbed + return; + } + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = calloc(1, sizeof(*keyboard_grab)); + if (!keyboard_grab) { + wl_client_post_no_memory(client); + return; + } + struct wl_resource *keyboard_grab_resource = wl_resource_create( + client, &zwp_input_method_keyboard_grab_v2_interface, + wl_resource_get_version(resource), keyboard); + if (keyboard_grab_resource == NULL) { + free(keyboard_grab); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(keyboard_grab_resource, + &keyboard_grab_impl, keyboard_grab, + keyboard_grab_resource_destroy); + keyboard_grab->resource = keyboard_grab_resource; + keyboard_grab->input_method = input_method; + input_method->keyboard_grab = keyboard_grab; + wl_signal_init(&keyboard_grab->events.destroy); + wl_signal_emit_mutable(&input_method->events.grab_keyboard, keyboard_grab); +} + +static const struct zwp_input_method_v2_interface input_method_impl = { + .destroy = im_destroy, + .commit = im_commit, + .commit_string = im_commit_string, + .set_preedit_string = im_set_preedit_string, + .delete_surrounding_text = im_delete_surrounding_text, + .get_input_popup_surface = im_get_input_popup_surface, + .grab_keyboard = im_grab_keyboard, +}; + +void wlr_input_method_v2_send_activate( + struct wlr_input_method_v2 *input_method) { + zwp_input_method_v2_send_activate(input_method->resource); + input_method->active = true; +} + +void wlr_input_method_v2_send_deactivate( + struct wlr_input_method_v2 *input_method) { + zwp_input_method_v2_send_deactivate(input_method->resource); + input_method->active = false; +} + +void wlr_input_method_v2_send_surrounding_text( + struct wlr_input_method_v2 *input_method, const char *text, + uint32_t cursor, uint32_t anchor) { + const char *send_text = text; + if (!send_text) { + send_text = ""; + } + zwp_input_method_v2_send_surrounding_text(input_method->resource, send_text, + cursor, anchor); +} + +void wlr_input_method_v2_send_text_change_cause( + struct wlr_input_method_v2 *input_method, uint32_t cause) { + zwp_input_method_v2_send_text_change_cause(input_method->resource, cause); +} + +void wlr_input_method_v2_send_content_type( + struct wlr_input_method_v2 *input_method, + uint32_t hint, uint32_t purpose) { + zwp_input_method_v2_send_content_type(input_method->resource, hint, + purpose); +} + +void wlr_input_method_v2_send_done(struct wlr_input_method_v2 *input_method) { + zwp_input_method_v2_send_done(input_method->resource); + input_method->client_active = input_method->active; + input_method->current_serial++; + struct wlr_input_popup_surface_v2 *popup_surface; + wl_list_for_each(popup_surface, &input_method->popup_surfaces, link) { + popup_surface_consider_map(popup_surface); + } +} + +void wlr_input_method_v2_send_unavailable( + struct wlr_input_method_v2 *input_method) { + zwp_input_method_v2_send_unavailable(input_method->resource); + struct wl_resource *resource = input_method->resource; + input_method_destroy(input_method); + wl_resource_set_user_data(resource, NULL); +} + +static const struct zwp_input_method_manager_v2_interface + input_method_manager_impl; + +static struct wlr_input_method_manager_v2 *input_method_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_input_method_manager_v2_interface, &input_method_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void input_method_handle_seat_client_destroy(struct wl_listener *listener, + void *data) { + struct wlr_input_method_v2 *input_method = wl_container_of(listener, + input_method, seat_client_destroy); + wlr_input_method_v2_send_unavailable(input_method); +} + +static void manager_get_input_method(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat, + uint32_t input_method_id) { + struct wlr_input_method_manager_v2 *im_manager = + input_method_manager_from_resource(resource); + struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); + + int version = wl_resource_get_version(resource); + struct wl_resource *im_resource = wl_resource_create(client, + &zwp_input_method_v2_interface, version, input_method_id); + if (im_resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(im_resource, &input_method_impl, + NULL, input_method_resource_destroy); + wl_list_init(wl_resource_get_link(im_resource)); + if (seat_client == NULL) { + return; + } + + struct wlr_input_method_v2 *input_method = calloc(1, sizeof(*input_method)); + if (!input_method) { + wl_client_post_no_memory(client); + return; + } + wl_list_init(&input_method->popup_surfaces); + wl_signal_init(&input_method->events.commit); + wl_signal_init(&input_method->events.new_popup_surface); + wl_signal_init(&input_method->events.grab_keyboard); + wl_signal_init(&input_method->events.destroy); + + input_method->seat_client = seat_client; + input_method->seat = input_method->seat_client->seat; + wl_signal_add(&input_method->seat_client->events.destroy, + &input_method->seat_client_destroy); + input_method->seat_client_destroy.notify = + input_method_handle_seat_client_destroy; + + input_method->resource = im_resource; + wl_resource_set_user_data(im_resource, input_method); + wl_list_insert(&im_manager->input_methods, + wl_resource_get_link(input_method->resource)); + wl_signal_emit_mutable(&im_manager->events.input_method, input_method); +} + +static void manager_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_input_method_manager_v2_interface + input_method_manager_impl = { + .get_input_method = manager_get_input_method, + .destroy = manager_destroy, +}; + +static void input_method_manager_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_input_method_manager_v2 *im_manager = data; + + struct wl_resource *bound_resource = wl_resource_create(wl_client, + &zwp_input_method_manager_v2_interface, version, id); + if (bound_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(bound_resource, &input_method_manager_impl, + im_manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_input_method_manager_v2 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_input_method_manager_v2 *wlr_input_method_manager_v2_create( + struct wl_display *display) { + struct wlr_input_method_manager_v2 *im_manager = calloc(1, sizeof(*im_manager)); + if (!im_manager) { + return NULL; + } + wl_signal_init(&im_manager->events.input_method); + wl_signal_init(&im_manager->events.destroy); + wl_list_init(&im_manager->input_methods); + + im_manager->global = wl_global_create(display, + &zwp_input_method_manager_v2_interface, 1, im_manager, + input_method_manager_bind); + if (!im_manager->global) { + free(im_manager); + return NULL; + } + + im_manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &im_manager->display_destroy); + + return im_manager; +} diff --git a/types/wlr_keyboard.c b/types/wlr_keyboard.c new file mode 100644 index 0000000..98978ee --- /dev/null +++ b/types/wlr_keyboard.c @@ -0,0 +1,307 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "interfaces/wlr_input_device.h" +#include "types/wlr_keyboard.h" +#include "util/set.h" +#include "util/shm.h" +#include "util/time.h" + +struct wlr_keyboard *wlr_keyboard_from_input_device( + struct wlr_input_device *input_device) { + assert(input_device->type == WLR_INPUT_DEVICE_KEYBOARD); + return wl_container_of(input_device, (struct wlr_keyboard *)NULL, base); +} + +void keyboard_led_update(struct wlr_keyboard *keyboard) { + if (keyboard->xkb_state == NULL) { + return; + } + + uint32_t leds = 0; + for (uint32_t i = 0; i < WLR_LED_COUNT; ++i) { + if (xkb_state_led_index_is_active(keyboard->xkb_state, + keyboard->led_indexes[i])) { + leds |= (1 << i); + } + } + wlr_keyboard_led_update(keyboard, leds); +} + +/** + * Update the modifier state of the wlr-keyboard. Returns true if the modifier + * state changed. + */ +bool keyboard_modifier_update(struct wlr_keyboard *keyboard) { + if (keyboard->xkb_state == NULL) { + return false; + } + + xkb_mod_mask_t depressed = xkb_state_serialize_mods(keyboard->xkb_state, + XKB_STATE_MODS_DEPRESSED); + xkb_mod_mask_t latched = xkb_state_serialize_mods(keyboard->xkb_state, + XKB_STATE_MODS_LATCHED); + xkb_mod_mask_t locked = xkb_state_serialize_mods(keyboard->xkb_state, + XKB_STATE_MODS_LOCKED); + xkb_layout_index_t group = xkb_state_serialize_layout(keyboard->xkb_state, + XKB_STATE_LAYOUT_EFFECTIVE); + if (depressed == keyboard->modifiers.depressed && + latched == keyboard->modifiers.latched && + locked == keyboard->modifiers.locked && + group == keyboard->modifiers.group) { + return false; + } + + keyboard->modifiers.depressed = depressed; + keyboard->modifiers.latched = latched; + keyboard->modifiers.locked = locked; + keyboard->modifiers.group = group; + + return true; +} + +void keyboard_key_update(struct wlr_keyboard *keyboard, + struct wlr_keyboard_key_event *event) { + if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + set_add(keyboard->keycodes, &keyboard->num_keycodes, + WLR_KEYBOARD_KEYS_CAP, event->keycode); + } + if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { + set_remove(keyboard->keycodes, &keyboard->num_keycodes, + WLR_KEYBOARD_KEYS_CAP, event->keycode); + } + + assert(keyboard->num_keycodes <= WLR_KEYBOARD_KEYS_CAP); +} + +void wlr_keyboard_notify_modifiers(struct wlr_keyboard *keyboard, + uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) { + if (keyboard->xkb_state == NULL) { + return; + } + xkb_state_update_mask(keyboard->xkb_state, mods_depressed, mods_latched, + mods_locked, 0, 0, group); + + bool updated = keyboard_modifier_update(keyboard); + if (updated) { + wl_signal_emit_mutable(&keyboard->events.modifiers, keyboard); + } + + keyboard_led_update(keyboard); +} + +void wlr_keyboard_notify_key(struct wlr_keyboard *keyboard, + struct wlr_keyboard_key_event *event) { + keyboard_key_update(keyboard, event); + wl_signal_emit_mutable(&keyboard->events.key, event); + + if (keyboard->xkb_state == NULL) { + return; + } + + if (event->update_state) { + uint32_t keycode = event->keycode + 8; + xkb_state_update_key(keyboard->xkb_state, keycode, + event->state == WL_KEYBOARD_KEY_STATE_PRESSED ? XKB_KEY_DOWN : XKB_KEY_UP); + } + + bool updated = keyboard_modifier_update(keyboard); + if (updated) { + wl_signal_emit_mutable(&keyboard->events.modifiers, keyboard); + } + + keyboard_led_update(keyboard); +} + +void wlr_keyboard_init(struct wlr_keyboard *kb, + const struct wlr_keyboard_impl *impl, const char *name) { + *kb = (struct wlr_keyboard){ + .impl = impl, + .keymap_fd = -1, + + // Sane defaults + .repeat_info.rate = 25, + .repeat_info.delay = 600, + }; + wlr_input_device_init(&kb->base, WLR_INPUT_DEVICE_KEYBOARD, name); + + wl_signal_init(&kb->events.key); + wl_signal_init(&kb->events.modifiers); + wl_signal_init(&kb->events.keymap); + wl_signal_init(&kb->events.repeat_info); +} + +static void keyboard_unset_keymap(struct wlr_keyboard *kb) { + xkb_keymap_unref(kb->keymap); + kb->keymap = NULL; + xkb_state_unref(kb->xkb_state); + kb->xkb_state = NULL; + free(kb->keymap_string); + kb->keymap_string = NULL; + kb->keymap_size = 0; + if (kb->keymap_fd >= 0) { + close(kb->keymap_fd); + } + kb->keymap_fd = -1; +} + +void wlr_keyboard_finish(struct wlr_keyboard *kb) { + /* Release pressed keys */ + size_t orig_num_keycodes = kb->num_keycodes; + for (size_t i = 0; i < orig_num_keycodes; ++i) { + assert(kb->num_keycodes == orig_num_keycodes - i); + struct wlr_keyboard_key_event event = { + .time_msec = get_current_time_msec(), + .keycode = kb->keycodes[orig_num_keycodes - i - 1], + .update_state = false, + .state = WL_KEYBOARD_KEY_STATE_RELEASED, + }; + wlr_keyboard_notify_key(kb, &event); // updates num_keycodes + } + + wlr_input_device_finish(&kb->base); + + keyboard_unset_keymap(kb); +} + +void wlr_keyboard_led_update(struct wlr_keyboard *kb, uint32_t leds) { + if (kb->leds == leds) { + return; + } + + kb->leds = leds; + + if (kb->impl && kb->impl->led_update) { + kb->impl->led_update(kb, leds); + } +} + +bool wlr_keyboard_set_keymap(struct wlr_keyboard *kb, struct xkb_keymap *keymap) { + if (keymap == NULL) { + keyboard_unset_keymap(kb); + wl_signal_emit_mutable(&kb->events.keymap, kb); + return true; + } + + struct xkb_state *xkb_state = xkb_state_new(keymap); + if (xkb_state == NULL) { + wlr_log(WLR_ERROR, "Failed to create XKB state"); + return false; + } + + char *keymap_str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1); + if (keymap_str == NULL) { + wlr_log(WLR_ERROR, "Failed to get string version of keymap"); + goto error_xkb_state; + } + size_t keymap_size = strlen(keymap_str) + 1; + + int rw_fd = -1, ro_fd = -1; + if (!allocate_shm_file_pair(keymap_size, &rw_fd, &ro_fd)) { + wlr_log(WLR_ERROR, "Failed to allocate shm file for keymap"); + goto error_keymap_str; + } + + void *dst = mmap(NULL, keymap_size, PROT_READ | PROT_WRITE, MAP_SHARED, rw_fd, 0); + close(rw_fd); + if (dst == MAP_FAILED) { + wlr_log_errno(WLR_ERROR, "mmap failed"); + close(ro_fd); + goto error_keymap_str; + } + + memcpy(dst, keymap_str, keymap_size); + munmap(dst, keymap_size); + + keyboard_unset_keymap(kb); + kb->keymap = xkb_keymap_ref(keymap); + kb->xkb_state = xkb_state; + kb->keymap_string = keymap_str; + kb->keymap_size = keymap_size; + kb->keymap_fd = ro_fd; + + const char *led_names[WLR_LED_COUNT] = { + XKB_LED_NAME_NUM, + XKB_LED_NAME_CAPS, + XKB_LED_NAME_SCROLL, + }; + for (size_t i = 0; i < WLR_LED_COUNT; ++i) { + kb->led_indexes[i] = xkb_map_led_get_index(kb->keymap, led_names[i]); + } + + const char *mod_names[WLR_MODIFIER_COUNT] = { + XKB_MOD_NAME_SHIFT, + XKB_MOD_NAME_CAPS, + XKB_MOD_NAME_CTRL, // "Control" + XKB_MOD_NAME_ALT, // "Mod1" + XKB_MOD_NAME_NUM, // "Mod2" + "Mod3", + XKB_MOD_NAME_LOGO, // "Mod4" + "Mod5", + }; + // TODO: there's also "Ctrl", "Alt"? + for (size_t i = 0; i < WLR_MODIFIER_COUNT; ++i) { + kb->mod_indexes[i] = xkb_map_mod_get_index(kb->keymap, mod_names[i]); + } + + for (size_t i = 0; i < kb->num_keycodes; ++i) { + xkb_keycode_t keycode = kb->keycodes[i] + 8; + xkb_state_update_key(kb->xkb_state, keycode, XKB_KEY_DOWN); + } + + keyboard_modifier_update(kb); + + wl_signal_emit_mutable(&kb->events.keymap, kb); + + return true; + +error_keymap_str: + free(keymap_str); +error_xkb_state: + xkb_state_unref(xkb_state); + return false; +} + +void wlr_keyboard_set_repeat_info(struct wlr_keyboard *kb, int32_t rate, + int32_t delay) { + if (kb->repeat_info.rate == rate && kb->repeat_info.delay == delay) { + return; + } + kb->repeat_info.rate = rate; + kb->repeat_info.delay = delay; + wl_signal_emit_mutable(&kb->events.repeat_info, kb); +} + +uint32_t wlr_keyboard_get_modifiers(struct wlr_keyboard *kb) { + xkb_mod_mask_t mask = kb->modifiers.depressed | kb->modifiers.latched; + uint32_t modifiers = 0; + for (size_t i = 0; i < WLR_MODIFIER_COUNT; ++i) { + if (kb->mod_indexes[i] != XKB_MOD_INVALID && + (mask & (1 << kb->mod_indexes[i]))) { + modifiers |= (1 << i); + } + } + return modifiers; +} + +bool wlr_keyboard_keymaps_match(struct xkb_keymap *km1, + struct xkb_keymap *km2) { + if (!km1 && !km2) { + return true; + } + if (!km1 || !km2) { + return false; + } + char *km1_str = xkb_keymap_get_as_string(km1, XKB_KEYMAP_FORMAT_TEXT_V1); + char *km2_str = xkb_keymap_get_as_string(km2, XKB_KEYMAP_FORMAT_TEXT_V1); + bool result = strcmp(km1_str, km2_str) == 0; + free(km1_str); + free(km2_str); + return result; +} diff --git a/types/wlr_keyboard_group.c b/types/wlr_keyboard_group.c new file mode 100644 index 0000000..ef557a9 --- /dev/null +++ b/types/wlr_keyboard_group.c @@ -0,0 +1,318 @@ +#include +#include +#include +#include +#include +#include +#include +#include "types/wlr_keyboard.h" +#include "wlr/interfaces/wlr_keyboard.h" +#include "wlr/types/wlr_keyboard.h" +#include "wlr/types/wlr_keyboard_group.h" +#include "wlr/util/log.h" + +struct keyboard_group_device { + struct wlr_keyboard *keyboard; + struct wl_listener key; + struct wl_listener modifiers; + struct wl_listener keymap; + struct wl_listener repeat_info; + struct wl_listener destroy; + struct wl_list link; // wlr_keyboard_group.devices +}; + +struct keyboard_group_key { + uint32_t keycode; + size_t count; + struct wl_list link; // wlr_keyboard_group.keys +}; + +static void keyboard_set_leds(struct wlr_keyboard *kb, uint32_t leds) { + struct wlr_keyboard_group *group = wlr_keyboard_group_from_wlr_keyboard(kb); + struct keyboard_group_device *device; + wl_list_for_each(device, &group->devices, link) { + wlr_keyboard_led_update(device->keyboard, leds); + } +} + +static const struct wlr_keyboard_impl impl = { + .name = "keyboard-group", + .led_update = keyboard_set_leds +}; + +struct wlr_keyboard_group *wlr_keyboard_group_create(void) { + struct wlr_keyboard_group *group = calloc(1, sizeof(*group)); + if (!group) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_keyboard_group"); + return NULL; + } + + wlr_keyboard_init(&group->keyboard, &impl, "wlr_keyboard_group"); + wl_list_init(&group->devices); + wl_list_init(&group->keys); + + wl_signal_init(&group->events.enter); + wl_signal_init(&group->events.leave); + + return group; +} + +struct wlr_keyboard_group *wlr_keyboard_group_from_wlr_keyboard( + struct wlr_keyboard *keyboard) { + if (keyboard->impl != &impl) { + return NULL; + } + struct wlr_keyboard_group *group = wl_container_of(keyboard, group, keyboard); + return group; +} + +static bool process_key(struct keyboard_group_device *group_device, + struct wlr_keyboard_key_event *event) { + struct wlr_keyboard_group *group = group_device->keyboard->group; + + struct keyboard_group_key *key, *tmp; + wl_list_for_each_safe(key, tmp, &group->keys, link) { + if (key->keycode != event->keycode) { + continue; + } + if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + key->count++; + return false; + } + if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { + key->count--; + if (key->count > 0) { + return false; + } + wl_list_remove(&key->link); + free(key); + } + break; + } + + if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + struct keyboard_group_key *key = calloc(1, sizeof(*key)); + if (!key) { + wlr_log(WLR_ERROR, "Failed to allocate keyboard_group_key"); + return false; + } + key->keycode = event->keycode; + key->count = 1; + wl_list_insert(&group->keys, &key->link); + } + + return true; +} + +static void handle_keyboard_key(struct wl_listener *listener, void *data) { + struct keyboard_group_device *group_device = + wl_container_of(listener, group_device, key); + if (process_key(group_device, data)) { + wlr_keyboard_notify_key(&group_device->keyboard->group->keyboard, data); + } +} + +static void handle_keyboard_modifiers(struct wl_listener *listener, + void *data) { + // Sync the effective layout (group modifier) to all keyboards. The rest of + // the modifiers will be derived from the wlr_keyboard_group's key state + struct keyboard_group_device *group_device = + wl_container_of(listener, group_device, modifiers); + struct wlr_keyboard_modifiers mods = group_device->keyboard->modifiers; + + struct keyboard_group_device *device; + wl_list_for_each(device, &group_device->keyboard->group->devices, link) { + if (mods.depressed != device->keyboard->modifiers.depressed || + mods.latched != device->keyboard->modifiers.latched || + mods.locked != device->keyboard->modifiers.locked || + mods.group != device->keyboard->modifiers.group) { + wlr_keyboard_notify_modifiers(device->keyboard, + mods.depressed, mods.latched, mods.locked, mods.group); + return; + } + } + + wlr_keyboard_notify_modifiers(&group_device->keyboard->group->keyboard, + mods.depressed, mods.latched, mods.locked, mods.group); +} + +static void handle_keyboard_keymap(struct wl_listener *listener, void *data) { + struct keyboard_group_device *group_device = + wl_container_of(listener, group_device, keymap); + struct wlr_keyboard *keyboard = group_device->keyboard; + + if (!wlr_keyboard_keymaps_match(keyboard->group->keyboard.keymap, + keyboard->keymap)) { + struct keyboard_group_device *device; + wl_list_for_each(device, &keyboard->group->devices, link) { + if (!wlr_keyboard_keymaps_match(keyboard->keymap, + device->keyboard->keymap)) { + wlr_keyboard_set_keymap(device->keyboard, keyboard->keymap); + return; + } + } + } + + wlr_keyboard_set_keymap(&keyboard->group->keyboard, keyboard->keymap); +} + +static void handle_keyboard_repeat_info(struct wl_listener *listener, + void *data) { + struct keyboard_group_device *group_device = + wl_container_of(listener, group_device, repeat_info); + struct wlr_keyboard *keyboard = group_device->keyboard; + + struct keyboard_group_device *device; + wl_list_for_each(device, &keyboard->group->devices, link) { + struct wlr_keyboard *devkb = device->keyboard; + if (devkb->repeat_info.rate != keyboard->repeat_info.rate || + devkb->repeat_info.delay != keyboard->repeat_info.delay) { + wlr_keyboard_set_repeat_info(devkb, keyboard->repeat_info.rate, + keyboard->repeat_info.delay); + return; + } + } + + wlr_keyboard_set_repeat_info(&keyboard->group->keyboard, + keyboard->repeat_info.rate, keyboard->repeat_info.delay); +} + +static void refresh_state(struct keyboard_group_device *device, + enum wl_keyboard_key_state state) { + struct wl_array keys; + wl_array_init(&keys); + + for (size_t i = 0; i < device->keyboard->num_keycodes; i++) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + struct wlr_keyboard_key_event event = { + .time_msec = (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000, + .keycode = device->keyboard->keycodes[i], + .update_state = true, + .state = state + }; + + // Update the group's key state and determine whether this is a unique + // key that needs to be passed on to the compositor + if (process_key(device, &event)) { + // Update state for wlr_keyboard_group's keyboard + keyboard_key_update(&device->keyboard->group->keyboard, &event); + keyboard_modifier_update(&device->keyboard->group->keyboard); + keyboard_led_update(&device->keyboard->group->keyboard); + + // Add the key to the array + uint32_t *key = wl_array_add(&keys, sizeof(uint32_t)); + *key = event.keycode; + } + } + + // If there are any unique keys, emit the enter/leave event + if (keys.size > 0) { + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + wl_signal_emit_mutable(&device->keyboard->group->events.enter, &keys); + } else { + wl_signal_emit_mutable(&device->keyboard->group->events.leave, &keys); + } + } + + wl_array_release(&keys); +} + +static void remove_keyboard_group_device(struct keyboard_group_device *device) { + refresh_state(device, WL_KEYBOARD_KEY_STATE_RELEASED); + device->keyboard->group = NULL; + wl_list_remove(&device->link); + wl_list_remove(&device->key.link); + wl_list_remove(&device->modifiers.link); + wl_list_remove(&device->keymap.link); + wl_list_remove(&device->repeat_info.link); + wl_list_remove(&device->destroy.link); + free(device); +} + +static void handle_keyboard_destroy(struct wl_listener *listener, void *data) { + struct keyboard_group_device *device = + wl_container_of(listener, device, destroy); + remove_keyboard_group_device(device); +} + +bool wlr_keyboard_group_add_keyboard(struct wlr_keyboard_group *group, + struct wlr_keyboard *keyboard) { + if (keyboard->group) { + wlr_log(WLR_ERROR, "A wlr_keyboard can only belong to one group"); + return false; + } + + if (keyboard->impl == &impl) { + wlr_log(WLR_ERROR, "Cannot add a group's keyboard to a group"); + return false; + } + + if (!wlr_keyboard_keymaps_match(group->keyboard.keymap, keyboard->keymap)) { + wlr_log(WLR_ERROR, "Device keymap does not match keyboard group's"); + return false; + } + + struct keyboard_group_device *device = calloc(1, sizeof(*device)); + if (!device) { + wlr_log(WLR_ERROR, "Failed to allocate keyboard_group_device"); + return false; + } + + device->keyboard = keyboard; + keyboard->group = group; + wl_list_insert(&group->devices, &device->link); + + wl_signal_add(&keyboard->events.key, &device->key); + device->key.notify = handle_keyboard_key; + + wl_signal_add(&keyboard->events.modifiers, &device->modifiers); + device->modifiers.notify = handle_keyboard_modifiers; + + wl_signal_add(&keyboard->events.keymap, &device->keymap); + device->keymap.notify = handle_keyboard_keymap; + + wl_signal_add(&keyboard->events.repeat_info, &device->repeat_info); + device->repeat_info.notify = handle_keyboard_repeat_info; + + wl_signal_add(&keyboard->base.events.destroy, &device->destroy); + device->destroy.notify = handle_keyboard_destroy; + + struct wlr_keyboard *group_kb = &group->keyboard; + if (keyboard->modifiers.group != group_kb->modifiers.group) { + wlr_keyboard_notify_modifiers(keyboard, keyboard->modifiers.depressed, + keyboard->modifiers.latched, keyboard->modifiers.locked, + group_kb->modifiers.group); + } + if (keyboard->repeat_info.rate != group_kb->repeat_info.rate || + keyboard->repeat_info.delay != group_kb->repeat_info.delay) { + wlr_keyboard_set_repeat_info(keyboard, group_kb->repeat_info.rate, + group_kb->repeat_info.delay); + } + + refresh_state(device, WL_KEYBOARD_KEY_STATE_PRESSED); + return true; +} + +void wlr_keyboard_group_remove_keyboard(struct wlr_keyboard_group *group, + struct wlr_keyboard *keyboard) { + struct keyboard_group_device *device, *tmp; + wl_list_for_each_safe(device, tmp, &group->devices, link) { + if (device->keyboard == keyboard) { + remove_keyboard_group_device(device); + return; + } + } + wlr_log(WLR_ERROR, "keyboard not found in group"); +} + +void wlr_keyboard_group_destroy(struct wlr_keyboard_group *group) { + struct keyboard_group_device *device, *tmp; + wl_list_for_each_safe(device, tmp, &group->devices, link) { + wlr_keyboard_group_remove_keyboard(group, device->keyboard); + } + wlr_keyboard_finish(&group->keyboard); + wl_list_remove(&group->events.enter.listener_list); + wl_list_remove(&group->events.leave.listener_list); + free(group); +} diff --git a/types/wlr_keyboard_shortcuts_inhibit_v1.c b/types/wlr_keyboard_shortcuts_inhibit_v1.c new file mode 100644 index 0000000..0e43556 --- /dev/null +++ b/types/wlr_keyboard_shortcuts_inhibit_v1.c @@ -0,0 +1,227 @@ +#include +#include +#include +#include +#include "keyboard-shortcuts-inhibit-unstable-v1-protocol.h" + +static const struct zwp_keyboard_shortcuts_inhibit_manager_v1_interface + keyboard_shortcuts_inhibit_impl; + +static const struct zwp_keyboard_shortcuts_inhibitor_v1_interface + keyboard_shortcuts_inhibitor_impl; + +static struct wlr_keyboard_shortcuts_inhibit_manager_v1 * +wlr_keyboard_shortcuts_inhibit_manager_v1_from_resource( + struct wl_resource *manager_resource) { + assert(wl_resource_instance_of(manager_resource, + &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, + &keyboard_shortcuts_inhibit_impl)); + return wl_resource_get_user_data(manager_resource); +} + +static struct wlr_keyboard_shortcuts_inhibitor_v1 * +wlr_keyboard_shortcuts_inhibitor_v1_from_resource( + struct wl_resource *inhibitor_resource) { + assert(wl_resource_instance_of(inhibitor_resource, + &zwp_keyboard_shortcuts_inhibitor_v1_interface, + &keyboard_shortcuts_inhibitor_impl)); + return wl_resource_get_user_data(inhibitor_resource); +} + +static void keyboard_shortcuts_inhibitor_v1_destroy( + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor) { + if (!inhibitor) { + return; + } + + wl_signal_emit_mutable(&inhibitor->events.destroy, inhibitor); + + wl_resource_set_user_data(inhibitor->resource, NULL); + wl_list_remove(&inhibitor->link); + wl_list_remove(&inhibitor->surface_destroy.link); + wl_list_remove(&inhibitor->seat_destroy.link); + free(inhibitor); +} + +static void keyboard_shortcuts_inhibitor_v1_handle_resource_destroy( + struct wl_resource *inhibitor_resource) { + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = + wlr_keyboard_shortcuts_inhibitor_v1_from_resource( + inhibitor_resource); + keyboard_shortcuts_inhibitor_v1_destroy(inhibitor); +} + +static void keyboard_shortcuts_inhibitor_handle_surface_destroy( + struct wl_listener *listener, void *data) { + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = + wl_container_of(listener, inhibitor, surface_destroy); + + // be gracious and notify client that destruction of a referenced + // resource makes inhibitor moot + wlr_keyboard_shortcuts_inhibitor_v1_deactivate(inhibitor); + keyboard_shortcuts_inhibitor_v1_destroy(inhibitor); +} + +static void keyboard_shortcuts_inhibitor_handle_seat_destroy( + struct wl_listener *listener, void *data) { + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = + wl_container_of(listener, inhibitor, seat_destroy); + wlr_keyboard_shortcuts_inhibitor_v1_deactivate(inhibitor); + keyboard_shortcuts_inhibitor_v1_destroy(inhibitor); +} + +static void keyboard_shortcuts_inhibitor_v1_handle_destroy( + struct wl_client *client, + struct wl_resource *inhibitor_resource) { + wl_resource_destroy(inhibitor_resource); +} + +static const struct zwp_keyboard_shortcuts_inhibitor_v1_interface +keyboard_shortcuts_inhibitor_impl = { + .destroy = keyboard_shortcuts_inhibitor_v1_handle_destroy, +}; + +static void manager_handle_inhibit_shortcuts(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *surface_resource, + struct wl_resource *seat_resource) { + struct wlr_surface *surface = + wlr_surface_from_resource(surface_resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + struct wlr_keyboard_shortcuts_inhibit_manager_v1 *manager = + wlr_keyboard_shortcuts_inhibit_manager_v1_from_resource( + manager_resource); + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *inhibitor_resource = wl_resource_create(client, + &zwp_keyboard_shortcuts_inhibitor_v1_interface, version, id); + if (inhibitor_resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(inhibitor_resource, + &keyboard_shortcuts_inhibitor_impl, NULL, + keyboard_shortcuts_inhibitor_v1_handle_resource_destroy); + if (seat_client == NULL) { + return; + } + + struct wlr_seat *seat = seat_client->seat; + struct wlr_keyboard_shortcuts_inhibitor_v1 *existing_inhibitor; + wl_list_for_each(existing_inhibitor, &manager->inhibitors, link) { + if (existing_inhibitor->surface != surface || + existing_inhibitor->seat != seat) { + continue; + } + + wl_resource_post_error(manager_resource, + ZWP_KEYBOARD_SHORTCUTS_INHIBIT_MANAGER_V1_ERROR_ALREADY_INHIBITED, + "this surface already has keyboard shortcuts " + "inhibited on this seat"); + return; + } + + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = calloc(1, sizeof(*inhibitor)); + if (!inhibitor) { + wl_client_post_no_memory(client); + return; + } + + inhibitor->resource = inhibitor_resource; + inhibitor->surface = surface; + inhibitor->seat = seat; + inhibitor->active = false; + wl_signal_init(&inhibitor->events.destroy); + + inhibitor->surface_destroy.notify = + keyboard_shortcuts_inhibitor_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &inhibitor->surface_destroy); + + inhibitor->seat_destroy.notify = + keyboard_shortcuts_inhibitor_handle_seat_destroy; + wl_signal_add(&seat->events.destroy, &inhibitor->seat_destroy); + + wl_resource_set_user_data(inhibitor_resource, inhibitor); + wl_list_insert(&manager->inhibitors, &inhibitor->link); + wl_signal_emit_mutable(&manager->events.new_inhibitor, inhibitor); +} + +static void manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwp_keyboard_shortcuts_inhibit_manager_v1_interface +keyboard_shortcuts_inhibit_impl = { + .destroy = manager_handle_destroy, + .inhibit_shortcuts = manager_handle_inhibit_shortcuts, +}; + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_keyboard_shortcuts_inhibit_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +static void keyboard_shortcuts_inhibit_bind(struct wl_client *wl_client, + void *data, uint32_t version, uint32_t id) { + struct wlr_keyboard_shortcuts_inhibit_manager_v1 *manager = data; + + struct wl_resource *manager_resource = wl_resource_create(wl_client, + &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, + version, id); + if (!manager_resource) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_resource_set_implementation(manager_resource, + &keyboard_shortcuts_inhibit_impl, manager, NULL); +} + +struct wlr_keyboard_shortcuts_inhibit_manager_v1 * +wlr_keyboard_shortcuts_inhibit_v1_create(struct wl_display *display) { + struct wlr_keyboard_shortcuts_inhibit_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + return NULL; + } + + wl_list_init(&manager->inhibitors); + wl_signal_init(&manager->events.new_inhibitor); + wl_signal_init(&manager->events.destroy); + + manager->global = wl_global_create(display, + &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1, + manager, keyboard_shortcuts_inhibit_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +void wlr_keyboard_shortcuts_inhibitor_v1_activate( + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor) { + if (!inhibitor->active) { + zwp_keyboard_shortcuts_inhibitor_v1_send_active( + inhibitor->resource); + inhibitor->active = true; + } +} + +void wlr_keyboard_shortcuts_inhibitor_v1_deactivate( + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor) { + if (inhibitor->active) { + zwp_keyboard_shortcuts_inhibitor_v1_send_inactive( + inhibitor->resource); + inhibitor->active = false; + } +} diff --git a/types/wlr_layer_shell_v1.c b/types/wlr_layer_shell_v1.c new file mode 100644 index 0000000..a59f110 --- /dev/null +++ b/types/wlr_layer_shell_v1.c @@ -0,0 +1,623 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wlr-layer-shell-unstable-v1-protocol.h" + +// Note: zwlr_layer_surface_v1 becomes inert on wlr_layer_surface_v1_destroy() + +#define LAYER_SHELL_VERSION 4 + +static void resource_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_layer_shell_v1_interface layer_shell_implementation; +static const struct zwlr_layer_surface_v1_interface layer_surface_implementation; + +static void layer_surface_configure_destroy( + struct wlr_layer_surface_v1_configure *configure) { + if (configure == NULL) { + return; + } + wl_list_remove(&configure->link); + free(configure); +} + +static void layer_surface_reset(struct wlr_layer_surface_v1 *surface) { + surface->configured = false; + surface->initialized = false; + + struct wlr_xdg_popup *popup, *popup_tmp; + wl_list_for_each_safe(popup, popup_tmp, &surface->popups, link) { + wlr_xdg_popup_destroy(popup); + } + + struct wlr_layer_surface_v1_configure *configure, *tmp; + wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) { + layer_surface_configure_destroy(configure); + } +} + +static void layer_surface_destroy(struct wlr_layer_surface_v1 *surface) { + wlr_surface_unmap(surface->surface); + layer_surface_reset(surface); + + wl_signal_emit_mutable(&surface->events.destroy, surface); + wlr_surface_synced_finish(&surface->synced); + wl_resource_set_user_data(surface->resource, NULL); + free(surface->namespace); + free(surface); +} + +static struct wlr_layer_shell_v1 *layer_shell_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwlr_layer_shell_v1_interface, + &layer_shell_implementation)); + return wl_resource_get_user_data(resource); +} + +struct wlr_layer_surface_v1 *wlr_layer_surface_v1_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwlr_layer_surface_v1_interface, + &layer_surface_implementation)); + return wl_resource_get_user_data(resource); +} + +static const struct wlr_surface_role layer_surface_role; + +struct wlr_layer_surface_v1 *wlr_layer_surface_v1_try_from_wlr_surface( + struct wlr_surface *surface) { + if (surface->role != &layer_surface_role || surface->role_resource == NULL) { + return NULL; + } + return wlr_layer_surface_v1_from_resource(surface->role_resource); +} + +static void layer_surface_handle_ack_configure(struct wl_client *client, + struct wl_resource *resource, uint32_t serial) { + struct wlr_layer_surface_v1 *surface = + wlr_layer_surface_v1_from_resource(resource); + + if (!surface) { + return; + } + + // First find the ack'ed configure + bool found = false; + struct wlr_layer_surface_v1_configure *configure, *tmp; + wl_list_for_each(configure, &surface->configure_list, link) { + if (configure->serial == serial) { + found = true; + break; + } + } + if (!found) { + wl_resource_post_error(resource, + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SURFACE_STATE, + "wrong configure serial: %" PRIu32, serial); + return; + } + // Then remove old configures from the list + wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) { + if (configure->serial == serial) { + break; + } + layer_surface_configure_destroy(configure); + } + + surface->pending.configure_serial = configure->serial; + surface->pending.actual_width = configure->width; + surface->pending.actual_height = configure->height; + + surface->configured = true; + + layer_surface_configure_destroy(configure); +} + +static void layer_surface_handle_set_size(struct wl_client *client, + struct wl_resource *resource, uint32_t width, uint32_t height) { + struct wlr_layer_surface_v1 *surface = + wlr_layer_surface_v1_from_resource(resource); + + if (!surface) { + return; + } + + if (width > INT32_MAX || height > INT32_MAX) { + wl_client_post_implementation_error(client, + "zwlr_layer_surface_v1.set_size: width and height can't be greater than INT32_MAX"); + return; + } + + if (surface->pending.desired_width == width + && surface->pending.desired_height == height) { + return; + } + + surface->pending.committed |= WLR_LAYER_SURFACE_V1_STATE_DESIRED_SIZE; + surface->pending.desired_width = width; + surface->pending.desired_height = height; +} + +static void layer_surface_handle_set_anchor(struct wl_client *client, + struct wl_resource *resource, uint32_t anchor) { + const uint32_t max_anchor = + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + if (anchor > max_anchor) { + wl_resource_post_error(resource, + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_ANCHOR, + "invalid anchor %" PRIu32, anchor); + } + struct wlr_layer_surface_v1 *surface = + wlr_layer_surface_v1_from_resource(resource); + + if (!surface) { + return; + } + + if (surface->pending.anchor == anchor) { + return; + } + + surface->pending.committed |= WLR_LAYER_SURFACE_V1_STATE_ANCHOR; + surface->pending.anchor = anchor; +} + +static void layer_surface_handle_set_exclusive_zone(struct wl_client *client, + struct wl_resource *resource, int32_t zone) { + struct wlr_layer_surface_v1 *surface = wlr_layer_surface_v1_from_resource(resource); + + if (!surface) { + return; + } + + if (surface->pending.exclusive_zone == zone) { + return; + } + + surface->pending.committed |= WLR_LAYER_SURFACE_V1_STATE_EXCLUSIVE_ZONE; + surface->pending.exclusive_zone = zone; +} + +static void layer_surface_handle_set_margin( + struct wl_client *client, struct wl_resource *resource, + int32_t top, int32_t right, int32_t bottom, int32_t left) { + struct wlr_layer_surface_v1 *surface = + wlr_layer_surface_v1_from_resource(resource); + + if (!surface) { + return; + } + + if (surface->pending.margin.top == top + && surface->pending.margin.right == right + && surface->pending.margin.bottom == bottom + && surface->pending.margin.left == left) { + return; + } + + surface->pending.committed |= WLR_LAYER_SURFACE_V1_STATE_MARGIN; + surface->pending.margin.top = top; + surface->pending.margin.right = right; + surface->pending.margin.bottom = bottom; + surface->pending.margin.left = left; +} + +static void layer_surface_handle_set_keyboard_interactivity( + struct wl_client *client, struct wl_resource *resource, + uint32_t interactive) { + struct wlr_layer_surface_v1 *surface = wlr_layer_surface_v1_from_resource(resource); + + if (!surface) { + return; + } + + surface->pending.committed |= WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY; + if (wl_resource_get_version(resource) < ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND_SINCE_VERSION) { + surface->pending.keyboard_interactive = !!interactive; + } else { + if (interactive > ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) { + wl_resource_post_error(resource, + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_KEYBOARD_INTERACTIVITY, + "wrong keyboard interactivity value: %" PRIu32, interactive); + } else { + surface->pending.keyboard_interactive = interactive; + } + } +} + +static void layer_surface_handle_get_popup(struct wl_client *client, + struct wl_resource *layer_resource, + struct wl_resource *popup_resource) { + struct wlr_layer_surface_v1 *parent = + wlr_layer_surface_v1_from_resource(layer_resource); + struct wlr_xdg_popup *popup = + wlr_xdg_popup_from_resource(popup_resource); + + if (!parent) { + return; + } + if (popup->parent != NULL) { + wl_resource_post_error(layer_resource, -1, "xdg_popup already has a parent"); + return; + } + popup->parent = parent->surface; + wl_list_insert(&parent->popups, &popup->link); + wl_signal_emit_mutable(&parent->events.new_popup, popup); +} + +static void layer_surface_set_layer(struct wl_client *client, + struct wl_resource *surface_resource, uint32_t layer) { + struct wlr_layer_surface_v1 *surface = + wlr_layer_surface_v1_from_resource(surface_resource); + if (!surface) { + return; + } + if (layer > ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) { + wl_resource_post_error(surface->resource, + ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER, + "Invalid layer %" PRIu32, layer); + return; + } + + if (surface->pending.layer == layer) { + return; + } + + surface->pending.committed |= WLR_LAYER_SURFACE_V1_STATE_LAYER; + surface->pending.layer = layer; +} + +static const struct zwlr_layer_surface_v1_interface layer_surface_implementation = { + .destroy = resource_handle_destroy, + .ack_configure = layer_surface_handle_ack_configure, + .set_size = layer_surface_handle_set_size, + .set_anchor = layer_surface_handle_set_anchor, + .set_exclusive_zone = layer_surface_handle_set_exclusive_zone, + .set_margin = layer_surface_handle_set_margin, + .set_keyboard_interactivity = layer_surface_handle_set_keyboard_interactivity, + .get_popup = layer_surface_handle_get_popup, + .set_layer = layer_surface_set_layer, +}; + +uint32_t wlr_layer_surface_v1_configure(struct wlr_layer_surface_v1 *surface, + uint32_t width, uint32_t height) { + if (!surface->initialized) { + wlr_log(WLR_ERROR, "A configure is sent to an uninitialized wlr_layer_surface_v1 %p", + surface); + } + + struct wl_display *display = + wl_client_get_display(wl_resource_get_client(surface->resource)); + struct wlr_layer_surface_v1_configure *configure = calloc(1, sizeof(*configure)); + if (configure == NULL) { + wl_client_post_no_memory(wl_resource_get_client(surface->resource)); + return surface->pending.configure_serial; + } + wl_list_insert(surface->configure_list.prev, &configure->link); + configure->width = width; + configure->height = height; + configure->serial = wl_display_next_serial(display); + zwlr_layer_surface_v1_send_configure(surface->resource, + configure->serial, configure->width, + configure->height); + return configure->serial; +} + +void wlr_layer_surface_v1_destroy(struct wlr_layer_surface_v1 *surface) { + if (surface == NULL) { + return; + } + zwlr_layer_surface_v1_send_closed(surface->resource); + layer_surface_destroy(surface); +} + +static void layer_surface_role_client_commit(struct wlr_surface *wlr_surface) { + struct wlr_layer_surface_v1 *surface = + wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface); + if (surface == NULL) { + return; + } + + if (wlr_surface_state_has_buffer(&wlr_surface->pending) && !surface->configured) { + wlr_surface_reject_pending(wlr_surface, surface->resource, + ZWLR_LAYER_SHELL_V1_ERROR_ALREADY_CONSTRUCTED, + "layer_surface has never been configured"); + return; + } + + const uint32_t horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + if (surface->pending.desired_width == 0 && + (surface->pending.anchor & horiz) != horiz) { + wlr_surface_reject_pending(wlr_surface, surface->resource, + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE, + "width 0 requested without setting left and right anchors"); + return; + } + + const uint32_t vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + if (surface->pending.desired_height == 0 && + (surface->pending.anchor & vert) != vert) { + wlr_surface_reject_pending(wlr_surface, surface->resource, + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE, + "height 0 requested without setting top and bottom anchors"); + return; + } +} + +static void layer_surface_role_commit(struct wlr_surface *wlr_surface) { + struct wlr_layer_surface_v1 *surface = + wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface); + if (surface == NULL) { + return; + } + + if (surface->surface->unmap_commit) { + layer_surface_reset(surface); + + assert(!surface->initialized); + surface->initial_commit = false; + } else { + surface->initial_commit = !surface->initialized; + surface->initialized = true; + } + + if (wlr_surface_has_buffer(wlr_surface)) { + wlr_surface_map(wlr_surface); + } +} + +static void layer_surface_role_destroy(struct wlr_surface *wlr_surface) { + struct wlr_layer_surface_v1 *surface = + wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface); + if (surface == NULL) { + return; + } + + layer_surface_destroy(surface); +} + +static const struct wlr_surface_role layer_surface_role = { + .name = "zwlr_layer_surface_v1", + .client_commit = layer_surface_role_client_commit, + .commit = layer_surface_role_commit, + .destroy = layer_surface_role_destroy, +}; + +static void surface_synced_move_state(void *_dst, void *_src) { + struct wlr_layer_surface_v1_state *dst = _dst, *src = _src; + *dst = *src; + src->committed = 0; +} + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_layer_surface_v1_state), + .move_state = surface_synced_move_state, +}; + +static void layer_shell_handle_get_layer_surface(struct wl_client *wl_client, + struct wl_resource *client_resource, uint32_t id, + struct wl_resource *surface_resource, + struct wl_resource *output_resource, + uint32_t layer, const char *namespace) { + struct wlr_layer_shell_v1 *shell = + layer_shell_from_resource(client_resource); + struct wlr_surface *wlr_surface = + wlr_surface_from_resource(surface_resource); + + if (layer > ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) { + wl_resource_post_error(client_resource, + ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER, + "Invalid layer %" PRIu32, layer); + return; + } + + struct wlr_layer_surface_v1 *surface = calloc(1, sizeof(*surface)); + if (surface == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + if (!wlr_surface_set_role(wlr_surface, &layer_surface_role, + client_resource, ZWLR_LAYER_SHELL_V1_ERROR_ROLE)) { + free(surface); + return; + } + + surface->shell = shell; + surface->surface = wlr_surface; + if (output_resource) { + surface->output = wlr_output_from_resource(output_resource); + } + surface->namespace = strdup(namespace); + if (surface->namespace == NULL) { + goto error_surface; + } + + if (!wlr_surface_synced_init(&surface->synced, wlr_surface, + &surface_synced_impl, &surface->pending, &surface->current)) { + goto error_namespace; + } + + surface->current.layer = surface->pending.layer = layer; + + struct wlr_surface_state *cached; + wl_list_for_each(cached, &wlr_surface->cached, cached_state_link) { + struct wlr_layer_surface_v1_state *state = + wlr_surface_synced_get_state(&surface->synced, cached); + state->layer = layer; + } + + surface->resource = wl_resource_create(wl_client, &zwlr_layer_surface_v1_interface, + wl_resource_get_version(client_resource), id); + if (surface->resource == NULL) { + goto error_synced; + } + + wl_list_init(&surface->configure_list); + wl_list_init(&surface->popups); + + wl_signal_init(&surface->events.destroy); + wl_signal_init(&surface->events.new_popup); + + wlr_log(WLR_DEBUG, "new layer_surface %p (res %p)", + surface, surface->resource); + wl_resource_set_implementation(surface->resource, + &layer_surface_implementation, surface, NULL); + + wlr_surface_set_role_object(wlr_surface, surface->resource); + + wl_signal_emit_mutable(&surface->shell->events.new_surface, surface); + + return; + +error_synced: + wlr_surface_synced_finish(&surface->synced); +error_namespace: + free(surface->namespace); +error_surface: + free(surface); + wl_client_post_no_memory(wl_client); +} + +static const struct zwlr_layer_shell_v1_interface layer_shell_implementation = { + .get_layer_surface = layer_shell_handle_get_layer_surface, + .destroy = resource_handle_destroy, +}; + +static void layer_shell_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_layer_shell_v1 *layer_shell = data; + + struct wl_resource *resource = wl_resource_create( + wl_client, &zwlr_layer_shell_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, + &layer_shell_implementation, layer_shell, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_layer_shell_v1 *layer_shell = + wl_container_of(listener, layer_shell, display_destroy); + wl_signal_emit_mutable(&layer_shell->events.destroy, layer_shell); + wl_list_remove(&layer_shell->display_destroy.link); + wl_global_destroy(layer_shell->global); + free(layer_shell); +} + +struct wlr_layer_shell_v1 *wlr_layer_shell_v1_create(struct wl_display *display, + uint32_t version) { + assert(version <= LAYER_SHELL_VERSION); + + struct wlr_layer_shell_v1 *layer_shell = calloc(1, sizeof(*layer_shell)); + if (!layer_shell) { + return NULL; + } + + struct wl_global *global = wl_global_create(display, + &zwlr_layer_shell_v1_interface, version, layer_shell, layer_shell_bind); + if (!global) { + free(layer_shell); + return NULL; + } + layer_shell->global = global; + + wl_signal_init(&layer_shell->events.new_surface); + wl_signal_init(&layer_shell->events.destroy); + + layer_shell->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &layer_shell->display_destroy); + + return layer_shell; +} + +struct layer_surface_iterator_data { + wlr_surface_iterator_func_t user_iterator; + void *user_data; + int x, y; +}; + +static void layer_surface_iterator(struct wlr_surface *surface, + int sx, int sy, void *data) { + struct layer_surface_iterator_data *iter_data = data; + iter_data->user_iterator(surface, iter_data->x + sx, iter_data->y + sy, + iter_data->user_data); +} + +void wlr_layer_surface_v1_for_each_surface(struct wlr_layer_surface_v1 *surface, + wlr_surface_iterator_func_t iterator, void *user_data) { + wlr_surface_for_each_surface(surface->surface, iterator, user_data); + wlr_layer_surface_v1_for_each_popup_surface(surface, iterator, user_data); +} + +void wlr_layer_surface_v1_for_each_popup_surface(struct wlr_layer_surface_v1 *surface, + wlr_surface_iterator_func_t iterator, void *user_data) { + struct wlr_xdg_popup *popup; + wl_list_for_each(popup, &surface->popups, link) { + if (!popup->base->surface->mapped) { + continue; + } + + double popup_sx, popup_sy; + popup_sx = popup->current.geometry.x - popup->base->current.geometry.x; + popup_sy = popup->current.geometry.y - popup->base->current.geometry.y; + + struct layer_surface_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .x = popup_sx, .y = popup_sy, + }; + + wlr_xdg_surface_for_each_surface(popup->base, + layer_surface_iterator, &data); + } +} + +struct wlr_surface *wlr_layer_surface_v1_surface_at( + struct wlr_layer_surface_v1 *surface, double sx, double sy, + double *sub_x, double *sub_y) { + struct wlr_surface *sub = wlr_layer_surface_v1_popup_surface_at(surface, + sx, sy, sub_x, sub_y); + if (sub != NULL) { + return sub; + } + return wlr_surface_surface_at(surface->surface, sx, sy, sub_x, sub_y); +} + +struct wlr_surface *wlr_layer_surface_v1_popup_surface_at( + struct wlr_layer_surface_v1 *surface, double sx, double sy, + double *sub_x, double *sub_y) { + struct wlr_xdg_popup *popup; + wl_list_for_each(popup, &surface->popups, link) { + if (!popup->base->surface->mapped) { + continue; + } + + double popup_sx, popup_sy; + popup_sx = popup->current.geometry.x - popup->base->current.geometry.x; + popup_sy = popup->current.geometry.y - popup->base->current.geometry.y; + + struct wlr_surface *sub = wlr_xdg_surface_surface_at( + popup->base, sx - popup_sx, sy - popup_sy, sub_x, sub_y); + if (sub != NULL) { + return sub; + } + } + + return NULL; +} diff --git a/types/wlr_linux_dmabuf_v1.c b/types/wlr_linux_dmabuf_v1.c new file mode 100644 index 0000000..13e8276 --- /dev/null +++ b/types/wlr_linux_dmabuf_v1.c @@ -0,0 +1,1173 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "linux-dmabuf-v1-protocol.h" +#include "render/drm_format_set.h" +#include "util/shm.h" + +#if WLR_HAS_DRM_BACKEND +#include +#endif + +#define LINUX_DMABUF_VERSION 5 + +struct wlr_linux_buffer_params_v1 { + struct wl_resource *resource; + struct wlr_linux_dmabuf_v1 *linux_dmabuf; + struct wlr_dmabuf_attributes attributes; + bool has_modifier; +}; + +struct wlr_linux_dmabuf_feedback_v1_compiled_tranche { + dev_t target_device; + uint32_t flags; // bitfield of enum zwp_linux_dmabuf_feedback_v1_tranche_flags + struct wl_array indices; // uint16_t +}; + +struct wlr_linux_dmabuf_feedback_v1_compiled { + dev_t main_device; + int table_fd; + size_t table_size; + + size_t tranches_len; + struct wlr_linux_dmabuf_feedback_v1_compiled_tranche tranches[]; +}; + +struct wlr_linux_dmabuf_feedback_v1_table_entry { + uint32_t format; + uint32_t pad; // unused + uint64_t modifier; +}; + +// TODO: switch back to static_assert once this fix propagates in stable trees: +// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=255290 +_Static_assert(sizeof(struct wlr_linux_dmabuf_feedback_v1_table_entry) == 16, + "Expected wlr_linux_dmabuf_feedback_v1_table_entry to be tightly packed"); + +struct wlr_linux_dmabuf_v1_surface { + struct wlr_surface *surface; + struct wlr_linux_dmabuf_v1 *linux_dmabuf; + struct wl_list link; // wlr_linux_dmabuf_v1.surfaces + + struct wlr_addon addon; + struct wlr_linux_dmabuf_feedback_v1_compiled *feedback; + + struct wl_list feedback_resources; // wl_resource_get_link +}; + +static void buffer_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_buffer_interface wl_buffer_impl = { + .destroy = buffer_handle_destroy, +}; + +static bool buffer_resource_is_instance(struct wl_resource *resource) { + return wl_resource_instance_of(resource, &wl_buffer_interface, + &wl_buffer_impl); +} + +struct wlr_dmabuf_v1_buffer *wlr_dmabuf_v1_buffer_try_from_buffer_resource( + struct wl_resource *resource) { + if (!buffer_resource_is_instance(resource) || + wl_resource_get_user_data(resource) == NULL) { + return NULL; + } + return wl_resource_get_user_data(resource); +} + +static const struct wlr_buffer_impl buffer_impl; + +static struct wlr_dmabuf_v1_buffer *dmabuf_v1_buffer_from_buffer( + struct wlr_buffer *wlr_buffer) { + assert(wlr_buffer->impl == &buffer_impl); + struct wlr_dmabuf_v1_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + return buffer; +} + +static void buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct wlr_dmabuf_v1_buffer *buffer = + dmabuf_v1_buffer_from_buffer(wlr_buffer); + if (buffer->resource != NULL) { + wl_resource_set_user_data(buffer->resource, NULL); + } + wlr_dmabuf_attributes_finish(&buffer->attributes); + wl_list_remove(&buffer->release.link); + free(buffer); +} + +static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buffer, + struct wlr_dmabuf_attributes *attribs) { + struct wlr_dmabuf_v1_buffer *buffer = + dmabuf_v1_buffer_from_buffer(wlr_buffer); + *attribs = buffer->attributes; + return true; +} + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = buffer_destroy, + .get_dmabuf = buffer_get_dmabuf, +}; + +static void buffer_handle_release(struct wl_listener *listener, void *data) { + struct wlr_dmabuf_v1_buffer *buffer = + wl_container_of(listener, buffer, release); + if (buffer->resource != NULL) { + wl_buffer_send_release(buffer->resource); + } +} + +static const struct zwp_linux_buffer_params_v1_interface buffer_params_impl; + +static struct wlr_linux_buffer_params_v1 *params_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_linux_buffer_params_v1_interface, &buffer_params_impl)); + return wl_resource_get_user_data(resource); +} + +static void params_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void params_add(struct wl_client *client, + struct wl_resource *params_resource, int32_t fd, + uint32_t plane_idx, uint32_t offset, uint32_t stride, + uint32_t modifier_hi, uint32_t modifier_lo) { + struct wlr_linux_buffer_params_v1 *params = + params_from_resource(params_resource); + if (!params) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, + "params was already used to create a wl_buffer"); + close(fd); + return; + } + + if (plane_idx >= WLR_DMABUF_MAX_PLANES) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX, + "plane index %u > %u", plane_idx, WLR_DMABUF_MAX_PLANES); + close(fd); + return; + } + + if (params->attributes.fd[plane_idx] != -1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET, + "a dmabuf with FD %d has already been added for plane %u", + params->attributes.fd[plane_idx], plane_idx); + close(fd); + return; + } + + uint64_t modifier = ((uint64_t)modifier_hi << 32) | modifier_lo; + if (params->has_modifier && modifier != params->attributes.modifier) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, + "sent modifier %" PRIu64 " for plane %u, expected" + " modifier %" PRIu64 " like other planes", + modifier, plane_idx, params->attributes.modifier); + close(fd); + return; + } + + params->attributes.modifier = modifier; + params->has_modifier = true; + + params->attributes.fd[plane_idx] = fd; + params->attributes.offset[plane_idx] = offset; + params->attributes.stride[plane_idx] = stride; + params->attributes.n_planes++; +} + +static void buffer_handle_resource_destroy(struct wl_resource *buffer_resource) { + struct wlr_dmabuf_v1_buffer *buffer = + wlr_dmabuf_v1_buffer_try_from_buffer_resource(buffer_resource); + assert(buffer != NULL); + buffer->resource = NULL; + wlr_buffer_drop(&buffer->base); +} + +static bool check_import_dmabuf(struct wlr_dmabuf_attributes *attribs, void *data) { + struct wlr_linux_dmabuf_v1 *linux_dmabuf = data; + + if (linux_dmabuf->main_device_fd < 0) { + return true; + } + + // TODO: check number of planes + for (int i = 0; i < attribs->n_planes; i++) { + uint32_t handle = 0; + if (drmPrimeFDToHandle(linux_dmabuf->main_device_fd, attribs->fd[i], &handle) != 0) { + wlr_log_errno(WLR_DEBUG, "Failed to import DMA-BUF FD"); + return false; + } + if (drmCloseBufferHandle(linux_dmabuf->main_device_fd, handle) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to close buffer handle"); + return false; + } + } + return true; +} + +static void params_create_common(struct wl_resource *params_resource, + uint32_t buffer_id, int32_t width, int32_t height, uint32_t format, + uint32_t flags) { + struct wlr_linux_buffer_params_v1 *params = + params_from_resource(params_resource); + if (!params) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, + "params was already used to create a wl_buffer"); + return; + } + + struct wlr_dmabuf_attributes attribs = params->attributes; + struct wlr_linux_dmabuf_v1 *linux_dmabuf = params->linux_dmabuf; + + // Make the params resource inert + wl_resource_set_user_data(params_resource, NULL); + free(params); + + if (!attribs.n_planes) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "no dmabuf has been added to the params"); + goto err_out; + } + + if (attribs.fd[0] == -1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "no dmabuf has been added for plane 0"); + goto err_out; + } + + if ((attribs.fd[3] >= 0 || attribs.fd[2] >= 0) && + (attribs.fd[2] == -1 || attribs.fd[1] == -1)) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "gap in dmabuf planes"); + goto err_out; + } + + /* reject unknown flags */ + uint32_t all_flags = ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT | + ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_INTERLACED | + ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_BOTTOM_FIRST; + if (flags & ~all_flags) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, + "Unknown dmabuf flags %"PRIu32, flags); + goto err_out; + } + + if (flags != 0) { + wlr_log(WLR_ERROR, "dmabuf flags aren't supported"); + goto err_failed; + } + + attribs.width = width; + attribs.height = height; + attribs.format = format; + + if (width < 1 || height < 1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS, + "invalid width %d or height %d", width, height); + goto err_out; + } + + for (int i = 0; i < attribs.n_planes; i++) { + if ((uint64_t)attribs.offset[i] + + attribs.stride[i] > UINT32_MAX) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "size overflow for plane %d", i); + goto err_out; + } + + if ((uint64_t)attribs.offset[i] + + (uint64_t)attribs.stride[i] * height > UINT32_MAX) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "size overflow for plane %d", i); + goto err_out; + } + + off_t size = lseek(attribs.fd[i], 0, SEEK_END); + if (size == -1) { + // Skip checks if kernel does no support seek on buffer + continue; + } + if (attribs.offset[i] > size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid offset %" PRIu32 " for plane %d", + attribs.offset[i], i); + goto err_out; + } + + if (attribs.offset[i] + attribs.stride[i] > size || + attribs.stride[i] == 0) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid stride %" PRIu32 " for plane %d", + attribs.stride[i], i); + goto err_out; + } + + // planes > 0 might be subsampled according to fourcc format + if (i == 0 && attribs.offset[i] + + attribs.stride[i] * height > size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid buffer stride or height for plane %d", i); + goto err_out; + } + } + + /* Check if dmabuf is usable */ + if (!linux_dmabuf->check_dmabuf_callback(&attribs, + linux_dmabuf->check_dmabuf_callback_data)) { + goto err_failed; + } + + struct wlr_dmabuf_v1_buffer *buffer = calloc(1, sizeof(*buffer)); + if (!buffer) { + wl_resource_post_no_memory(params_resource); + goto err_failed; + } + wlr_buffer_init(&buffer->base, &buffer_impl, attribs.width, attribs.height); + + struct wl_client *client = wl_resource_get_client(params_resource); + buffer->resource = wl_resource_create(client, &wl_buffer_interface, + 1, buffer_id); + if (!buffer->resource) { + wl_resource_post_no_memory(params_resource); + free(buffer); + goto err_failed; + } + wl_resource_set_implementation(buffer->resource, + &wl_buffer_impl, buffer, buffer_handle_resource_destroy); + + buffer->attributes = attribs; + + buffer->release.notify = buffer_handle_release; + wl_signal_add(&buffer->base.events.release, &buffer->release); + + /* send 'created' event when the request is not for an immediate + * import, that is buffer_id is zero */ + if (buffer_id == 0) { + zwp_linux_buffer_params_v1_send_created(params_resource, + buffer->resource); + } + + return; + +err_failed: + if (buffer_id == 0) { + zwp_linux_buffer_params_v1_send_failed(params_resource); + } else { + /* since the behavior is left implementation defined by the + * protocol in case of create_immed failure due to an unknown cause, + * we choose to treat it as a fatal error and immediately kill the + * client instead of creating an invalid handle and waiting for it + * to be used. + */ + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER, + "importing the supplied dmabufs failed"); + } +err_out: + wlr_dmabuf_attributes_finish(&attribs); +} + +static void params_create(struct wl_client *client, + struct wl_resource *params_resource, + int32_t width, int32_t height, uint32_t format, uint32_t flags) { + params_create_common(params_resource, 0, width, height, format, + flags); +} + +static void params_create_immed(struct wl_client *client, + struct wl_resource *params_resource, uint32_t buffer_id, + int32_t width, int32_t height, uint32_t format, uint32_t flags) { + params_create_common(params_resource, buffer_id, width, height, + format, flags); +} + +static const struct zwp_linux_buffer_params_v1_interface buffer_params_impl = { + .destroy = params_destroy, + .add = params_add, + .create = params_create, + .create_immed = params_create_immed, +}; + +static void params_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_linux_buffer_params_v1 *params = params_from_resource(resource); + if (!params) { + return; + } + wlr_dmabuf_attributes_finish(¶ms->attributes); + free(params); +} + +static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_impl; + +static struct wlr_linux_dmabuf_v1 *linux_dmabuf_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_linux_dmabuf_v1_interface, + &linux_dmabuf_impl)); + + struct wlr_linux_dmabuf_v1 *dmabuf = wl_resource_get_user_data(resource); + assert(dmabuf); + return dmabuf; +} + +static void linux_dmabuf_create_params(struct wl_client *client, + struct wl_resource *linux_dmabuf_resource, + uint32_t params_id) { + struct wlr_linux_dmabuf_v1 *linux_dmabuf = + linux_dmabuf_from_resource(linux_dmabuf_resource); + + struct wlr_linux_buffer_params_v1 *params = calloc(1, sizeof(*params)); + if (!params) { + wl_resource_post_no_memory(linux_dmabuf_resource); + return; + } + + for (int i = 0; i < WLR_DMABUF_MAX_PLANES; i++) { + params->attributes.fd[i] = -1; + } + + params->linux_dmabuf = linux_dmabuf; + + uint32_t version = wl_resource_get_version(linux_dmabuf_resource); + params->resource = wl_resource_create(client, + &zwp_linux_buffer_params_v1_interface, version, params_id); + if (!params->resource) { + free(params); + wl_resource_post_no_memory(linux_dmabuf_resource); + return; + } + wl_resource_set_implementation(params->resource, + &buffer_params_impl, params, params_handle_resource_destroy); +} + +static void linux_dmabuf_feedback_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_linux_dmabuf_feedback_v1_interface + linux_dmabuf_feedback_impl = { + .destroy = linux_dmabuf_feedback_destroy, +}; + +static ssize_t get_drm_format_set_index(const struct wlr_drm_format_set *set, + uint32_t format, uint64_t modifier) { + bool format_found = false; + const struct wlr_drm_format *fmt; + size_t idx = 0; + for (size_t i = 0; i < set->len; i++) { + fmt = &set->formats[i]; + if (fmt->format == format) { + format_found = true; + break; + } + idx += fmt->len; + } + if (!format_found) { + return -1; + } + + for (size_t i = 0; i < fmt->len; i++) { + if (fmt->modifiers[i] == modifier) { + return idx; + } + idx++; + } + return -1; +} + +static struct wlr_linux_dmabuf_feedback_v1_compiled *feedback_compile( + const struct wlr_linux_dmabuf_feedback_v1 *feedback) { + const struct wlr_linux_dmabuf_feedback_v1_tranche *tranches = feedback->tranches.data; + size_t tranches_len = feedback->tranches.size / sizeof(struct wlr_linux_dmabuf_feedback_v1_tranche); + assert(tranches_len > 0); + + // Make one big format set that contains all formats across all tranches so that we + // can build an index + struct wlr_drm_format_set all_formats = {0}; + for (size_t i = 0; i < tranches_len; i++) { + const struct wlr_linux_dmabuf_feedback_v1_tranche *tranche = &tranches[i]; + if (!wlr_drm_format_set_union(&all_formats, &all_formats, &tranche->formats)) { + wlr_log(WLR_ERROR, "Failed to union scanout formats into one tranche"); + goto err_all_formats; + } + } + + size_t table_len = 0; + for (size_t i = 0; i < all_formats.len; i++) { + const struct wlr_drm_format *fmt = &all_formats.formats[i]; + table_len += fmt->len; + } + assert(table_len > 0); + + size_t table_size = + table_len * sizeof(struct wlr_linux_dmabuf_feedback_v1_table_entry); + int rw_fd, ro_fd; + if (!allocate_shm_file_pair(table_size, &rw_fd, &ro_fd)) { + wlr_log(WLR_ERROR, "Failed to allocate shm file for format table"); + return NULL; + } + + struct wlr_linux_dmabuf_feedback_v1_table_entry *table = + mmap(NULL, table_size, PROT_READ | PROT_WRITE, MAP_SHARED, rw_fd, 0); + if (table == MAP_FAILED) { + wlr_log_errno(WLR_ERROR, "mmap failed"); + close(rw_fd); + close(ro_fd); + goto err_all_formats; + } + + close(rw_fd); + + size_t n = 0; + for (size_t i = 0; i < all_formats.len; i++) { + const struct wlr_drm_format *fmt = &all_formats.formats[i]; + + for (size_t k = 0; k < fmt->len; k++) { + table[n] = (struct wlr_linux_dmabuf_feedback_v1_table_entry){ + .format = fmt->format, + .modifier = fmt->modifiers[k], + }; + n++; + } + } + assert(n == table_len); + + munmap(table, table_size); + + struct wlr_linux_dmabuf_feedback_v1_compiled *compiled = calloc(1, sizeof(*compiled) + + tranches_len * sizeof(struct wlr_linux_dmabuf_feedback_v1_compiled_tranche)); + if (compiled == NULL) { + close(ro_fd); + goto err_all_formats; + } + + compiled->main_device = feedback->main_device; + compiled->tranches_len = tranches_len; + compiled->table_fd = ro_fd; + compiled->table_size = table_size; + + // Build the indices lists for all tranches + for (size_t i = 0; i < tranches_len; i++) { + const struct wlr_linux_dmabuf_feedback_v1_tranche *tranche = &tranches[i]; + struct wlr_linux_dmabuf_feedback_v1_compiled_tranche *compiled_tranche = + &compiled->tranches[i]; + + compiled_tranche->target_device = tranche->target_device; + compiled_tranche->flags = tranche->flags; + + wl_array_init(&compiled_tranche->indices); + if (!wl_array_add(&compiled_tranche->indices, table_len * sizeof(uint16_t))) { + wlr_log(WLR_ERROR, "Failed to allocate tranche indices array"); + goto error_compiled; + } + + n = 0; + uint16_t *indices = compiled_tranche->indices.data; + for (size_t j = 0; j < tranche->formats.len; j++) { + const struct wlr_drm_format *fmt = &tranche->formats.formats[j]; + for (size_t k = 0; k < fmt->len; k++) { + ssize_t index = get_drm_format_set_index( + &all_formats, fmt->format, fmt->modifiers[k]); + if (index < 0) { + wlr_log(WLR_ERROR, "Format 0x%" PRIX32 " and modifier " + "0x%" PRIX64 " are in tranche #%zu but are missing " + "from the fallback tranche", + fmt->format, fmt->modifiers[k], i); + goto error_compiled; + } + indices[n] = index; + n++; + } + } + compiled_tranche->indices.size = n * sizeof(uint16_t); + } + + wlr_drm_format_set_finish(&all_formats); + + return compiled; + +error_compiled: + close(compiled->table_fd); + free(compiled); +err_all_formats: + wlr_drm_format_set_finish(&all_formats); + return NULL; +} + +static void compiled_feedback_destroy( + struct wlr_linux_dmabuf_feedback_v1_compiled *feedback) { + if (feedback == NULL) { + return; + } + for (size_t i = 0; i < feedback->tranches_len; i++) { + wl_array_release(&feedback->tranches[i].indices); + } + close(feedback->table_fd); + free(feedback); +} + +static void feedback_tranche_send( + const struct wlr_linux_dmabuf_feedback_v1_compiled_tranche *tranche, + struct wl_resource *resource) { + struct wl_array dev_array = { + .size = sizeof(tranche->target_device), + .data = (void *)&tranche->target_device, + }; + zwp_linux_dmabuf_feedback_v1_send_tranche_target_device(resource, &dev_array); + zwp_linux_dmabuf_feedback_v1_send_tranche_flags(resource, tranche->flags); + zwp_linux_dmabuf_feedback_v1_send_tranche_formats(resource, + (struct wl_array *)&tranche->indices); + zwp_linux_dmabuf_feedback_v1_send_tranche_done(resource); +} + +static void feedback_send(const struct wlr_linux_dmabuf_feedback_v1_compiled *feedback, + struct wl_resource *resource) { + struct wl_array dev_array = { + .size = sizeof(feedback->main_device), + .data = (void *)&feedback->main_device, + }; + zwp_linux_dmabuf_feedback_v1_send_main_device(resource, &dev_array); + + zwp_linux_dmabuf_feedback_v1_send_format_table(resource, + feedback->table_fd, feedback->table_size); + + for (size_t i = 0; i < feedback->tranches_len; i++) { + feedback_tranche_send(&feedback->tranches[i], resource); + } + + zwp_linux_dmabuf_feedback_v1_send_done(resource); +} + +static void linux_dmabuf_get_default_feedback(struct wl_client *client, + struct wl_resource *resource, uint32_t id) { + struct wlr_linux_dmabuf_v1 *linux_dmabuf = + linux_dmabuf_from_resource(resource); + + uint32_t version = wl_resource_get_version(resource); + struct wl_resource *feedback_resource = wl_resource_create(client, + &zwp_linux_dmabuf_feedback_v1_interface, version, id); + if (feedback_resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(feedback_resource, &linux_dmabuf_feedback_impl, + NULL, NULL); + + feedback_send(linux_dmabuf->default_feedback, feedback_resource); +} + +static void surface_destroy(struct wlr_linux_dmabuf_v1_surface *surface) { + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &surface->feedback_resources) { + struct wl_list *link = wl_resource_get_link(resource); + wl_list_remove(link); + wl_list_init(link); + } + + compiled_feedback_destroy(surface->feedback); + + wlr_addon_finish(&surface->addon); + wl_list_remove(&surface->link); + free(surface); +} + +static void surface_addon_destroy(struct wlr_addon *addon) { + struct wlr_linux_dmabuf_v1_surface *surface = + wl_container_of(addon, surface, addon); + surface_destroy(surface); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "wlr_linux_dmabuf_v1_surface", + .destroy = surface_addon_destroy, +}; + +static struct wlr_linux_dmabuf_v1_surface *surface_get_or_create( + struct wlr_linux_dmabuf_v1 *linux_dmabuf, + struct wlr_surface *wlr_surface) { + struct wlr_addon *addon = + wlr_addon_find(&wlr_surface->addons, linux_dmabuf, &surface_addon_impl); + if (addon != NULL) { + struct wlr_linux_dmabuf_v1_surface *surface = + wl_container_of(addon, surface, addon); + return surface; + } + + struct wlr_linux_dmabuf_v1_surface *surface = calloc(1, sizeof(*surface)); + if (surface == NULL) { + return NULL; + } + + surface->surface = wlr_surface; + surface->linux_dmabuf = linux_dmabuf; + wl_list_init(&surface->feedback_resources); + wlr_addon_init(&surface->addon, &wlr_surface->addons, linux_dmabuf, + &surface_addon_impl); + wl_list_insert(&linux_dmabuf->surfaces, &surface->link); + + return surface; +} + +static const struct wlr_linux_dmabuf_feedback_v1_compiled *surface_get_feedback( + struct wlr_linux_dmabuf_v1_surface *surface) { + if (surface->feedback != NULL) { + return surface->feedback; + } + return surface->linux_dmabuf->default_feedback; +} + +static void surface_feedback_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void linux_dmabuf_get_surface_feedback(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_linux_dmabuf_v1 *linux_dmabuf = + linux_dmabuf_from_resource(resource); + struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); + + struct wlr_linux_dmabuf_v1_surface *surface = + surface_get_or_create(linux_dmabuf, wlr_surface); + if (surface == NULL) { + wl_client_post_no_memory(client); + return; + } + + uint32_t version = wl_resource_get_version(resource); + struct wl_resource *feedback_resource = wl_resource_create(client, + &zwp_linux_dmabuf_feedback_v1_interface, version, id); + if (feedback_resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(feedback_resource, &linux_dmabuf_feedback_impl, + NULL, surface_feedback_handle_resource_destroy); + wl_list_insert(&surface->feedback_resources, wl_resource_get_link(feedback_resource)); + + feedback_send(surface_get_feedback(surface), feedback_resource); +} + +static void linux_dmabuf_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_impl = { + .destroy = linux_dmabuf_destroy, + .create_params = linux_dmabuf_create_params, + .get_default_feedback = linux_dmabuf_get_default_feedback, + .get_surface_feedback = linux_dmabuf_get_surface_feedback, +}; + +static void linux_dmabuf_send_modifiers(struct wl_resource *resource, + const struct wlr_drm_format *fmt) { + if (wl_resource_get_version(resource) < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) { + if (wlr_drm_format_has(fmt, DRM_FORMAT_MOD_INVALID)) { + zwp_linux_dmabuf_v1_send_format(resource, fmt->format); + } + return; + } + + // In case only INVALID and LINEAR are advertised, send INVALID only due to XWayland: + // https://gitlab.freedesktop.org/xorg/xserver/-/issues/1166 + if (fmt->len == 2 && wlr_drm_format_has(fmt, DRM_FORMAT_MOD_INVALID) + && wlr_drm_format_has(fmt, DRM_FORMAT_MOD_LINEAR)) { + uint64_t mod = DRM_FORMAT_MOD_INVALID; + zwp_linux_dmabuf_v1_send_modifier(resource, fmt->format, + mod >> 32, mod & 0xFFFFFFFF); + return; + } + + for (size_t i = 0; i < fmt->len; i++) { + uint64_t mod = fmt->modifiers[i]; + zwp_linux_dmabuf_v1_send_modifier(resource, fmt->format, + mod >> 32, mod & 0xFFFFFFFF); + } +} + +static void linux_dmabuf_send_formats(struct wlr_linux_dmabuf_v1 *linux_dmabuf, + struct wl_resource *resource) { + for (size_t i = 0; i < linux_dmabuf->default_formats.len; i++) { + const struct wlr_drm_format *fmt = &linux_dmabuf->default_formats.formats[i]; + linux_dmabuf_send_modifiers(resource, fmt); + } +} + +static void linux_dmabuf_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_linux_dmabuf_v1 *linux_dmabuf = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwp_linux_dmabuf_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &linux_dmabuf_impl, + linux_dmabuf, NULL); + + if (version < ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION) { + linux_dmabuf_send_formats(linux_dmabuf, resource); + } +} + +static struct wlr_buffer *buffer_from_resource(struct wl_resource *resource) { + struct wlr_dmabuf_v1_buffer *buffer = + wlr_dmabuf_v1_buffer_try_from_buffer_resource(resource); + assert(buffer != NULL); + return &buffer->base; +} + +static const struct wlr_buffer_resource_interface buffer_resource_interface = { + .name = "wlr_dmabuf_v1_buffer", + .is_instance = buffer_resource_is_instance, + .from_resource = buffer_from_resource, +}; + +static void linux_dmabuf_v1_destroy(struct wlr_linux_dmabuf_v1 *linux_dmabuf) { + wl_signal_emit_mutable(&linux_dmabuf->events.destroy, linux_dmabuf); + + struct wlr_linux_dmabuf_v1_surface *surface, *surface_tmp; + wl_list_for_each_safe(surface, surface_tmp, &linux_dmabuf->surfaces, link) { + surface_destroy(surface); + } + + compiled_feedback_destroy(linux_dmabuf->default_feedback); + wlr_drm_format_set_finish(&linux_dmabuf->default_formats); + if (linux_dmabuf->main_device_fd >= 0) { + close(linux_dmabuf->main_device_fd); + } + + wl_list_remove(&linux_dmabuf->display_destroy.link); + + wl_global_destroy(linux_dmabuf->global); + free(linux_dmabuf); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_linux_dmabuf_v1 *linux_dmabuf = + wl_container_of(listener, linux_dmabuf, display_destroy); + linux_dmabuf_v1_destroy(linux_dmabuf); +} + +static bool set_default_feedback(struct wlr_linux_dmabuf_v1 *linux_dmabuf, + const struct wlr_linux_dmabuf_feedback_v1 *feedback) { + struct wlr_linux_dmabuf_feedback_v1_compiled *compiled = feedback_compile(feedback); + if (compiled == NULL) { + return false; + } + + drmDevice *device = NULL; + if (drmGetDeviceFromDevId(feedback->main_device, 0, &device) != 0) { + wlr_log_errno(WLR_ERROR, "drmGetDeviceFromDevId failed"); + goto error_compiled; + } + + int main_device_fd = -1; + if (device->available_nodes & (1 << DRM_NODE_RENDER)) { + const char *name = device->nodes[DRM_NODE_RENDER]; + main_device_fd = open(name, O_RDWR | O_CLOEXEC); + drmFreeDevice(&device); + if (main_device_fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open DRM device %s", name); + goto error_compiled; + } + } else { + // Likely a split display/render setup. Unfortunately we have no way to + // get back the proper render node used by the renderer under-the-hood. + // TODO: drop once mesa!24825 is widespread + assert(device->available_nodes & (1 << DRM_NODE_PRIMARY)); + wlr_log(WLR_DEBUG, "DRM device %s has no render node, " + "skipping DMA-BUF import checks", device->nodes[DRM_NODE_PRIMARY]); + drmFreeDevice(&device); + } + + size_t tranches_len = + feedback->tranches.size / sizeof(struct wlr_linux_dmabuf_feedback_v1_tranche); + const struct wlr_linux_dmabuf_feedback_v1_tranche *tranches = feedback->tranches.data; + struct wlr_drm_format_set formats = {0}; + for (size_t i = 0; i < tranches_len; i++) { + const struct wlr_linux_dmabuf_feedback_v1_tranche *tranche = &tranches[i]; + + if (!wlr_drm_format_set_union(&formats, &formats, &tranche->formats)) { + goto error_formats; + } + } + + compiled_feedback_destroy(linux_dmabuf->default_feedback); + linux_dmabuf->default_feedback = compiled; + + if (linux_dmabuf->main_device_fd >= 0) { + close(linux_dmabuf->main_device_fd); + } + linux_dmabuf->main_device_fd = main_device_fd; + + wlr_drm_format_set_finish(&linux_dmabuf->default_formats); + linux_dmabuf->default_formats = formats; + + return true; + +error_formats: + wlr_drm_format_set_finish(&formats); +error_compiled: + compiled_feedback_destroy(compiled); + return false; +} + +struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_create(struct wl_display *display, + uint32_t version, const struct wlr_linux_dmabuf_feedback_v1 *default_feedback) { + assert(version <= LINUX_DMABUF_VERSION); + + struct wlr_linux_dmabuf_v1 *linux_dmabuf = calloc(1, sizeof(*linux_dmabuf)); + if (linux_dmabuf == NULL) { + wlr_log(WLR_ERROR, "could not create simple dmabuf manager"); + return NULL; + } + linux_dmabuf->main_device_fd = -1; + + wl_list_init(&linux_dmabuf->surfaces); + wl_signal_init(&linux_dmabuf->events.destroy); + + linux_dmabuf->global = wl_global_create(display, &zwp_linux_dmabuf_v1_interface, + version, linux_dmabuf, linux_dmabuf_bind); + if (!linux_dmabuf->global) { + wlr_log(WLR_ERROR, "could not create linux dmabuf v1 wl global"); + goto error_linux_dmabuf; + } + + if (!set_default_feedback(linux_dmabuf, default_feedback)) { + goto error_global; + } + + linux_dmabuf->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &linux_dmabuf->display_destroy); + + wlr_linux_dmabuf_v1_set_check_dmabuf_callback(linux_dmabuf, + check_import_dmabuf, linux_dmabuf); + + wlr_buffer_register_resource_interface(&buffer_resource_interface); + + return linux_dmabuf; + +error_global: + wl_global_destroy(linux_dmabuf->global); +error_linux_dmabuf: + free(linux_dmabuf); + return NULL; +} + +struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_create_with_renderer(struct wl_display *display, + uint32_t version, struct wlr_renderer *renderer) { + const struct wlr_linux_dmabuf_feedback_v1_init_options options = { + .main_renderer = renderer, + }; + struct wlr_linux_dmabuf_feedback_v1 feedback = {0}; + if (!wlr_linux_dmabuf_feedback_v1_init_with_options(&feedback, &options)) { + return NULL; + } + struct wlr_linux_dmabuf_v1 *linux_dmabuf = + wlr_linux_dmabuf_v1_create(display, version, &feedback); + wlr_linux_dmabuf_feedback_v1_finish(&feedback); + return linux_dmabuf; +} + +void wlr_linux_dmabuf_v1_set_check_dmabuf_callback(struct wlr_linux_dmabuf_v1 *linux_dmabuf, + bool (*callback)(struct wlr_dmabuf_attributes *attribs, void *data), void *data) { + assert(callback); + linux_dmabuf->check_dmabuf_callback = callback; + linux_dmabuf->check_dmabuf_callback_data = data; +} + +bool wlr_linux_dmabuf_v1_set_surface_feedback( + struct wlr_linux_dmabuf_v1 *linux_dmabuf, + struct wlr_surface *wlr_surface, + const struct wlr_linux_dmabuf_feedback_v1 *feedback) { + struct wlr_linux_dmabuf_v1_surface *surface = + surface_get_or_create(linux_dmabuf, wlr_surface); + if (surface == NULL) { + return false; + } + + struct wlr_linux_dmabuf_feedback_v1_compiled *compiled = NULL; + if (feedback != NULL) { + compiled = feedback_compile(feedback); + if (compiled == NULL) { + return false; + } + } + + compiled_feedback_destroy(surface->feedback); + surface->feedback = compiled; + + struct wl_resource *resource; + wl_resource_for_each(resource, &surface->feedback_resources) { + feedback_send(surface_get_feedback(surface), resource); + } + + return true; +} + +struct wlr_linux_dmabuf_feedback_v1_tranche *wlr_linux_dmabuf_feedback_add_tranche( + struct wlr_linux_dmabuf_feedback_v1 *feedback) { + struct wlr_linux_dmabuf_feedback_v1_tranche *tranche = + wl_array_add(&feedback->tranches, sizeof(*tranche)); + if (tranche == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + *tranche = (struct wlr_linux_dmabuf_feedback_v1_tranche){0}; + return tranche; +} + +void wlr_linux_dmabuf_feedback_v1_finish(struct wlr_linux_dmabuf_feedback_v1 *feedback) { + struct wlr_linux_dmabuf_feedback_v1_tranche *tranche; + wl_array_for_each(tranche, &feedback->tranches) { + wlr_drm_format_set_finish(&tranche->formats); + } + wl_array_release(&feedback->tranches); +} + +static bool devid_from_fd(int fd, dev_t *devid) { + struct stat stat; + if (fstat(fd, &stat) != 0) { + wlr_log_errno(WLR_ERROR, "fstat failed"); + return false; + } + *devid = stat.st_rdev; + return true; +} + +static bool is_secondary_drm_backend(struct wlr_backend *backend) { +#if WLR_HAS_DRM_BACKEND + return wlr_backend_is_drm(backend) && + wlr_drm_backend_get_parent(backend) != NULL; +#else + return false; +#endif +} + +bool wlr_linux_dmabuf_feedback_v1_init_with_options(struct wlr_linux_dmabuf_feedback_v1 *feedback, + const struct wlr_linux_dmabuf_feedback_v1_init_options *options) { + assert(options->main_renderer != NULL); + assert(options->scanout_primary_output == NULL || + options->output_layer_feedback_event == NULL); + + *feedback = (struct wlr_linux_dmabuf_feedback_v1){0}; + + int renderer_drm_fd = wlr_renderer_get_drm_fd(options->main_renderer); + if (renderer_drm_fd < 0) { + wlr_log(WLR_ERROR, "Failed to get renderer DRM FD"); + goto error; + } + dev_t renderer_dev; + if (!devid_from_fd(renderer_drm_fd, &renderer_dev)) { + goto error; + } + + feedback->main_device = renderer_dev; + + const struct wlr_drm_format_set *renderer_formats = + wlr_renderer_get_dmabuf_texture_formats(options->main_renderer); + if (renderer_formats == NULL) { + wlr_log(WLR_ERROR, "Failed to get renderer DMA-BUF texture formats"); + goto error; + } + + if (options->output_layer_feedback_event != NULL) { + const struct wlr_output_layer_feedback_event *event = options->output_layer_feedback_event; + + struct wlr_linux_dmabuf_feedback_v1_tranche *tranche = + wlr_linux_dmabuf_feedback_add_tranche(feedback); + if (tranche == NULL) { + goto error; + } + + tranche->target_device = event->target_device; + tranche->flags = ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT; + if (!wlr_drm_format_set_intersect(&tranche->formats, event->formats, renderer_formats)) { + wlr_log(WLR_ERROR, "Failed to intersect renderer and scanout formats"); + goto error; + } + } else if (options->scanout_primary_output != NULL && + !is_secondary_drm_backend(options->scanout_primary_output->backend)) { + int backend_drm_fd = wlr_backend_get_drm_fd(options->scanout_primary_output->backend); + if (backend_drm_fd < 0) { + wlr_log(WLR_ERROR, "Failed to get backend DRM FD"); + goto error; + } + dev_t backend_dev; + if (!devid_from_fd(backend_drm_fd, &backend_dev)) { + goto error; + } + + const struct wlr_drm_format_set *scanout_formats = + wlr_output_get_primary_formats(options->scanout_primary_output, WLR_BUFFER_CAP_DMABUF); + if (scanout_formats == NULL) { + wlr_log(WLR_ERROR, "Failed to get output primary DMA-BUF formats"); + goto error; + } + + struct wlr_linux_dmabuf_feedback_v1_tranche *tranche = + wlr_linux_dmabuf_feedback_add_tranche(feedback); + if (tranche == NULL) { + goto error; + } + + tranche->target_device = backend_dev; + tranche->flags = ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT; + if (!wlr_drm_format_set_intersect(&tranche->formats, scanout_formats, renderer_formats)) { + wlr_log(WLR_ERROR, "Failed to intersect renderer and scanout formats"); + goto error; + } + } + + struct wlr_linux_dmabuf_feedback_v1_tranche *tranche = + wlr_linux_dmabuf_feedback_add_tranche(feedback); + if (tranche == NULL) { + goto error; + } + + tranche->target_device = renderer_dev; + if (!wlr_drm_format_set_copy(&tranche->formats, renderer_formats)) { + goto error; + } + + return true; + +error: + wlr_linux_dmabuf_feedback_v1_finish(feedback); + return false; +} diff --git a/types/wlr_matrix.c b/types/wlr_matrix.c new file mode 100644 index 0000000..7b6671f --- /dev/null +++ b/types/wlr_matrix.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include +#include +#include "types/wlr_matrix.h" + +void wlr_matrix_identity(float mat[static 9]) { + static const float identity[9] = { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }; + memcpy(mat, identity, sizeof(identity)); +} + +void wlr_matrix_multiply(float mat[static 9], const float a[static 9], + const float b[static 9]) { + float product[9]; + + product[0] = a[0]*b[0] + a[1]*b[3] + a[2]*b[6]; + product[1] = a[0]*b[1] + a[1]*b[4] + a[2]*b[7]; + product[2] = a[0]*b[2] + a[1]*b[5] + a[2]*b[8]; + + product[3] = a[3]*b[0] + a[4]*b[3] + a[5]*b[6]; + product[4] = a[3]*b[1] + a[4]*b[4] + a[5]*b[7]; + product[5] = a[3]*b[2] + a[4]*b[5] + a[5]*b[8]; + + product[6] = a[6]*b[0] + a[7]*b[3] + a[8]*b[6]; + product[7] = a[6]*b[1] + a[7]*b[4] + a[8]*b[7]; + product[8] = a[6]*b[2] + a[7]*b[5] + a[8]*b[8]; + + memcpy(mat, product, sizeof(product)); +} + +void wlr_matrix_transpose(float mat[static 9], const float a[static 9]) { + float transposition[9] = { + a[0], a[3], a[6], + a[1], a[4], a[7], + a[2], a[5], a[8], + }; + memcpy(mat, transposition, sizeof(transposition)); +} + +void wlr_matrix_translate(float mat[static 9], float x, float y) { + float translate[9] = { + 1.0f, 0.0f, x, + 0.0f, 1.0f, y, + 0.0f, 0.0f, 1.0f, + }; + wlr_matrix_multiply(mat, mat, translate); +} + +void wlr_matrix_scale(float mat[static 9], float x, float y) { + float scale[9] = { + x, 0.0f, 0.0f, + 0.0f, y, 0.0f, + 0.0f, 0.0f, 1.0f, + }; + wlr_matrix_multiply(mat, mat, scale); +} + +void wlr_matrix_rotate(float mat[static 9], float rad) { + float rotate[9] = { + cos(rad), -sin(rad), 0.0f, + sin(rad), cos(rad), 0.0f, + 0.0f, 0.0f, 1.0f, + }; + wlr_matrix_multiply(mat, mat, rotate); +} + +static const float transforms[][9] = { + [WL_OUTPUT_TRANSFORM_NORMAL] = { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_90] = { + 0.0f, 1.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_180] = { + -1.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_270] = { + 0.0f, -1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_FLIPPED] = { + -1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_FLIPPED_90] = { + 0.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_FLIPPED_180] = { + 1.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_FLIPPED_270] = { + 0.0f, -1.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, +}; + +void wlr_matrix_transform(float mat[static 9], + enum wl_output_transform transform) { + wlr_matrix_multiply(mat, mat, transforms[transform]); +} + +void matrix_projection(float mat[static 9], int width, int height, + enum wl_output_transform transform) { + memset(mat, 0, sizeof(*mat) * 9); + + const float *t = transforms[transform]; + float x = 2.0f / width; + float y = 2.0f / height; + + // Rotation + reflection + mat[0] = x * t[0]; + mat[1] = x * t[1]; + mat[3] = y * -t[3]; + mat[4] = y * -t[4]; + + // Translation + mat[2] = -copysign(1.0f, mat[0] + mat[1]); + mat[5] = -copysign(1.0f, mat[3] + mat[4]); + + // Identity + mat[8] = 1.0f; +} + +void wlr_matrix_project_box(float mat[static 9], const struct wlr_box *box, + enum wl_output_transform transform, float rotation, + const float projection[static 9]) { + int x = box->x; + int y = box->y; + int width = box->width; + int height = box->height; + + wlr_matrix_identity(mat); + wlr_matrix_translate(mat, x, y); + + if (rotation != 0) { + wlr_matrix_translate(mat, width/2, height/2); + wlr_matrix_rotate(mat, rotation); + wlr_matrix_translate(mat, -width/2, -height/2); + } + + wlr_matrix_scale(mat, width, height); + + if (transform != WL_OUTPUT_TRANSFORM_NORMAL) { + wlr_matrix_translate(mat, 0.5, 0.5); + wlr_matrix_transform(mat, transform); + wlr_matrix_translate(mat, -0.5, -0.5); + } + + wlr_matrix_multiply(mat, projection, mat); +} diff --git a/types/wlr_output_layer.c b/types/wlr_output_layer.c new file mode 100644 index 0000000..7624846 --- /dev/null +++ b/types/wlr_output_layer.c @@ -0,0 +1,25 @@ +#include +#include + +struct wlr_output_layer *wlr_output_layer_create(struct wlr_output *output) { + struct wlr_output_layer *layer = calloc(1, sizeof(*layer)); + if (layer == NULL) { + return NULL; + } + + wl_list_insert(&output->layers, &layer->link); + wlr_addon_set_init(&layer->addons); + wl_signal_init(&layer->events.feedback); + + return layer; +} + +void wlr_output_layer_destroy(struct wlr_output_layer *layer) { + if (layer == NULL) { + return; + } + + wlr_addon_set_finish(&layer->addons); + wl_list_remove(&layer->link); + free(layer); +} diff --git a/types/wlr_output_layout.c b/types/wlr_output_layout.c new file mode 100644 index 0000000..853ff59 --- /dev/null +++ b/types/wlr_output_layout.c @@ -0,0 +1,488 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct wlr_addon_interface addon_impl; + +static void output_layout_handle_display_destroy(struct wl_listener *listener, + void *data) { + struct wlr_output_layout *layout = wl_container_of(listener, layout, display_destroy); + wlr_output_layout_destroy(layout); +} + +struct wlr_output_layout *wlr_output_layout_create(struct wl_display *display) { + struct wlr_output_layout *layout = calloc(1, sizeof(*layout)); + if (layout == NULL) { + return NULL; + } + wl_list_init(&layout->outputs); + layout->display = display; + + wl_signal_init(&layout->events.add); + wl_signal_init(&layout->events.change); + wl_signal_init(&layout->events.destroy); + + layout->display_destroy.notify = output_layout_handle_display_destroy; + wl_display_add_destroy_listener(display, &layout->display_destroy); + + return layout; +} + +static void output_layout_output_destroy( + struct wlr_output_layout_output *l_output) { + wl_signal_emit_mutable(&l_output->events.destroy, l_output); + wlr_output_destroy_global(l_output->output); + wl_list_remove(&l_output->commit.link); + wl_list_remove(&l_output->link); + wlr_addon_finish(&l_output->addon); + free(l_output); +} + +void wlr_output_layout_destroy(struct wlr_output_layout *layout) { + if (!layout) { + return; + } + + wl_signal_emit_mutable(&layout->events.destroy, layout); + + struct wlr_output_layout_output *l_output, *temp; + wl_list_for_each_safe(l_output, temp, &layout->outputs, link) { + output_layout_output_destroy(l_output); + } + + wl_list_remove(&layout->display_destroy.link); + free(layout); +} + +static void output_layout_output_get_box( + struct wlr_output_layout_output *l_output, + struct wlr_box *box) { + box->x = l_output->x; + box->y = l_output->y; + wlr_output_effective_resolution(l_output->output, + &box->width, &box->height); +} + +/** + * This must be called whenever the layout changes to reconfigure the auto + * configured outputs and emit the `changed` event. + * + * Auto configured outputs are placed to the right of the north east corner of + * the rightmost output in the layout in a horizontal line. + */ +static void output_layout_reconfigure(struct wlr_output_layout *layout) { + int max_x = INT_MIN; + int max_x_y = INT_MIN; // y value for the max_x output + + // find the rightmost x coordinate occupied by a manually configured output + // in the layout + struct wlr_output_layout_output *l_output; + struct wlr_box output_box; + + wl_list_for_each(l_output, &layout->outputs, link) { + if (l_output->auto_configured) { + continue; + } + + output_layout_output_get_box(l_output, &output_box); + if (output_box.x + output_box.width > max_x) { + max_x = output_box.x + output_box.width; + max_x_y = output_box.y; + } + } + + if (max_x == INT_MIN) { + // there are no manually configured outputs + max_x = 0; + max_x_y = 0; + } + + wl_list_for_each(l_output, &layout->outputs, link) { + if (!l_output->auto_configured) { + continue; + } + output_layout_output_get_box(l_output, &output_box); + l_output->x = max_x; + l_output->y = max_x_y; + max_x += output_box.width; + } + + wl_signal_emit_mutable(&layout->events.change, layout); +} + +static void output_update_global(struct wlr_output_layout *layout, + struct wlr_output *output) { + // Don't expose the output if it doesn't have a current mode + if (output->width > 0 && output->height > 0) { + wlr_output_create_global(output, layout->display); + } else { + wlr_output_destroy_global(output); + } +} + +static void handle_output_commit(struct wl_listener *listener, void *data) { + struct wlr_output_layout_output *l_output = + wl_container_of(listener, l_output, commit); + struct wlr_output_event_commit *event = data; + + if (event->state->committed & (WLR_OUTPUT_STATE_SCALE | + WLR_OUTPUT_STATE_TRANSFORM | + WLR_OUTPUT_STATE_MODE)) { + output_layout_reconfigure(l_output->layout); + output_update_global(l_output->layout, l_output->output); + } +} + +static void addon_destroy(struct wlr_addon *addon) { + assert(addon->impl == &addon_impl); + struct wlr_output_layout_output *l_output = + wl_container_of(addon, l_output, addon); + struct wlr_output_layout *layout = l_output->layout; + output_layout_output_destroy(l_output); + output_layout_reconfigure(layout); +} + +static const struct wlr_addon_interface addon_impl = { + .name = "wlr_output_layout_output", + .destroy = addon_destroy, +}; + +static struct wlr_output_layout_output *output_layout_output_create( + struct wlr_output_layout *layout, struct wlr_output *output) { + struct wlr_output_layout_output *l_output = calloc(1, sizeof(*l_output)); + if (l_output == NULL) { + return NULL; + } + l_output->layout = layout; + l_output->output = output; + wl_signal_init(&l_output->events.destroy); + + /* + * Insert at the end of the list so that auto-configuring the + * new output doesn't change the layout of other outputs + */ + wl_list_insert(layout->outputs.prev, &l_output->link); + + wl_signal_add(&output->events.commit, &l_output->commit); + l_output->commit.notify = handle_output_commit; + + wlr_addon_init(&l_output->addon, &output->addons, layout, &addon_impl); + + return l_output; +} + +static struct wlr_output_layout_output *output_layout_add(struct wlr_output_layout *layout, + struct wlr_output *output, int lx, int ly, + bool auto_configured) { + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(layout, output); + bool is_new = l_output == NULL; + if (is_new) { + l_output = output_layout_output_create(layout, output); + if (l_output == NULL) { + return NULL; + } + } + + l_output->x = lx; + l_output->y = ly; + l_output->auto_configured = auto_configured; + + output_layout_reconfigure(layout); + output_update_global(layout, output); + + if (is_new) { + wl_signal_emit_mutable(&layout->events.add, l_output); + } + + return l_output; +} + +struct wlr_output_layout_output *wlr_output_layout_add(struct wlr_output_layout *layout, + struct wlr_output *output, int lx, int ly) { + return output_layout_add(layout, output, lx, ly, false); +} + +struct wlr_output_layout_output *wlr_output_layout_add_auto(struct wlr_output_layout *layout, + struct wlr_output *output) { + return output_layout_add(layout, output, 0, 0, true); +} + +void wlr_output_layout_remove(struct wlr_output_layout *layout, + struct wlr_output *output) { + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(layout, output); + if (l_output != NULL) { + output_layout_output_destroy(l_output); + output_layout_reconfigure(layout); + } +} + +struct wlr_output_layout_output *wlr_output_layout_get( + struct wlr_output_layout *layout, struct wlr_output *reference) { + struct wlr_output_layout_output *l_output = NULL; + struct wlr_addon *addon = + wlr_addon_find(&reference->addons, layout, &addon_impl); + if (addon) { + l_output = wl_container_of(addon, l_output, addon); + } + return l_output; +} + +bool wlr_output_layout_contains_point(struct wlr_output_layout *layout, + struct wlr_output *reference, int lx, int ly) { + if (reference) { + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(layout, reference); + if (!l_output) { + return false; + } + struct wlr_box output_box; + output_layout_output_get_box(l_output, &output_box); + return wlr_box_contains_point(&output_box, lx, ly); + } else { + return !!wlr_output_layout_output_at(layout, lx, ly); + } +} + +bool wlr_output_layout_intersects(struct wlr_output_layout *layout, + struct wlr_output *reference, const struct wlr_box *target_lbox) { + struct wlr_box out_box; + + if (reference == NULL) { + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->outputs, link) { + struct wlr_box output_box; + output_layout_output_get_box(l_output, &output_box); + if (wlr_box_intersection(&out_box, &output_box, target_lbox)) { + return true; + } + } + return false; + } else { + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(layout, reference); + if (!l_output) { + return false; + } + + struct wlr_box output_box; + output_layout_output_get_box(l_output, &output_box); + return wlr_box_intersection(&out_box, &output_box, target_lbox); + } +} + +struct wlr_output *wlr_output_layout_output_at(struct wlr_output_layout *layout, + double lx, double ly) { + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->outputs, link) { + struct wlr_box output_box; + output_layout_output_get_box(l_output, &output_box); + if (wlr_box_contains_point(&output_box, lx, ly)) { + return l_output->output; + } + } + return NULL; +} + +void wlr_output_layout_output_coords(struct wlr_output_layout *layout, + struct wlr_output *reference, double *lx, double *ly) { + assert(layout && reference); + double src_x = *lx; + double src_y = *ly; + + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->outputs, link) { + if (l_output->output == reference) { + *lx = src_x - (double)l_output->x; + *ly = src_y - (double)l_output->y; + return; + } + } +} + +void wlr_output_layout_closest_point(struct wlr_output_layout *layout, + struct wlr_output *reference, double lx, double ly, double *dest_lx, + double *dest_ly) { + if (dest_lx == NULL && dest_ly == NULL) { + return; + } + + double min_x = lx, min_y = ly, min_distance = DBL_MAX; + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->outputs, link) { + if (reference != NULL && reference != l_output->output) { + continue; + } + + double output_x, output_y, output_distance; + struct wlr_box output_box; + output_layout_output_get_box(l_output, &output_box); + wlr_box_closest_point(&output_box, lx, ly, &output_x, &output_y); + + // calculate squared distance suitable for comparison + output_distance = + (lx - output_x) * (lx - output_x) + (ly - output_y) * (ly - output_y); + + if (!isfinite(output_distance)) { + output_distance = DBL_MAX; + } + + if (output_distance < min_distance) { + min_x = output_x; + min_y = output_y; + min_distance = output_distance; + } + } + + if (dest_lx) { + *dest_lx = min_x; + } + if (dest_ly) { + *dest_ly = min_y; + } +} + +void wlr_output_layout_get_box(struct wlr_output_layout *layout, + struct wlr_output *reference, struct wlr_box *dest_box) { + *dest_box = (struct wlr_box){0}; + + struct wlr_output_layout_output *l_output; + if (reference) { + // output extents + l_output = wlr_output_layout_get(layout, reference); + + if (l_output) { + output_layout_output_get_box(l_output, dest_box); + } + } else { + // layout extents + int min_x = 0, max_x = 0, min_y = 0, max_y = 0; + if (!wl_list_empty(&layout->outputs)) { + min_x = min_y = INT_MAX; + max_x = max_y = INT_MIN; + wl_list_for_each(l_output, &layout->outputs, link) { + struct wlr_box output_box; + output_layout_output_get_box(l_output, &output_box); + if (output_box.x < min_x) { + min_x = output_box.x; + } + if (output_box.y < min_y) { + min_y = output_box.y; + } + if (output_box.x + output_box.width > max_x) { + max_x = output_box.x + output_box.width; + } + if (output_box.y + output_box.height > max_y) { + max_y = output_box.y + output_box.height; + } + } + } + + dest_box->x = min_x; + dest_box->y = min_y; + dest_box->width = max_x - min_x; + dest_box->height = max_y - min_y; + } +} + +struct wlr_output *wlr_output_layout_get_center_output( + struct wlr_output_layout *layout) { + if (wl_list_empty(&layout->outputs)) { + return NULL; + } + + struct wlr_box extents; + wlr_output_layout_get_box(layout, NULL, &extents); + double center_x = extents.width / 2. + extents.x; + double center_y = extents.height / 2. + extents.y; + + double dest_x = 0, dest_y = 0; + wlr_output_layout_closest_point(layout, NULL, center_x, center_y, + &dest_x, &dest_y); + + return wlr_output_layout_output_at(layout, dest_x, dest_y); +} + +enum distance_selection_method { + NEAREST, + FARTHEST +}; + +static struct wlr_output *wlr_output_layout_output_in_direction( + struct wlr_output_layout *layout, enum wlr_direction direction, + struct wlr_output *reference, double ref_lx, double ref_ly, + enum distance_selection_method distance_method) { + assert(reference); + + struct wlr_box ref_box; + wlr_output_layout_get_box(layout, reference, &ref_box); + if (wlr_box_empty(&ref_box)) { + // The output doesn't belong to the layout + return NULL; + } + + double min_distance = (distance_method == NEAREST) ? DBL_MAX : DBL_MIN; + struct wlr_output *closest_output = NULL; + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->outputs, link) { + if (reference != NULL && reference == l_output->output) { + continue; + } + + struct wlr_box box; + output_layout_output_get_box(l_output, &box); + + bool match = false; + // test to make sure this output is in the given direction + if (direction & WLR_DIRECTION_LEFT) { + match = box.x + box.width <= ref_box.x || match; + } + if (direction & WLR_DIRECTION_RIGHT) { + match = box.x >= ref_box.x + ref_box.width || match; + } + if (direction & WLR_DIRECTION_UP) { + match = box.y + box.height <= ref_box.y || match; + } + if (direction & WLR_DIRECTION_DOWN) { + match = box.y >= ref_box.y + ref_box.height || match; + } + if (!match) { + continue; + } + + // calculate distance from the given reference point + double x, y; + wlr_output_layout_closest_point(layout, l_output->output, + ref_lx, ref_ly, &x, &y); + double distance = + (x - ref_lx) * (x - ref_lx) + (y - ref_ly) * (y - ref_ly); + + if ((distance_method == NEAREST) + ? distance < min_distance + : distance > min_distance) { + min_distance = distance; + closest_output = l_output->output; + } + } + return closest_output; +} + +struct wlr_output *wlr_output_layout_adjacent_output( + struct wlr_output_layout *layout, enum wlr_direction direction, + struct wlr_output *reference, double ref_lx, double ref_ly) { + return wlr_output_layout_output_in_direction(layout, direction, + reference, ref_lx, ref_ly, NEAREST); +} + +struct wlr_output *wlr_output_layout_farthest_output( + struct wlr_output_layout *layout, enum wlr_direction direction, + struct wlr_output *reference, double ref_lx, double ref_ly) { + return wlr_output_layout_output_in_direction(layout, direction, + reference, ref_lx, ref_ly, FARTHEST); +} diff --git a/types/wlr_output_management_v1.c b/types/wlr_output_management_v1.c new file mode 100644 index 0000000..b90d227 --- /dev/null +++ b/types/wlr_output_management_v1.c @@ -0,0 +1,1029 @@ +#include +#include +#include +#include +#include +#include "wlr-output-management-unstable-v1-protocol.h" + +#define OUTPUT_MANAGER_VERSION 4 + +enum { + HEAD_STATE_ENABLED = 1 << 0, + HEAD_STATE_MODE = 1 << 1, + HEAD_STATE_POSITION = 1 << 2, + HEAD_STATE_TRANSFORM = 1 << 3, + HEAD_STATE_SCALE = 1 << 4, + HEAD_STATE_ADAPTIVE_SYNC = 1 << 5, +}; + +static const uint32_t HEAD_STATE_ALL = HEAD_STATE_ENABLED | HEAD_STATE_MODE | + HEAD_STATE_POSITION | HEAD_STATE_TRANSFORM | HEAD_STATE_SCALE | + HEAD_STATE_ADAPTIVE_SYNC; + +static const struct zwlr_output_head_v1_interface head_impl; + +// Can return NULL if the head is inert +static struct wlr_output_head_v1 *head_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_output_head_v1_interface, &head_impl)); + return wl_resource_get_user_data(resource); +} + +static const struct zwlr_output_mode_v1_interface output_mode_impl; + +// Can return NULL if the mode is custom or inert +static struct wlr_output_mode *mode_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_output_mode_v1_interface, &output_mode_impl)); + return wl_resource_get_user_data(resource); +} + +static void head_destroy(struct wlr_output_head_v1 *head) { + if (head == NULL) { + return; + } + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &head->mode_resources) { + zwlr_output_mode_v1_send_finished(resource); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + wl_resource_set_user_data(resource, NULL); + } + wl_resource_for_each_safe(resource, tmp, &head->resources) { + zwlr_output_head_v1_send_finished(resource); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + wl_resource_set_user_data(resource, NULL); + } + wl_list_remove(&head->link); + wl_list_remove(&head->output_destroy.link); + free(head); +} + +static void head_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_output_head_v1 *head = + wl_container_of(listener, head, output_destroy); + head->manager->current_configuration_dirty = true; + head_destroy(head); +} + +static struct wlr_output_head_v1 *head_create( + struct wlr_output_manager_v1 *manager, struct wlr_output *output) { + struct wlr_output_head_v1 *head = calloc(1, sizeof(*head)); + if (head == NULL) { + return NULL; + } + head->manager = manager; + head->state.output = output; + wl_list_init(&head->resources); + wl_list_init(&head->mode_resources); + wl_list_insert(&manager->heads, &head->link); + head->output_destroy.notify = head_handle_output_destroy; + wl_signal_add(&output->events.destroy, &head->output_destroy); + return head; +} + +static void head_destroy_custom_mode_resources(struct wlr_output_head_v1 *head) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &head->mode_resources) { + if (wl_resource_get_user_data(resource) != NULL) { + continue; + } + zwlr_output_mode_v1_send_finished(resource); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } +} + +static bool head_has_custom_mode_resources(const struct wlr_output_head_v1 *head) { + struct wl_resource *resource; + wl_resource_for_each(resource, &head->mode_resources) { + if (wl_resource_get_user_data(resource) == NULL) { + return true; + } + } + return false; +} + + +static void config_head_destroy( + struct wlr_output_configuration_head_v1 *config_head) { + if (config_head == NULL) { + return; + } + if (config_head->resource != NULL) { + wl_resource_set_user_data(config_head->resource, NULL); // make inert + } + wl_list_remove(&config_head->link); + wl_list_remove(&config_head->output_destroy.link); + free(config_head); +} + +static void config_head_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_output_configuration_head_v1 *config_head = + wl_container_of(listener, config_head, output_destroy); + config_head_destroy(config_head); +} + +static struct wlr_output_configuration_head_v1 *config_head_create( + struct wlr_output_configuration_v1 *config, struct wlr_output *output) { + struct wlr_output_configuration_head_v1 *config_head = + calloc(1, sizeof(*config_head)); + if (config_head == NULL) { + return NULL; + } + config_head->config = config; + config_head->state.output = output; + wl_list_insert(&config->heads, &config_head->link); + config_head->output_destroy.notify = config_head_handle_output_destroy; + wl_signal_add(&output->events.destroy, &config_head->output_destroy); + return config_head; +} + +struct wlr_output_configuration_head_v1 * + wlr_output_configuration_head_v1_create( + struct wlr_output_configuration_v1 *config, struct wlr_output *output) { + struct wlr_output_configuration_head_v1 *config_head = + config_head_create(config, output); + if (config_head == NULL) { + return NULL; + } + config_head->state.enabled = output->enabled; + config_head->state.mode = output->current_mode; + config_head->state.custom_mode.width = output->width; + config_head->state.custom_mode.height = output->height; + config_head->state.custom_mode.refresh = output->refresh; + config_head->state.transform = output->transform; + config_head->state.scale = output->scale; + config_head->state.adaptive_sync_enabled = + output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED; + return config_head; +} + +static const struct zwlr_output_configuration_head_v1_interface config_head_impl; + +// Can return NULL if the configuration head is inert +static struct wlr_output_configuration_head_v1 *config_head_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_output_configuration_head_v1_interface, &config_head_impl)); + return wl_resource_get_user_data(resource); +} + +static void config_head_handle_set_mode(struct wl_client *client, + struct wl_resource *config_head_resource, + struct wl_resource *mode_resource) { + struct wlr_output_configuration_head_v1 *config_head = + config_head_from_resource(config_head_resource); + if (config_head == NULL) { + return; + } + + // Mode can be NULL if the output uses a custom mode (in which case we + // expose a virtual mode with user data set to NULL). + struct wlr_output_mode *mode = mode_from_resource(mode_resource); + struct wlr_output *output = config_head->state.output; + + bool found = mode == NULL; + struct wlr_output_mode *m; + wl_list_for_each(m, &output->modes, link) { + if (mode == m) { + found = true; + break; + } + } + + if (!found) { + wl_resource_post_error(config_head_resource, + ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_MODE, + "mode doesn't belong to head"); + return; + } + + config_head->state.mode = mode; + if (mode != NULL) { + config_head->state.custom_mode.width = 0; + config_head->state.custom_mode.height = 0; + config_head->state.custom_mode.refresh = 0; + } +} + +static void config_head_handle_set_custom_mode(struct wl_client *client, + struct wl_resource *config_head_resource, int32_t width, int32_t height, + int32_t refresh) { + struct wlr_output_configuration_head_v1 *config_head = + config_head_from_resource(config_head_resource); + if (config_head == NULL) { + return; + } + + if (width <= 0 || height <= 0 || refresh < 0) { + wl_resource_post_error(config_head_resource, + ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_CUSTOM_MODE, + "invalid custom mode"); + return; + } + + config_head->state.mode = NULL; + config_head->state.custom_mode.width = width; + config_head->state.custom_mode.height = height; + config_head->state.custom_mode.refresh = refresh; +} + +static void config_head_handle_set_position(struct wl_client *client, + struct wl_resource *config_head_resource, int32_t x, int32_t y) { + struct wlr_output_configuration_head_v1 *config_head = + config_head_from_resource(config_head_resource); + if (config_head == NULL) { + return; + } + + config_head->state.x = x; + config_head->state.y = y; +} + +static void config_head_handle_set_transform(struct wl_client *client, + struct wl_resource *config_head_resource, int32_t transform) { + struct wlr_output_configuration_head_v1 *config_head = + config_head_from_resource(config_head_resource); + if (config_head == NULL) { + return; + } + + if (transform < WL_OUTPUT_TRANSFORM_NORMAL || + transform > WL_OUTPUT_TRANSFORM_FLIPPED_270) { + wl_resource_post_error(config_head_resource, + ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_TRANSFORM, + "invalid transform"); + return; + } + + config_head->state.transform = transform; +} + +static void config_head_handle_set_scale(struct wl_client *client, + struct wl_resource *config_head_resource, wl_fixed_t scale_fixed) { + struct wlr_output_configuration_head_v1 *config_head = + config_head_from_resource(config_head_resource); + if (config_head == NULL) { + return; + } + + float scale = wl_fixed_to_double(scale_fixed); + if (scale <= 0) { + wl_resource_post_error(config_head_resource, + ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_SCALE, + "invalid scale"); + return; + } + + config_head->state.scale = scale; +} + +static void config_head_handle_set_adaptive_sync(struct wl_client *client, + struct wl_resource *config_head_resource, uint32_t state) { + struct wlr_output_configuration_head_v1 *config_head = + config_head_from_resource(config_head_resource); + if (config_head == NULL) { + return; + } + + switch (state) { + case ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENABLED: + config_head->state.adaptive_sync_enabled = true; + break; + case ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_DISABLED: + config_head->state.adaptive_sync_enabled = false; + break; + default: + wl_resource_post_error(config_head_resource, + ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_ADAPTIVE_SYNC_STATE, + "client requested invalid adaptive sync state %ul", state); + break; + } +} + +static const struct zwlr_output_configuration_head_v1_interface config_head_impl = { + .set_mode = config_head_handle_set_mode, + .set_custom_mode = config_head_handle_set_custom_mode, + .set_position = config_head_handle_set_position, + .set_transform = config_head_handle_set_transform, + .set_scale = config_head_handle_set_scale, + .set_adaptive_sync = config_head_handle_set_adaptive_sync, +}; + +static void config_head_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_output_configuration_head_v1 *config_head = + config_head_from_resource(resource); + config_head_destroy(config_head); +} + + +static const struct zwlr_output_configuration_v1_interface config_impl; + +// Can return NULL if the config has been used +static struct wlr_output_configuration_v1 *config_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_output_configuration_v1_interface, &config_impl)); + return wl_resource_get_user_data(resource); +} + +// Checks that the head is unconfigured (ie. no enable_head/disable_head request +// has yet been sent for this head), if not sends a protocol error. +static bool config_check_head_is_unconfigured( + struct wlr_output_configuration_v1 *config, struct wlr_output *output) { + struct wlr_output_configuration_head_v1 *head; + wl_list_for_each(head, &config->heads, link) { + if (head->state.output == output) { + wl_resource_post_error(config->resource, + ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ALREADY_CONFIGURED_HEAD, + "head has already been configured"); + return false; + } + } + return true; +} + +static void config_handle_enable_head(struct wl_client *client, + struct wl_resource *config_resource, uint32_t id, + struct wl_resource *head_resource) { + struct wlr_output_configuration_v1 *config = + config_from_resource(config_resource); + if (config == NULL || config->finalized) { + wl_resource_post_error(config_resource, + ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ALREADY_USED, + "configuration object has already been used"); + return; + } + struct wlr_output_head_v1 *head = head_from_resource(head_resource); + + // Create an inert resource if the head no longer exists + struct wlr_output_configuration_head_v1 *config_head = NULL; + if (head != NULL) { + if (!config_check_head_is_unconfigured(config, head->state.output)) { + return; + } + config_head = config_head_create(config, head->state.output); + if (config_head == NULL) { + wl_resource_post_no_memory(config_resource); + return; + } + config_head->state = head->state; + } + + uint32_t version = wl_resource_get_version(config_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwlr_output_configuration_head_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &config_head_impl, + config_head, config_head_handle_resource_destroy); + + if (config_head != NULL) { + config_head->resource = resource; + config_head->state.enabled = true; + } +} + +static void config_handle_disable_head(struct wl_client *client, + struct wl_resource *config_resource, + struct wl_resource *head_resource) { + struct wlr_output_configuration_v1 *config = + config_from_resource(config_resource); + if (config == NULL || config->finalized) { + wl_resource_post_error(config_resource, + ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ALREADY_USED, + "configuration object has already been used"); + return; + } + struct wlr_output_head_v1 *head = head_from_resource(head_resource); + if (head == NULL) { + return; + } + + if (!config_check_head_is_unconfigured(config, head->state.output)) { + return; + } + struct wlr_output_configuration_head_v1 *config_head = + config_head_create(config, head->state.output); + if (config_head == NULL) { + wl_resource_post_no_memory(config_resource); + return; + } + + config_head->state.enabled = false; +} + +// Finalizes a configuration. This prevents the same config from being used +// multiple times. +static void config_finalize(struct wlr_output_configuration_v1 *config) { + if (config->finalized) { + return; + } + + // Destroy config head resources now, the client is forbidden to use them at + // this point anyway + struct wlr_output_configuration_head_v1 *config_head, *tmp; + wl_list_for_each_safe(config_head, tmp, &config->heads, link) { + // Resource is NULL if head has been disabled + if (config_head->resource != NULL) { + wl_resource_set_user_data(config_head->resource, NULL); + wl_resource_destroy(config_head->resource); + config_head->resource = NULL; + } + } + + config->finalized = true; +} + +// Destroys the config if serial is invalid +static bool config_validate_serial(struct wlr_output_configuration_v1 *config) { + if (config->serial != config->manager->serial) { + wlr_log(WLR_DEBUG, "Ignored configuration request: invalid serial"); + zwlr_output_configuration_v1_send_cancelled(config->resource); + wlr_output_configuration_v1_destroy(config); + return false; + } + return true; +} + +static void config_handle_apply(struct wl_client *client, + struct wl_resource *config_resource) { + struct wlr_output_configuration_v1 *config = + config_from_resource(config_resource); + if (config == NULL || config->finalized) { + wl_resource_post_error(config_resource, + ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ALREADY_USED, + "configuration object has already been used"); + return; + } + + config_finalize(config); + if (!config_validate_serial(config)) { + return; + } + + wl_signal_emit_mutable(&config->manager->events.apply, config); +} + +static void config_handle_test(struct wl_client *client, + struct wl_resource *config_resource) { + struct wlr_output_configuration_v1 *config = + config_from_resource(config_resource); + if (config == NULL || config->finalized) { + wl_resource_post_error(config_resource, + ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ALREADY_USED, + "configuration object has already been used"); + return; + } + + config_finalize(config); + if (!config_validate_serial(config)) { + return; + } + + wl_signal_emit_mutable(&config->manager->events.test, config); +} + +static void config_handle_destroy(struct wl_client *client, + struct wl_resource *config_resource) { + wl_resource_destroy(config_resource); +} + +static const struct zwlr_output_configuration_v1_interface config_impl = { + .enable_head = config_handle_enable_head, + .disable_head = config_handle_disable_head, + .apply = config_handle_apply, + .test = config_handle_test, + .destroy = config_handle_destroy, +}; + +static struct wlr_output_configuration_v1 *config_create(bool finalized) { + struct wlr_output_configuration_v1 *config = calloc(1, sizeof(*config)); + if (config == NULL) { + return NULL; + } + wl_list_init(&config->heads); + config->finalized = finalized; + return config; +} + +struct wlr_output_configuration_v1 *wlr_output_configuration_v1_create(void) { + return config_create(true); +} + +void wlr_output_configuration_v1_destroy( + struct wlr_output_configuration_v1 *config) { + if (config == NULL) { + return; + } + config_finalize(config); + if (config->resource != NULL) { + wl_resource_set_user_data(config->resource, NULL); // make inert + } + struct wlr_output_configuration_head_v1 *config_head, *tmp; + wl_list_for_each_safe(config_head, tmp, &config->heads, link) { + config_head_destroy(config_head); + } + free(config); +} + +static void config_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_output_configuration_v1 *config = config_from_resource(resource); + if (config == NULL) { + return; + } + if (config->finalized) { + config->resource = NULL; // we no longer own the config + } else { + wlr_output_configuration_v1_destroy(config); + } +} + +void wlr_output_configuration_v1_send_succeeded( + struct wlr_output_configuration_v1 *config) { + assert(!config->finished); + if (config->resource == NULL) { + return; // client destroyed the resource early + } + zwlr_output_configuration_v1_send_succeeded(config->resource); + config->finished = true; +} + +void wlr_output_configuration_v1_send_failed( + struct wlr_output_configuration_v1 *config) { + assert(!config->finished); + if (config->resource == NULL) { + return; // client destroyed the resource early + } + zwlr_output_configuration_v1_send_failed(config->resource); + config->finished = true; +} + + +static const struct zwlr_output_manager_v1_interface manager_impl; + +static struct wlr_output_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_output_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void manager_handle_create_configuration(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, uint32_t serial) { + struct wlr_output_manager_v1 *manager = + manager_from_resource(manager_resource); + + struct wlr_output_configuration_v1 *config = config_create(false); + if (config == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + config->manager = manager; + config->serial = serial; + + uint32_t version = wl_resource_get_version(manager_resource); + config->resource = wl_resource_create(client, + &zwlr_output_configuration_v1_interface, version, id); + if (config->resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(config->resource, &config_impl, + config, config_handle_resource_destroy); +} + +static void manager_handle_stop(struct wl_client *client, + struct wl_resource *manager_resource) { + zwlr_output_manager_v1_send_finished(manager_resource); + wl_resource_destroy(manager_resource); +} + +static const struct zwlr_output_manager_v1_interface manager_impl = { + .create_configuration = manager_handle_create_configuration, + .stop = manager_handle_stop, +}; + +static void manager_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void manager_send_head(struct wlr_output_manager_v1 *manager, + struct wlr_output_head_v1 *head, struct wl_resource *manager_resource); + +static void manager_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_output_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwlr_output_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, + manager_handle_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); + + struct wlr_output_head_v1 *head; + wl_list_for_each(head, &manager->heads, link) { + manager_send_head(manager, head, resource); + } + + zwlr_output_manager_v1_send_done(resource, manager->serial); +} + +static void manager_handle_display_destroy(struct wl_listener *listener, + void *data) { + struct wlr_output_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + struct wlr_output_head_v1 *head, *tmp; + wl_list_for_each_safe(head, tmp, &manager->heads, link) { + head_destroy(head); + } + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_output_manager_v1 *wlr_output_manager_v1_create( + struct wl_display *display) { + struct wlr_output_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + manager->display = display; + + wl_list_init(&manager->resources); + wl_list_init(&manager->heads); + wl_signal_init(&manager->events.destroy); + wl_signal_init(&manager->events.apply); + wl_signal_init(&manager->events.test); + + manager->global = wl_global_create(display, + &zwlr_output_manager_v1_interface, OUTPUT_MANAGER_VERSION, + manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +static struct wlr_output_configuration_head_v1 *configuration_get_head( + struct wlr_output_configuration_v1 *config, struct wlr_output *output) { + struct wlr_output_configuration_head_v1 *head; + wl_list_for_each(head, &config->heads, link) { + if (head->state.output == output) { + return head; + } + } + return NULL; +} + +static void send_mode_state(struct wl_resource *mode_resource, + struct wlr_output_mode *mode) { + zwlr_output_mode_v1_send_size(mode_resource, mode->width, mode->height); + if (mode->refresh > 0) { + zwlr_output_mode_v1_send_refresh(mode_resource, mode->refresh); + } + if (mode->preferred) { + zwlr_output_mode_v1_send_preferred(mode_resource); + } +} + +static void output_mode_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void output_mode_handle_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_output_mode_v1_interface output_mode_impl = { + .release = output_mode_handle_release, +}; + +static struct wl_resource *head_send_mode(struct wlr_output_head_v1 *head, + struct wl_resource *head_resource, struct wlr_output_mode *mode) { + struct wl_client *client = wl_resource_get_client(head_resource); + uint32_t version = wl_resource_get_version(head_resource); + struct wl_resource *mode_resource = + wl_resource_create(client, &zwlr_output_mode_v1_interface, version, 0); + if (mode_resource == NULL) { + wl_resource_post_no_memory(head_resource); + return NULL; + } + wl_resource_set_implementation(mode_resource, &output_mode_impl, mode, + output_mode_handle_resource_destroy); + wl_list_insert(&head->mode_resources, wl_resource_get_link(mode_resource)); + + zwlr_output_head_v1_send_mode(head_resource, mode_resource); + + if (mode != NULL) { + send_mode_state(mode_resource, mode); + } + + return mode_resource; +} + +// Sends new head state to a client. +static void head_send_state(struct wlr_output_head_v1 *head, + struct wl_resource *head_resource, uint32_t state) { + struct wl_client *client = wl_resource_get_client(head_resource); + + if (state & HEAD_STATE_ENABLED) { + zwlr_output_head_v1_send_enabled(head_resource, head->state.enabled); + // On enabling we send all current data since clients have not been + // notified about potential data changes while the head was disabled. + state = HEAD_STATE_ALL; + } + + if (!head->state.enabled) { + return; + } + + if (state & HEAD_STATE_MODE) { + bool found = false; + struct wl_resource *mode_resource; + wl_resource_for_each(mode_resource, &head->mode_resources) { + if (wl_resource_get_client(mode_resource) == client && + mode_from_resource(mode_resource) == head->state.mode) { + found = true; + break; + } + } + assert(found); + + if (head->state.mode == NULL) { + // Fake a single output mode if output doesn't support modes + struct wlr_output_mode virtual_mode = { + .width = head->state.custom_mode.width, + .height = head->state.custom_mode.height, + .refresh = head->state.custom_mode.refresh, + }; + send_mode_state(mode_resource, &virtual_mode); + } + + zwlr_output_head_v1_send_current_mode(head_resource, mode_resource); + } + + if (state & HEAD_STATE_POSITION) { + zwlr_output_head_v1_send_position(head_resource, + head->state.x, head->state.y); + } + + if (state & HEAD_STATE_TRANSFORM) { + zwlr_output_head_v1_send_transform(head_resource, + head->state.transform); + } + + if (state & HEAD_STATE_SCALE) { + zwlr_output_head_v1_send_scale(head_resource, + wl_fixed_from_double(head->state.scale)); + } + + if ((state & HEAD_STATE_ADAPTIVE_SYNC) && + wl_resource_get_version(head_resource) >= + ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_SINCE_VERSION) { + if (head->state.adaptive_sync_enabled) { + zwlr_output_head_v1_send_adaptive_sync(head_resource, + ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENABLED); + } else { + zwlr_output_head_v1_send_adaptive_sync(head_resource, + ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_DISABLED); + } + } +} + +static void head_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void head_handle_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_output_head_v1_interface head_impl = { + .release = head_handle_release, +}; + +static void manager_send_head(struct wlr_output_manager_v1 *manager, + struct wlr_output_head_v1 *head, struct wl_resource *manager_resource) { + struct wlr_output *output = head->state.output; + + struct wl_client *client = wl_resource_get_client(manager_resource); + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *head_resource = wl_resource_create(client, + &zwlr_output_head_v1_interface, version, 0); + if (head_resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(head_resource, &head_impl, head, + head_handle_resource_destroy); + wl_list_insert(&head->resources, wl_resource_get_link(head_resource)); + + zwlr_output_manager_v1_send_head(manager_resource, head_resource); + + zwlr_output_head_v1_send_name(head_resource, output->name); + zwlr_output_head_v1_send_description(head_resource, + output->description ? output->description : "Unknown"); + + if (output->phys_width > 0 && output->phys_height > 0) { + zwlr_output_head_v1_send_physical_size(head_resource, + output->phys_width, output->phys_height); + } + + if (version >= ZWLR_OUTPUT_HEAD_V1_MAKE_SINCE_VERSION && output->make != NULL) { + zwlr_output_head_v1_send_make(head_resource, output->make); + } + if (version >= ZWLR_OUTPUT_HEAD_V1_MODEL_SINCE_VERSION && output->model != NULL) { + zwlr_output_head_v1_send_model(head_resource, output->model); + } + if (version >= ZWLR_OUTPUT_HEAD_V1_SERIAL_NUMBER_SINCE_VERSION && output->serial != NULL) { + zwlr_output_head_v1_send_serial_number(head_resource, output->serial); + } + + struct wlr_output_mode *mode; + wl_list_for_each(mode, &output->modes, link) { + head_send_mode(head, head_resource, mode); + } + + if (output->current_mode == NULL) { + // Output doesn't have a fixed mode set. Send a virtual one. + head_send_mode(head, head_resource, NULL); + } + + head_send_state(head, head_resource, HEAD_STATE_ALL); +} + +// Compute state that has changed and sends it to all clients. Then writes the +// new state to the head. +static bool manager_update_head(struct wlr_output_manager_v1 *manager, + struct wlr_output_head_v1 *head, + struct wlr_output_head_v1_state *next) { + struct wlr_output_head_v1_state *current = &head->state; + + uint32_t state = 0; + if (current->enabled != next->enabled) { + state |= HEAD_STATE_ENABLED; + } + if (current->mode != next->mode) { + state |= HEAD_STATE_MODE; + } + if (current->custom_mode.width != next->custom_mode.width || + current->custom_mode.height != next->custom_mode.height || + current->custom_mode.refresh != next->custom_mode.refresh) { + state |= HEAD_STATE_MODE; + } + if (current->x != next->x || current->y != next->y) { + state |= HEAD_STATE_POSITION; + } + if (current->transform != next->transform) { + state |= HEAD_STATE_TRANSFORM; + } + if (current->scale != next->scale) { + state |= HEAD_STATE_SCALE; + } + if (current->adaptive_sync_enabled != next->adaptive_sync_enabled) { + state |= HEAD_STATE_ADAPTIVE_SYNC; + } + + // If a mode was added to wlr_output.modes we need to add the new mode + // to the wlr_output_head + struct wlr_output_mode *mode; + wl_list_for_each(mode, &head->state.output->modes, link) { + bool found = false; + struct wl_resource *mode_resource; + wl_resource_for_each(mode_resource, &head->mode_resources) { + if (mode_from_resource(mode_resource) == mode) { + found = true; + break; + } + } + if (!found) { + struct wl_resource *resource; + wl_resource_for_each(resource, &head->resources) { + head_send_mode(head, resource, mode); + } + } + } + + if (next->mode == NULL && !head_has_custom_mode_resources(head)) { + struct wl_resource *resource; + wl_resource_for_each(resource, &head->resources) { + head_send_mode(head, resource, NULL); + } + } else if (next->mode != NULL) { + head_destroy_custom_mode_resources(head); + } + + if (state != 0) { + *current = *next; + + struct wl_resource *resource; + wl_resource_for_each(resource, &head->resources) { + head_send_state(head, resource, state); + } + } + + return state != 0; +} + +void wlr_output_manager_v1_set_configuration( + struct wlr_output_manager_v1 *manager, + struct wlr_output_configuration_v1 *config) { + bool changed = manager->current_configuration_dirty; + + // Either update or destroy existing heads + struct wlr_output_head_v1 *existing_head, *head_tmp; + wl_list_for_each_safe(existing_head, head_tmp, &manager->heads, link) { + struct wlr_output_configuration_head_v1 *updated_head = + configuration_get_head(config, existing_head->state.output); + if (updated_head != NULL) { + changed |= manager_update_head(manager, + existing_head, &updated_head->state); + config_head_destroy(updated_head); + } else { + head_destroy(existing_head); + changed = true; + } + } + + // Heads remaining in `config` are new heads + + // Move new heads to current config + struct wlr_output_configuration_head_v1 *config_head, *config_head_tmp; + wl_list_for_each_safe(config_head, config_head_tmp, &config->heads, link) { + struct wlr_output_head_v1 *head = + head_create(manager, config_head->state.output); + if (head == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + continue; + } + + head->state = config_head->state; + + struct wl_resource *manager_resource; + wl_resource_for_each(manager_resource, &manager->resources) { + manager_send_head(manager, head, manager_resource); + } + + changed = true; + } + + wlr_output_configuration_v1_destroy(config); + + if (!changed) { + return; + } + + manager->serial = wl_display_next_serial(manager->display); + struct wl_resource *manager_resource; + wl_resource_for_each(manager_resource, &manager->resources) { + zwlr_output_manager_v1_send_done(manager_resource, + manager->serial); + } + manager->current_configuration_dirty = false; +} + +void wlr_output_head_v1_state_apply( + const struct wlr_output_head_v1_state *head_state, + struct wlr_output_state *output_state) { + wlr_output_state_set_enabled(output_state, head_state->enabled); + + if (!head_state->enabled) { + return; + } + + if (head_state->mode != NULL) { + wlr_output_state_set_mode(output_state, head_state->mode); + } else { + wlr_output_state_set_custom_mode(output_state, + head_state->custom_mode.width, + head_state->custom_mode.height, + head_state->custom_mode.refresh); + } + + wlr_output_state_set_scale(output_state, head_state->scale); + wlr_output_state_set_transform(output_state, head_state->transform); + wlr_output_state_set_adaptive_sync_enabled(output_state, + head_state->adaptive_sync_enabled); +} diff --git a/types/wlr_output_power_management_v1.c b/types/wlr_output_power_management_v1.c new file mode 100644 index 0000000..763d0d6 --- /dev/null +++ b/types/wlr_output_power_management_v1.c @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wlr-output-power-management-unstable-v1-protocol.h" + +#define OUTPUT_POWER_MANAGER_V1_VERSION 1 + +static void output_power_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void output_power_destroy(struct wlr_output_power_v1 *output_power) { + if (output_power == NULL) { + return; + } + wl_resource_set_user_data(output_power->resource, NULL); + wl_list_remove(&output_power->output_destroy_listener.link); + wl_list_remove(&output_power->output_commit_listener.link); + wl_list_remove(&output_power->link); + free(output_power); +} + +static const struct zwlr_output_power_v1_interface output_power_impl; + +static struct wlr_output_power_v1 *output_power_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwlr_output_power_v1_interface, + &output_power_impl)); + return wl_resource_get_user_data(resource); +} + +static void output_power_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_output_power_v1 *output_power = + output_power_from_resource(resource); + output_power_destroy(output_power); +} + +static void output_power_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_output_power_v1 *output_power = + wl_container_of(listener, output_power, output_destroy_listener); + output_power_destroy(output_power); +} + +static void output_power_v1_send_mode(struct wlr_output_power_v1 *output_power) { + enum zwlr_output_power_v1_mode mode; + + mode = output_power->output->enabled ? ZWLR_OUTPUT_POWER_V1_MODE_ON : + ZWLR_OUTPUT_POWER_V1_MODE_OFF; + zwlr_output_power_v1_send_mode(output_power->resource, mode); +} + +static void output_power_handle_output_commit(struct wl_listener *listener, + void *data) { + struct wlr_output_power_v1 *output_power = + wl_container_of(listener, output_power, output_commit_listener); + struct wlr_output_event_commit *event = data; + if (event->state->committed & WLR_OUTPUT_STATE_ENABLED) { + output_power_v1_send_mode(output_power); + } +} + +static void output_power_handle_set_mode(struct wl_client *client, + struct wl_resource *output_power_resource, + enum zwlr_output_power_v1_mode mode) { + struct wlr_output_power_v1 *output_power = + output_power_from_resource(output_power_resource); + if (output_power == NULL) { + return; + } + + switch (mode) { + case ZWLR_OUTPUT_POWER_V1_MODE_OFF: + case ZWLR_OUTPUT_POWER_V1_MODE_ON: + break; + default: + wlr_log(WLR_ERROR, "Invalid power mode %d", mode); + wl_resource_post_error(output_power_resource, + ZWLR_OUTPUT_POWER_V1_ERROR_INVALID_MODE, + "Invalid power mode"); + return; + } + + struct wlr_output_power_v1_set_mode_event event = { + .output = output_power->output, + .mode = mode, + }; + wl_signal_emit_mutable(&output_power->manager->events.set_mode, &event); +} + +static const struct zwlr_output_power_v1_interface output_power_impl = { + .destroy = output_power_handle_destroy, + .set_mode = output_power_handle_set_mode, +}; + +static const struct zwlr_output_power_manager_v1_interface + output_power_manager_impl; + +static struct wlr_output_power_manager_v1 *output_power_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_output_power_manager_v1_interface, &output_power_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void output_power_manager_get_output_power(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *output_resource) { + struct wlr_output_power_manager_v1 *manager = + output_power_manager_from_resource(manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + struct wlr_output_power_v1 *output_power = calloc(1, sizeof(*output_power)); + if (output_power == NULL) { + wl_client_post_no_memory(client); + return; + } + output_power->output = output; + output_power->manager = manager; + wl_list_init(&output_power->link); + + uint32_t version = wl_resource_get_version(manager_resource); + output_power->resource = wl_resource_create(client, + &zwlr_output_power_v1_interface, version, id); + if (output_power->resource == NULL) { + free(output_power); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(output_power->resource, &output_power_impl, + output_power, output_power_handle_resource_destroy); + + if (!output) { + wl_resource_set_user_data(output_power->resource, NULL); + zwlr_output_power_v1_send_failed(output_power->resource); + free(output_power); + return; + } + + wl_signal_add(&output->events.destroy, + &output_power->output_destroy_listener); + output_power->output_destroy_listener.notify = + output_power_handle_output_destroy; + wl_signal_add(&output->events.commit, + &output_power->output_commit_listener); + output_power->output_commit_listener.notify = + output_power_handle_output_commit; + + struct wlr_output_power_v1 *mgmt; + wl_list_for_each(mgmt, &manager->output_powers, link) { + if (mgmt->output == output) { + zwlr_output_power_v1_send_failed(output_power->resource); + output_power_destroy(output_power); + return; + } + } + + wl_list_insert(&manager->output_powers, &output_power->link); + output_power_v1_send_mode(output_power); +} + +static void output_power_manager_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwlr_output_power_manager_v1_interface + output_power_manager_impl = { + .get_output_power = output_power_manager_get_output_power, + .destroy = output_power_manager_destroy, +}; + +static void output_power_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_output_power_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwlr_output_power_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &output_power_manager_impl, + manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_output_power_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_output_power_manager_v1 *wlr_output_power_manager_v1_create( + struct wl_display *display) { + struct wlr_output_power_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + return NULL; + } + + manager->global = wl_global_create(display, + &zwlr_output_power_manager_v1_interface, + OUTPUT_POWER_MANAGER_V1_VERSION, manager, output_power_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.set_mode); + wl_signal_init(&manager->events.destroy); + wl_list_init(&manager->output_powers); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_pointer.c b/types/wlr_pointer.c new file mode 100644 index 0000000..150b901 --- /dev/null +++ b/types/wlr_pointer.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include +#include + +#include "interfaces/wlr_input_device.h" + +struct wlr_pointer *wlr_pointer_from_input_device( + struct wlr_input_device *input_device) { + assert(input_device->type == WLR_INPUT_DEVICE_POINTER); + return wl_container_of(input_device, (struct wlr_pointer *)NULL, base); +} + +void wlr_pointer_init(struct wlr_pointer *pointer, + const struct wlr_pointer_impl *impl, const char *name) { + *pointer = (struct wlr_pointer){ + .impl = impl, + }; + wlr_input_device_init(&pointer->base, WLR_INPUT_DEVICE_POINTER, name); + + wl_signal_init(&pointer->events.motion); + wl_signal_init(&pointer->events.motion_absolute); + wl_signal_init(&pointer->events.button); + wl_signal_init(&pointer->events.axis); + wl_signal_init(&pointer->events.frame); + wl_signal_init(&pointer->events.swipe_begin); + wl_signal_init(&pointer->events.swipe_update); + wl_signal_init(&pointer->events.swipe_end); + wl_signal_init(&pointer->events.pinch_begin); + wl_signal_init(&pointer->events.pinch_update); + wl_signal_init(&pointer->events.pinch_end); + wl_signal_init(&pointer->events.hold_begin); + wl_signal_init(&pointer->events.hold_end); +} + +void wlr_pointer_finish(struct wlr_pointer *pointer) { + wlr_input_device_finish(&pointer->base); + + free(pointer->output_name); +} diff --git a/types/wlr_pointer_constraints_v1.c b/types/wlr_pointer_constraints_v1.c new file mode 100644 index 0000000..f3c8fb7 --- /dev/null +++ b/types/wlr_pointer_constraints_v1.c @@ -0,0 +1,399 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct zwp_locked_pointer_v1_interface locked_pointer_impl; +static const struct zwp_confined_pointer_v1_interface confined_pointer_impl; +static const struct zwp_pointer_constraints_v1_interface pointer_constraints_impl; +static void pointer_constraint_destroy(struct wlr_pointer_constraint_v1 *constraint); + +static struct wlr_pointer_constraint_v1 *pointer_constraint_from_resource( + struct wl_resource *resource) { + assert( + wl_resource_instance_of( + resource, &zwp_confined_pointer_v1_interface, + &confined_pointer_impl) || + wl_resource_instance_of( + resource, &zwp_locked_pointer_v1_interface, + &locked_pointer_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_pointer_constraints_v1 *pointer_constraints_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_pointer_constraints_v1_interface, + &pointer_constraints_impl)); + return wl_resource_get_user_data(resource); +} + +static void resource_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void pointer_constraint_destroy(struct wlr_pointer_constraint_v1 *constraint) { + if (constraint == NULL) { + return; + } + + wlr_log(WLR_DEBUG, "destroying constraint %p", constraint); + + wl_signal_emit_mutable(&constraint->events.destroy, constraint); + + wl_resource_set_user_data(constraint->resource, NULL); + wlr_surface_synced_finish(&constraint->synced); + wl_list_remove(&constraint->link); + wl_list_remove(&constraint->surface_commit.link); + wl_list_remove(&constraint->surface_destroy.link); + wl_list_remove(&constraint->seat_destroy.link); + pixman_region32_fini(&constraint->current.region); + pixman_region32_fini(&constraint->pending.region); + pixman_region32_fini(&constraint->region); + free(constraint); +} + +static void pointer_constraint_destroy_resource(struct wl_resource *resource) { + struct wlr_pointer_constraint_v1 *constraint = + pointer_constraint_from_resource(resource); + + pointer_constraint_destroy(constraint); +} + +static void pointer_constraint_set_region( + struct wlr_pointer_constraint_v1 *constraint, + struct wl_resource *region_resource) { + pixman_region32_clear(&constraint->pending.region); + + if (region_resource) { + const pixman_region32_t *region = wlr_region_from_resource(region_resource); + pixman_region32_copy(&constraint->pending.region, region); + } + + constraint->pending.committed |= WLR_POINTER_CONSTRAINT_V1_STATE_REGION; +} + +static void pointer_constraint_handle_set_region(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *region_resource) { + struct wlr_pointer_constraint_v1 *constraint = + pointer_constraint_from_resource(resource); + if (constraint == NULL) { + return; + } + + pointer_constraint_set_region(constraint, region_resource); +} + +static void pointer_constraint_set_cursor_position_hint(struct wl_client *client, + struct wl_resource *resource, wl_fixed_t x, wl_fixed_t y) { + struct wlr_pointer_constraint_v1 *constraint = + pointer_constraint_from_resource(resource); + if (constraint == NULL) { + return; + } + + constraint->pending.cursor_hint.enabled = true; + constraint->pending.cursor_hint.x = wl_fixed_to_double(x); + constraint->pending.cursor_hint.y = wl_fixed_to_double(y); + constraint->pending.committed |= WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT; +} + +static void pointer_constraint_commit( + struct wlr_pointer_constraint_v1 *constraint) { + pixman_region32_clear(&constraint->region); + if (pixman_region32_not_empty(&constraint->current.region)) { + pixman_region32_intersect(&constraint->region, + &constraint->surface->input_region, &constraint->current.region); + } else { + pixman_region32_copy(&constraint->region, + &constraint->surface->input_region); + } + + if (constraint->current.committed & WLR_POINTER_CONSTRAINT_V1_STATE_REGION) { + wl_signal_emit_mutable(&constraint->events.set_region, NULL); + } +} + +static void handle_surface_commit(struct wl_listener *listener, void *data) { + struct wlr_pointer_constraint_v1 *constraint = + wl_container_of(listener, constraint, surface_commit); + + pointer_constraint_commit(constraint); +} + +static void handle_surface_destroy(struct wl_listener *listener, void *data) { + struct wlr_pointer_constraint_v1 *constraint = + wl_container_of(listener, constraint, surface_destroy); + + pointer_constraint_destroy(constraint); +} + +static void handle_seat_destroy(struct wl_listener *listener, void *data) { + struct wlr_pointer_constraint_v1 *constraint = + wl_container_of(listener, constraint, seat_destroy); + + pointer_constraint_destroy(constraint); +} + +static const struct zwp_confined_pointer_v1_interface confined_pointer_impl = { + .destroy = resource_destroy, + .set_region = pointer_constraint_handle_set_region, +}; + +static const struct zwp_locked_pointer_v1_interface locked_pointer_impl = { + .destroy = resource_destroy, + .set_region = pointer_constraint_handle_set_region, + .set_cursor_position_hint = pointer_constraint_set_cursor_position_hint, +}; + +static void surface_synced_init_state(void *_state) { + struct wlr_pointer_constraint_v1_state *state = _state; + pixman_region32_init(&state->region); +} + +static void surface_synced_finish_state(void *_state) { + struct wlr_pointer_constraint_v1_state *state = _state; + pixman_region32_fini(&state->region); +} + +static void surface_synced_move_state(void *_dst, void *_src) { + struct wlr_pointer_constraint_v1_state *dst = _dst, *src = _src; + + if (src->committed & WLR_POINTER_CONSTRAINT_V1_STATE_REGION) { + pixman_region32_copy(&dst->region, &src->region); + } + if (src->committed & WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT) { + dst->cursor_hint = src->cursor_hint; + } + + dst->committed = src->committed; + src->committed = 0; +} + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_pointer_constraint_v1_state), + .init_state = surface_synced_init_state, + .finish_state = surface_synced_finish_state, + .move_state = surface_synced_move_state, +}; + +static void pointer_constraint_create(struct wl_client *client, + struct wl_resource *pointer_constraints_resource, uint32_t id, + struct wl_resource *surface_resource, + struct wl_resource *pointer_resource, + struct wl_resource *region_resource, + enum zwp_pointer_constraints_v1_lifetime lifetime, + enum wlr_pointer_constraint_v1_type type) { + struct wlr_pointer_constraints_v1 *pointer_constraints = + pointer_constraints_from_resource(pointer_constraints_resource); + + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_pointer_resource(pointer_resource); + + uint32_t version = wl_resource_get_version(pointer_constraints_resource); + + bool locked_pointer = type == WLR_POINTER_CONSTRAINT_V1_LOCKED; + + struct wl_resource *resource = locked_pointer ? + wl_resource_create(client, &zwp_locked_pointer_v1_interface, version, id) : + wl_resource_create(client, &zwp_confined_pointer_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + void *impl = locked_pointer ? + (void *)&locked_pointer_impl : (void *)&confined_pointer_impl; + wl_resource_set_implementation(resource, impl, NULL, + pointer_constraint_destroy_resource); + + if (seat_client == NULL) { + // Leave the resource inert + return; + } + + struct wlr_seat *seat = seat_client->seat; + + if (wlr_pointer_constraints_v1_constraint_for_surface(pointer_constraints, + surface, seat)) { + wl_resource_destroy(resource); + wl_resource_post_error(pointer_constraints_resource, + ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED, + "a pointer constraint with a wl_pointer of the same wl_seat" + " is already on this surface"); + return; + } + + struct wlr_pointer_constraint_v1 *constraint = calloc(1, sizeof(*constraint)); + if (constraint == NULL) { + wl_resource_destroy(resource); + wl_client_post_no_memory(client); + return; + } + + if (!wlr_surface_synced_init(&constraint->synced, surface, + &surface_synced_impl, &constraint->pending, &constraint->current)) { + free(constraint); + wl_resource_destroy(resource); + wl_client_post_no_memory(client); + return; + } + + constraint->resource = resource; + constraint->surface = surface; + constraint->seat = seat; + constraint->lifetime = lifetime; + constraint->type = type; + constraint->pointer_constraints = pointer_constraints; + + wl_signal_init(&constraint->events.set_region); + wl_signal_init(&constraint->events.destroy); + + pixman_region32_init(&constraint->region); + + pixman_region32_init(&constraint->pending.region); + pixman_region32_init(&constraint->current.region); + + pointer_constraint_set_region(constraint, region_resource); + pointer_constraint_commit(constraint); + + constraint->surface_commit.notify = handle_surface_commit; + wl_signal_add(&surface->events.commit, &constraint->surface_commit); + + constraint->surface_destroy.notify = handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &constraint->surface_destroy); + + constraint->seat_destroy.notify = handle_seat_destroy; + wl_signal_add(&seat->events.destroy, &constraint->seat_destroy); + + wl_resource_set_user_data(resource, constraint); + + wlr_log(WLR_DEBUG, "new %s_pointer %p (res %p)", + locked_pointer ? "locked" : "confined", + constraint, constraint->resource); + + wl_list_insert(&pointer_constraints->constraints, &constraint->link); + + wl_signal_emit_mutable(&pointer_constraints->events.new_constraint, + constraint); +} + +static void pointer_constraints_lock_pointer(struct wl_client *client, + struct wl_resource *cons_resource, uint32_t id, + struct wl_resource *surface, struct wl_resource *pointer, + struct wl_resource *region, enum zwp_pointer_constraints_v1_lifetime lifetime) { + pointer_constraint_create(client, cons_resource, id, surface, pointer, + region, lifetime, WLR_POINTER_CONSTRAINT_V1_LOCKED); +} + +static void pointer_constraints_confine_pointer(struct wl_client *client, + struct wl_resource *cons_resource, uint32_t id, + struct wl_resource *surface, struct wl_resource *pointer, + struct wl_resource *region, + enum zwp_pointer_constraints_v1_lifetime lifetime) { + pointer_constraint_create(client, cons_resource, id, surface, pointer, + region, lifetime, WLR_POINTER_CONSTRAINT_V1_CONFINED); +} + +static const struct zwp_pointer_constraints_v1_interface + pointer_constraints_impl = { + .destroy = resource_destroy, + .lock_pointer = pointer_constraints_lock_pointer, + .confine_pointer = pointer_constraints_confine_pointer, +}; + +static void pointer_constraints_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_pointer_constraints_v1 *pointer_constraints = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwp_pointer_constraints_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &pointer_constraints_impl, + pointer_constraints, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_pointer_constraints_v1 *pointer_constraints = + wl_container_of(listener, pointer_constraints, display_destroy); + wl_list_remove(&pointer_constraints->display_destroy.link); + wl_global_destroy(pointer_constraints->global); + free(pointer_constraints); +} + +struct wlr_pointer_constraints_v1 *wlr_pointer_constraints_v1_create( + struct wl_display *display) { + struct wlr_pointer_constraints_v1 *pointer_constraints = + calloc(1, sizeof(*pointer_constraints)); + if (!pointer_constraints) { + return NULL; + } + + struct wl_global *wl_global = wl_global_create(display, + &zwp_pointer_constraints_v1_interface, 1, pointer_constraints, + pointer_constraints_bind); + if (!wl_global) { + free(pointer_constraints); + return NULL; + } + pointer_constraints->global = wl_global; + + wl_list_init(&pointer_constraints->constraints); + wl_signal_init(&pointer_constraints->events.new_constraint); + + pointer_constraints->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, + &pointer_constraints->display_destroy); + + return pointer_constraints; +} + +struct wlr_pointer_constraint_v1 * + wlr_pointer_constraints_v1_constraint_for_surface( + struct wlr_pointer_constraints_v1 *pointer_constraints, + struct wlr_surface *surface, struct wlr_seat *seat) { + struct wlr_pointer_constraint_v1 *constraint; + wl_list_for_each(constraint, &pointer_constraints->constraints, link) { + if (constraint->surface == surface && constraint->seat == seat) { + return constraint; + } + } + + return NULL; +} + +void wlr_pointer_constraint_v1_send_activated( + struct wlr_pointer_constraint_v1 *constraint) { + wlr_log(WLR_DEBUG, "constrained %p", constraint); + if (constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) { + zwp_locked_pointer_v1_send_locked(constraint->resource); + } else { + zwp_confined_pointer_v1_send_confined(constraint->resource); + } +} + +void wlr_pointer_constraint_v1_send_deactivated( + struct wlr_pointer_constraint_v1 *constraint) { + wlr_log(WLR_DEBUG, "unconstrained %p", constraint); + if (constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) { + zwp_locked_pointer_v1_send_unlocked(constraint->resource); + } else { + zwp_confined_pointer_v1_send_unconfined(constraint->resource); + } + + if (constraint->lifetime == + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT) { + pointer_constraint_destroy(constraint); + } +} diff --git a/types/wlr_pointer_gestures_v1.c b/types/wlr_pointer_gestures_v1.c new file mode 100644 index 0000000..0763f3a --- /dev/null +++ b/types/wlr_pointer_gestures_v1.c @@ -0,0 +1,427 @@ +#include +#include +#include +#include +#include +#include +#include +#include "pointer-gestures-unstable-v1-protocol.h" + +#define POINTER_GESTURES_VERSION 3 + +static void resource_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void resource_remove_from_list(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static const struct zwp_pointer_gestures_v1_interface gestures_impl; +static const struct zwp_pointer_gesture_swipe_v1_interface swipe_impl; +static const struct zwp_pointer_gesture_pinch_v1_interface pinch_impl; +static const struct zwp_pointer_gesture_hold_v1_interface hold_impl; + +static struct wlr_pointer_gestures_v1 *pointer_gestures_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_pointer_gestures_v1_interface, &gestures_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_seat *seat_from_pointer_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_pointer_gesture_swipe_v1_interface, &swipe_impl) || + wl_resource_instance_of(resource, + &zwp_pointer_gesture_pinch_v1_interface, &pinch_impl) || + wl_resource_instance_of(resource, + &zwp_pointer_gesture_hold_v1_interface, &hold_impl)); + return wl_resource_get_user_data(resource); +} + +void wlr_pointer_gestures_v1_send_swipe_begin( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + uint32_t fingers) { + struct wlr_surface *focus = seat->pointer_state.focused_surface; + struct wlr_seat_client *focus_seat_client = + seat->pointer_state.focused_client; + if (focus == NULL || focus_seat_client == NULL) { + return; + } + + struct wl_client *focus_client = focus_seat_client->client; + uint32_t serial = wlr_seat_client_next_serial(focus_seat_client); + + struct wl_resource *gesture; + wl_resource_for_each(gesture, &gestures->swipes) { + struct wlr_seat *gesture_seat = seat_from_pointer_resource(gesture); + struct wl_client *gesture_client = wl_resource_get_client(gesture); + if (gesture_seat != seat || gesture_client != focus_client) { + continue; + } + zwp_pointer_gesture_swipe_v1_send_begin(gesture, serial, + time_msec, focus->resource, fingers); + } +} + +void wlr_pointer_gestures_v1_send_swipe_update( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + double dx, + double dy) { + struct wlr_surface *focus = seat->pointer_state.focused_surface; + struct wlr_seat_client *focus_seat_client = + seat->pointer_state.focused_client; + if (focus == NULL || focus_seat_client == NULL) { + return; + } + + struct wl_client *focus_client = focus_seat_client->client; + + struct wl_resource *gesture; + wl_resource_for_each(gesture, &gestures->swipes) { + struct wlr_seat *gesture_seat = seat_from_pointer_resource(gesture); + struct wl_client *gesture_client = wl_resource_get_client(gesture); + if (gesture_seat != seat || gesture_client != focus_client) { + continue; + } + zwp_pointer_gesture_swipe_v1_send_update(gesture, time_msec, + wl_fixed_from_double(dx), wl_fixed_from_double(dy)); + } +} + +void wlr_pointer_gestures_v1_send_swipe_end( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + bool cancelled) { + struct wlr_surface *focus = seat->pointer_state.focused_surface; + struct wlr_seat_client *focus_seat_client = + seat->pointer_state.focused_client; + if (focus == NULL || focus_seat_client == NULL) { + return; + } + + struct wl_client *focus_client = focus_seat_client->client; + uint32_t serial = wlr_seat_client_next_serial(focus_seat_client); + + struct wl_resource *gesture; + wl_resource_for_each(gesture, &gestures->swipes) { + struct wlr_seat *gesture_seat = seat_from_pointer_resource(gesture); + struct wl_client *gesture_client = wl_resource_get_client(gesture); + if (gesture_seat != seat || gesture_client != focus_client) { + continue; + } + zwp_pointer_gesture_swipe_v1_send_end(gesture, serial, + time_msec, cancelled); + } +} + +static const struct zwp_pointer_gesture_swipe_v1_interface swipe_impl = { + .destroy = resource_handle_destroy, +}; + +static void get_swipe_gesture(struct wl_client *client, + struct wl_resource *gestures_resource, + uint32_t id, + struct wl_resource *pointer_resource) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_pointer_resource(pointer_resource); + struct wlr_seat *seat = NULL; + + if (seat_client != NULL) { + seat = seat_client->seat; + } + // Otherwise, the resource will be inert + // (NULL seat, so all seat comparisons will fail) + + struct wlr_pointer_gestures_v1 *gestures = + pointer_gestures_from_resource(gestures_resource); + + struct wl_resource *gesture = wl_resource_create(client, + &zwp_pointer_gesture_swipe_v1_interface, + wl_resource_get_version(gestures_resource), + id); + if (gesture == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(gesture, &swipe_impl, seat, + resource_remove_from_list); + wl_list_insert(&gestures->swipes, wl_resource_get_link(gesture)); +} + +void wlr_pointer_gestures_v1_send_pinch_begin( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + uint32_t fingers) { + struct wlr_surface *focus = seat->pointer_state.focused_surface; + struct wlr_seat_client *focus_seat_client = + seat->pointer_state.focused_client; + if (focus == NULL || focus_seat_client == NULL) { + return; + } + + struct wl_client *focus_client = focus_seat_client->client; + uint32_t serial = wlr_seat_client_next_serial(focus_seat_client); + + struct wl_resource *gesture; + wl_resource_for_each(gesture, &gestures->pinches) { + struct wlr_seat *gesture_seat = seat_from_pointer_resource(gesture); + struct wl_client *gesture_client = wl_resource_get_client(gesture); + if (gesture_seat != seat || gesture_client != focus_client) { + continue; + } + zwp_pointer_gesture_pinch_v1_send_begin(gesture, serial, + time_msec, focus->resource, fingers); + } +} + +void wlr_pointer_gestures_v1_send_pinch_update( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + double dx, + double dy, + double scale, + double rotation) { + struct wlr_surface *focus = seat->pointer_state.focused_surface; + struct wlr_seat_client *focus_seat_client = + seat->pointer_state.focused_client; + if (focus == NULL || focus_seat_client == NULL) { + return; + } + + struct wl_client *focus_client = focus_seat_client->client; + + struct wl_resource *gesture; + wl_resource_for_each(gesture, &gestures->pinches) { + struct wlr_seat *gesture_seat = seat_from_pointer_resource(gesture); + struct wl_client *gesture_client = wl_resource_get_client(gesture); + if (gesture_seat != seat || gesture_client != focus_client) { + continue; + } + zwp_pointer_gesture_pinch_v1_send_update(gesture, time_msec, + wl_fixed_from_double(dx), wl_fixed_from_double(dy), + wl_fixed_from_double(scale), + wl_fixed_from_double(rotation)); + } +} + +void wlr_pointer_gestures_v1_send_pinch_end( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + bool cancelled) { + struct wlr_surface *focus = seat->pointer_state.focused_surface; + struct wlr_seat_client *focus_seat_client = + seat->pointer_state.focused_client; + if (focus == NULL || focus_seat_client == NULL) { + return; + } + + struct wl_client *focus_client = focus_seat_client->client; + uint32_t serial = wlr_seat_client_next_serial(focus_seat_client); + + struct wl_resource *gesture; + wl_resource_for_each(gesture, &gestures->pinches) { + struct wlr_seat *gesture_seat = seat_from_pointer_resource(gesture); + struct wl_client *gesture_client = wl_resource_get_client(gesture); + if (gesture_seat != seat || gesture_client != focus_client) { + continue; + } + zwp_pointer_gesture_pinch_v1_send_end(gesture, serial, + time_msec, cancelled); + } +} + +static const struct zwp_pointer_gesture_pinch_v1_interface pinch_impl = { + .destroy = resource_handle_destroy, +}; + +static void get_pinch_gesture(struct wl_client *client, + struct wl_resource *gestures_resource, + uint32_t id, + struct wl_resource *pointer_resource) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_pointer_resource(pointer_resource); + struct wlr_seat *seat = NULL; + + if (seat_client != NULL) { + seat = seat_client->seat; + } + // Otherwise, the resource will be inert + // (NULL seat, so all seat comparisons will fail) + + struct wlr_pointer_gestures_v1 *gestures = + pointer_gestures_from_resource(gestures_resource); + + struct wl_resource *gesture = wl_resource_create(client, + &zwp_pointer_gesture_pinch_v1_interface, + wl_resource_get_version(gestures_resource), + id); + if (gesture == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(gesture, &pinch_impl, seat, + resource_remove_from_list); + wl_list_insert(&gestures->pinches, wl_resource_get_link(gesture)); +} + +static void pointer_gestures_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +void wlr_pointer_gestures_v1_send_hold_begin( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + uint32_t fingers) { + struct wlr_surface *focus = seat->pointer_state.focused_surface; + struct wlr_seat_client *focus_seat_client = + seat->pointer_state.focused_client; + if (focus == NULL || focus_seat_client == NULL) { + return; + } + + struct wl_client *focus_client = focus_seat_client->client; + uint32_t serial = wlr_seat_client_next_serial(focus_seat_client); + + struct wl_resource *gesture; + wl_resource_for_each(gesture, &gestures->holds) { + struct wlr_seat *gesture_seat = seat_from_pointer_resource(gesture); + struct wl_client *gesture_client = wl_resource_get_client(gesture); + if (gesture_seat != seat || gesture_client != focus_client) { + continue; + } + zwp_pointer_gesture_hold_v1_send_begin(gesture, serial, + time_msec, focus->resource, fingers); + } +} + +void wlr_pointer_gestures_v1_send_hold_end( + struct wlr_pointer_gestures_v1 *gestures, + struct wlr_seat *seat, + uint32_t time_msec, + bool cancelled) { + struct wlr_surface *focus = seat->pointer_state.focused_surface; + struct wlr_seat_client *focus_seat_client = + seat->pointer_state.focused_client; + if (focus == NULL || focus_seat_client == NULL) { + return; + } + + struct wl_client *focus_client = focus_seat_client->client; + uint32_t serial = wlr_seat_client_next_serial(focus_seat_client); + + struct wl_resource *gesture; + wl_resource_for_each(gesture, &gestures->holds) { + struct wlr_seat *gesture_seat = seat_from_pointer_resource(gesture); + struct wl_client *gesture_client = wl_resource_get_client(gesture); + if (gesture_seat != seat || gesture_client != focus_client) { + continue; + } + zwp_pointer_gesture_hold_v1_send_end(gesture, serial, + time_msec, cancelled); + } +} + +static const struct zwp_pointer_gesture_hold_v1_interface hold_impl = { + .destroy = resource_handle_destroy, +}; + +static void get_hold_gesture(struct wl_client *client, + struct wl_resource *gestures_resource, + uint32_t id, + struct wl_resource *pointer_resource) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_pointer_resource(pointer_resource); + struct wlr_seat *seat = NULL; + + if (seat_client != NULL) { + seat = seat_client->seat; + } + // Otherwise, the resource will be inert + // (NULL seat, so all seat comparisons will fail) + + struct wlr_pointer_gestures_v1 *gestures = + pointer_gestures_from_resource(gestures_resource); + + struct wl_resource *gesture = wl_resource_create(client, + &zwp_pointer_gesture_hold_v1_interface, + wl_resource_get_version(gestures_resource), + id); + if (gesture == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(gesture, &hold_impl, seat, + resource_remove_from_list); + wl_list_insert(&gestures->holds, wl_resource_get_link(gesture)); +} + +static const struct zwp_pointer_gestures_v1_interface gestures_impl = { + .get_swipe_gesture = get_swipe_gesture, + .get_pinch_gesture = get_pinch_gesture, + .release = pointer_gestures_release, + .get_hold_gesture = get_hold_gesture, +}; + +static void pointer_gestures_v1_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_pointer_gestures_v1 *gestures = data; + + struct wl_resource *resource = wl_resource_create(wl_client, + &zwp_pointer_gestures_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_resource_set_implementation(resource, + &gestures_impl, gestures, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_pointer_gestures_v1 *gestures = + wl_container_of(listener, gestures, display_destroy); + wl_list_remove(&gestures->display_destroy.link); + wl_global_destroy(gestures->global); + free(gestures); +} + +struct wlr_pointer_gestures_v1 *wlr_pointer_gestures_v1_create( + struct wl_display *display) { + struct wlr_pointer_gestures_v1 *gestures = calloc(1, sizeof(*gestures)); + if (!gestures) { + return NULL; + } + + wl_list_init(&gestures->swipes); + wl_list_init(&gestures->pinches); + wl_list_init(&gestures->holds); + + gestures->global = wl_global_create(display, + &zwp_pointer_gestures_v1_interface, POINTER_GESTURES_VERSION, + gestures, pointer_gestures_v1_bind); + if (gestures->global == NULL) { + free(gestures); + return NULL; + } + + gestures->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &gestures->display_destroy); + + return gestures; +} diff --git a/types/wlr_presentation_time.c b/types/wlr_presentation_time.c new file mode 100644 index 0000000..35b960d --- /dev/null +++ b/types/wlr_presentation_time.c @@ -0,0 +1,329 @@ +#include +#include +#include +#include +#include +#include +#include +#include "presentation-time-protocol.h" + +#define PRESENTATION_VERSION 1 + +struct wlr_presentation_surface_state { + struct wlr_presentation_feedback *feedback; +}; + +struct wlr_presentation_surface { + struct wlr_presentation_surface_state current, pending; + + struct wlr_addon addon; // wlr_surface.addons + struct wlr_surface_synced synced; +}; + +static void feedback_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void feedback_resource_send_presented( + struct wl_resource *feedback_resource, + const struct wlr_presentation_event *event) { + struct wl_client *client = wl_resource_get_client(feedback_resource); + struct wl_resource *output_resource; + wl_resource_for_each(output_resource, &event->output->resources) { + if (wl_resource_get_client(output_resource) == client) { + wp_presentation_feedback_send_sync_output(feedback_resource, + output_resource); + } + } + + uint32_t tv_sec_hi = event->tv_sec >> 32; + uint32_t tv_sec_lo = event->tv_sec & 0xFFFFFFFF; + uint32_t seq_hi = event->seq >> 32; + uint32_t seq_lo = event->seq & 0xFFFFFFFF; + wp_presentation_feedback_send_presented(feedback_resource, + tv_sec_hi, tv_sec_lo, event->tv_nsec, event->refresh, + seq_hi, seq_lo, event->flags); + + wl_resource_destroy(feedback_resource); +} + +static void feedback_resource_send_discarded( + struct wl_resource *feedback_resource) { + wp_presentation_feedback_send_discarded(feedback_resource); + wl_resource_destroy(feedback_resource); +} + +static const struct wlr_addon_interface presentation_surface_addon_impl; + +static void presentation_surface_addon_destroy(struct wlr_addon *addon) { + struct wlr_presentation_surface *p_surface = + wl_container_of(addon, p_surface, addon); + + wlr_addon_finish(addon); + wlr_surface_synced_finish(&p_surface->synced); + + free(p_surface); +} + +static const struct wlr_addon_interface presentation_surface_addon_impl = { + .name = "wlr_presentation_surface", + .destroy = presentation_surface_addon_destroy, +}; + +static void surface_synced_finish_state(void *_state) { + struct wlr_presentation_surface_state *state = _state; + wlr_presentation_feedback_destroy(state->feedback); +} + +static void surface_synced_move_state(void *_dst, void *_src) { + struct wlr_presentation_surface_state *dst = _dst, *src = _src; + surface_synced_finish_state(dst); + dst->feedback = src->feedback; + src->feedback = NULL; +} + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_presentation_surface_state), + .finish_state = surface_synced_finish_state, + .move_state = surface_synced_move_state, +}; + +static void presentation_handle_feedback(struct wl_client *client, + struct wl_resource *presentation_resource, + struct wl_resource *surface_resource, uint32_t id) { + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_addon *addon = + wlr_addon_find(&surface->addons, NULL, &presentation_surface_addon_impl); + struct wlr_presentation_surface *p_surface = NULL; + if (addon != NULL) { + p_surface = wl_container_of(addon, p_surface, addon); + } else { + p_surface = calloc(1, sizeof(*p_surface)); + if (p_surface == NULL) { + wl_client_post_no_memory(client); + return; + } + wlr_addon_init(&p_surface->addon, &surface->addons, + NULL, &presentation_surface_addon_impl); + if (!wlr_surface_synced_init(&p_surface->synced, surface, + &surface_synced_impl, &p_surface->pending, &p_surface->current)) { + free(p_surface); + wl_client_post_no_memory(client); + return; + } + } + + struct wlr_presentation_feedback *feedback = p_surface->pending.feedback; + if (feedback == NULL) { + feedback = calloc(1, sizeof(*feedback)); + if (feedback == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_list_init(&feedback->resources); + p_surface->pending.feedback = feedback; + } + + uint32_t version = wl_resource_get_version(presentation_resource); + struct wl_resource *resource = wl_resource_create(client, + &wp_presentation_feedback_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, NULL, feedback, + feedback_handle_resource_destroy); + + wl_list_insert(&feedback->resources, wl_resource_get_link(resource)); +} + +static void presentation_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wp_presentation_interface presentation_impl = { + .feedback = presentation_handle_feedback, + .destroy = presentation_handle_destroy, +}; + +static void presentation_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wl_resource *resource = wl_resource_create(client, + &wp_presentation_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &presentation_impl, NULL, NULL); + + wp_presentation_send_clock_id(resource, CLOCK_MONOTONIC); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_presentation *presentation = + wl_container_of(listener, presentation, display_destroy); + wl_signal_emit_mutable(&presentation->events.destroy, presentation); + wl_list_remove(&presentation->display_destroy.link); + wl_global_destroy(presentation->global); + free(presentation); +} + +struct wlr_presentation *wlr_presentation_create(struct wl_display *display, + struct wlr_backend *backend) { + struct wlr_presentation *presentation = calloc(1, sizeof(*presentation)); + if (presentation == NULL) { + return NULL; + } + + presentation->global = wl_global_create(display, &wp_presentation_interface, + PRESENTATION_VERSION, NULL, presentation_bind); + if (presentation->global == NULL) { + free(presentation); + return NULL; + } + + wl_signal_init(&presentation->events.destroy); + + presentation->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &presentation->display_destroy); + + return presentation; +} + +void wlr_presentation_feedback_send_presented( + struct wlr_presentation_feedback *feedback, + const struct wlr_presentation_event *event) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &feedback->resources) { + feedback_resource_send_presented(resource, event); + } +} + +struct wlr_presentation_feedback *wlr_presentation_surface_sampled( + struct wlr_surface *surface) { + struct wlr_addon *addon = + wlr_addon_find(&surface->addons, NULL, &presentation_surface_addon_impl); + if (addon != NULL) { + struct wlr_presentation_surface *p_surface = + wl_container_of(addon, p_surface, addon); + struct wlr_presentation_feedback *sampled = + p_surface->current.feedback; + p_surface->current.feedback = NULL; + return sampled; + } + return NULL; +} + +static void feedback_unset_output(struct wlr_presentation_feedback *feedback); + +void wlr_presentation_feedback_destroy( + struct wlr_presentation_feedback *feedback) { + if (feedback == NULL) { + return; + } + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &feedback->resources) { + feedback_resource_send_discarded(resource); + } + assert(wl_list_empty(&feedback->resources)); + + feedback_unset_output(feedback); + free(feedback); +} + +void wlr_presentation_event_from_output(struct wlr_presentation_event *event, + const struct wlr_output_event_present *output_event) { + *event = (struct wlr_presentation_event){ + .output = output_event->output, + .tv_sec = (uint64_t)output_event->when->tv_sec, + .tv_nsec = (uint32_t)output_event->when->tv_nsec, + .refresh = (uint32_t)output_event->refresh, + .seq = (uint64_t)output_event->seq, + .flags = output_event->flags, + }; +} + +static void feedback_unset_output(struct wlr_presentation_feedback *feedback) { + if (feedback->output == NULL) { + return; + } + + feedback->output = NULL; + wl_list_remove(&feedback->output_commit.link); + wl_list_remove(&feedback->output_present.link); + wl_list_remove(&feedback->output_destroy.link); +} + +static void feedback_handle_output_commit(struct wl_listener *listener, + void *data) { + struct wlr_presentation_feedback *feedback = + wl_container_of(listener, feedback, output_commit); + if (feedback->output_committed) { + return; + } + feedback->output_committed = true; + feedback->output_commit_seq = feedback->output->commit_seq; +} + +static void feedback_handle_output_present(struct wl_listener *listener, + void *data) { + struct wlr_presentation_feedback *feedback = + wl_container_of(listener, feedback, output_present); + struct wlr_output_event_present *output_event = data; + + if (!feedback->output_committed || + output_event->commit_seq != feedback->output_commit_seq) { + return; + } + + if (output_event->presented) { + struct wlr_presentation_event event = {0}; + wlr_presentation_event_from_output(&event, output_event); + if (!feedback->zero_copy) { + event.flags &= ~WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; + } + wlr_presentation_feedback_send_presented(feedback, &event); + } + wlr_presentation_feedback_destroy(feedback); +} + +static void feedback_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_presentation_feedback *feedback = + wl_container_of(listener, feedback, output_destroy); + wlr_presentation_feedback_destroy(feedback); +} + +static void presentation_surface_queued_on_output(struct wlr_surface *surface, + struct wlr_output *output, bool zero_copy) { + struct wlr_presentation_feedback *feedback = + wlr_presentation_surface_sampled(surface); + if (feedback == NULL) { + return; + } + + assert(feedback->output == NULL); + feedback->output = output; + feedback->zero_copy = zero_copy; + + feedback->output_commit.notify = feedback_handle_output_commit; + wl_signal_add(&output->events.commit, &feedback->output_commit); + feedback->output_present.notify = feedback_handle_output_present; + wl_signal_add(&output->events.present, &feedback->output_present); + feedback->output_destroy.notify = feedback_handle_output_destroy; + wl_signal_add(&output->events.destroy, &feedback->output_destroy); +} + +void wlr_presentation_surface_textured_on_output(struct wlr_surface *surface, + struct wlr_output *output) { + return presentation_surface_queued_on_output(surface, output, false); +} + +void wlr_presentation_surface_scanned_out_on_output(struct wlr_surface *surface, + struct wlr_output *output) { + return presentation_surface_queued_on_output(surface, output, true); +} diff --git a/types/wlr_primary_selection.c b/types/wlr_primary_selection.c new file mode 100644 index 0000000..e3212d6 --- /dev/null +++ b/types/wlr_primary_selection.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include + +void wlr_primary_selection_source_init( + struct wlr_primary_selection_source *source, + const struct wlr_primary_selection_source_impl *impl) { + assert(impl->send); + *source = (struct wlr_primary_selection_source){ + .impl = impl, + }; + wl_array_init(&source->mime_types); + wl_signal_init(&source->events.destroy); +} + +void wlr_primary_selection_source_destroy( + struct wlr_primary_selection_source *source) { + if (source == NULL) { + return; + } + + wl_signal_emit_mutable(&source->events.destroy, source); + + char **p; + wl_array_for_each(p, &source->mime_types) { + free(*p); + } + wl_array_release(&source->mime_types); + + if (source->impl->destroy) { + source->impl->destroy(source); + } else { + free(source); + } +} + +void wlr_primary_selection_source_send( + struct wlr_primary_selection_source *source, const char *mime_type, + int32_t fd) { + source->impl->send(source, mime_type, fd); +} + + +void wlr_seat_request_set_primary_selection(struct wlr_seat *seat, + struct wlr_seat_client *client, + struct wlr_primary_selection_source *source, uint32_t serial) { + if (client && !wlr_seat_client_validate_event_serial(client, serial)) { + wlr_log(WLR_DEBUG, "Rejecting set_primary_selection request, " + "serial %"PRIu32" was never given to client", serial); + return; + } + + if (seat->primary_selection_source && + serial - seat->primary_selection_serial > UINT32_MAX / 2) { + wlr_log(WLR_DEBUG, "Rejecting set_primary_selection request, " + "serial indicates superseded (%"PRIu32" < %"PRIu32")", + serial, seat->primary_selection_serial); + return; + } + + struct wlr_seat_request_set_primary_selection_event event = { + .source = source, + .serial = serial, + }; + wl_signal_emit_mutable(&seat->events.request_set_primary_selection, &event); +} + +static void seat_handle_primary_selection_source_destroy( + struct wl_listener *listener, void *data) { + struct wlr_seat *seat = + wl_container_of(listener, seat, primary_selection_source_destroy); + wl_list_remove(&seat->primary_selection_source_destroy.link); + seat->primary_selection_source = NULL; + wl_signal_emit_mutable(&seat->events.set_primary_selection, seat); +} + +void wlr_seat_set_primary_selection(struct wlr_seat *seat, + struct wlr_primary_selection_source *source, uint32_t serial) { + if (seat->primary_selection_source == source) { + seat->primary_selection_serial = serial; + return; + } + + if (seat->primary_selection_source != NULL) { + wl_list_remove(&seat->primary_selection_source_destroy.link); + wlr_primary_selection_source_destroy(seat->primary_selection_source); + seat->primary_selection_source = NULL; + } + + seat->primary_selection_source = source; + seat->primary_selection_serial = serial; + + if (source != NULL) { + seat->primary_selection_source_destroy.notify = + seat_handle_primary_selection_source_destroy; + wl_signal_add(&source->events.destroy, + &seat->primary_selection_source_destroy); + } + + wl_signal_emit_mutable(&seat->events.set_primary_selection, seat); +} diff --git a/types/wlr_primary_selection_v1.c b/types/wlr_primary_selection_v1.c new file mode 100644 index 0000000..0a5f0fb --- /dev/null +++ b/types/wlr_primary_selection_v1.c @@ -0,0 +1,492 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "primary-selection-unstable-v1-protocol.h" + +#define DEVICE_MANAGER_VERSION 1 + +static const struct zwp_primary_selection_offer_v1_interface offer_impl; + +static struct wlr_primary_selection_v1_device *device_from_offer_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_primary_selection_offer_v1_interface, &offer_impl)); + return wl_resource_get_user_data(resource); +} + +static void offer_handle_receive(struct wl_client *client, + struct wl_resource *resource, const char *mime_type, int32_t fd) { + struct wlr_primary_selection_v1_device *device = + device_from_offer_resource(resource); + if (device == NULL || device->seat->primary_selection_source == NULL) { + close(fd); + return; + } + + wlr_primary_selection_source_send(device->seat->primary_selection_source, + mime_type, fd); +} + +static void offer_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_primary_selection_offer_v1_interface offer_impl = { + .receive = offer_handle_receive, + .destroy = offer_handle_destroy, +}; + +static void offer_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static struct wlr_primary_selection_v1_device *device_from_resource( + struct wl_resource *resource); + +static void create_offer(struct wl_resource *device_resource, + struct wlr_primary_selection_source *source) { + struct wlr_primary_selection_v1_device *device = + device_from_resource(device_resource); + assert(device != NULL); + + struct wl_client *client = wl_resource_get_client(device_resource); + uint32_t version = wl_resource_get_version(device_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwp_primary_selection_offer_v1_interface, version, 0); + if (resource == NULL) { + wl_resource_post_no_memory(device_resource); + return; + } + wl_resource_set_implementation(resource, &offer_impl, device, + offer_handle_resource_destroy); + + wl_list_insert(&device->offers, wl_resource_get_link(resource)); + + zwp_primary_selection_device_v1_send_data_offer(device_resource, resource); + + char **p; + wl_array_for_each(p, &source->mime_types) { + zwp_primary_selection_offer_v1_send_offer(resource, *p); + } + + zwp_primary_selection_device_v1_send_selection(device_resource, resource); +} + +static void destroy_offer(struct wl_resource *resource) { + if (device_from_offer_resource(resource) == NULL) { + return; + } + + // Make the offer inert + wl_resource_set_user_data(resource, NULL); + + struct wl_list *link = wl_resource_get_link(resource); + wl_list_remove(link); + wl_list_init(link); +} + + +struct client_data_source { + struct wlr_primary_selection_source source; + struct wl_resource *resource; + bool finalized; +}; + +static void client_source_send( + struct wlr_primary_selection_source *wlr_source, + const char *mime_type, int fd) { + struct client_data_source *source = wl_container_of(wlr_source, source, source); + zwp_primary_selection_source_v1_send_send(source->resource, mime_type, fd); + close(fd); +} + +static void client_source_destroy( + struct wlr_primary_selection_source *wlr_source) { + struct client_data_source *source = wl_container_of(wlr_source, source, source); + zwp_primary_selection_source_v1_send_cancelled(source->resource); + // Make the source resource inert + wl_resource_set_user_data(source->resource, NULL); + free(source); +} + +static const struct wlr_primary_selection_source_impl client_source_impl = { + .send = client_source_send, + .destroy = client_source_destroy, +}; + +static const struct zwp_primary_selection_source_v1_interface source_impl; + +static struct client_data_source *client_data_source_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_primary_selection_source_v1_interface, &source_impl)); + return wl_resource_get_user_data(resource); +} + +static void source_handle_offer(struct wl_client *client, + struct wl_resource *resource, const char *mime_type) { + struct client_data_source *source = + client_data_source_from_resource(resource); + if (source == NULL) { + return; + } + if (source->finalized) { + wlr_log(WLR_DEBUG, "Offering additional MIME type after set_selection"); + } + + const char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, &source->source.mime_types) { + if (strcmp(*mime_type_ptr, mime_type) == 0) { + wlr_log(WLR_DEBUG, "Ignoring duplicate MIME type offer %s", + mime_type); + return; + } + } + + char *dup_mime_type = strdup(mime_type); + if (dup_mime_type == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + char **p = wl_array_add(&source->source.mime_types, sizeof(*p)); + if (p == NULL) { + free(dup_mime_type); + wl_resource_post_no_memory(resource); + return; + } + + *p = dup_mime_type; +} + +static void source_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_primary_selection_source_v1_interface source_impl = { + .offer = source_handle_offer, + .destroy = source_handle_destroy, +}; + +static void source_resource_handle_destroy(struct wl_resource *resource) { + struct client_data_source *source = + client_data_source_from_resource(resource); + if (source == NULL) { + return; + } + wlr_primary_selection_source_destroy(&source->source); +} + + +static const struct zwp_primary_selection_device_v1_interface device_impl; + +static struct wlr_primary_selection_v1_device *device_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_primary_selection_device_v1_interface, &device_impl)); + return wl_resource_get_user_data(resource); +} + +static void device_handle_set_selection(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *source_resource, + uint32_t serial) { + struct wlr_primary_selection_v1_device *device = + device_from_resource(resource); + if (device == NULL) { + return; + } + + struct client_data_source *client_source = NULL; + if (source_resource != NULL) { + client_source = client_data_source_from_resource(source_resource); + } + + struct wlr_primary_selection_source *source = NULL; + if (client_source != NULL) { + client_source->finalized = true; + source = &client_source->source; + } + + struct wlr_seat_client *seat_client = + wlr_seat_client_for_wl_client(device->seat, client); + + wlr_seat_request_set_primary_selection(device->seat, seat_client, source, serial); +} + +static void device_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_primary_selection_device_v1_interface device_impl = { + .set_selection = device_handle_set_selection, + .destroy = device_handle_destroy, +}; + +static void device_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + + +static void device_resource_send_selection(struct wl_resource *resource, + struct wlr_primary_selection_source *source) { + assert(device_from_resource(resource) != NULL); + + if (source != NULL) { + create_offer(resource, source); + } else { + zwp_primary_selection_device_v1_send_selection(resource, NULL); + } +} + +static void device_send_selection( + struct wlr_primary_selection_v1_device *device) { + struct wlr_seat_client *seat_client = + device->seat->keyboard_state.focused_client; + if (seat_client == NULL) { + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &device->resources) { + if (wl_resource_get_client(resource) == seat_client->client) { + device_resource_send_selection(resource, + device->seat->primary_selection_source); + } + } +} + +static void device_destroy(struct wlr_primary_selection_v1_device *device); + +static void device_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_primary_selection_v1_device *device = + wl_container_of(listener, device, seat_destroy); + device_destroy(device); +} + +static void device_handle_seat_focus_change(struct wl_listener *listener, + void *data) { + struct wlr_primary_selection_v1_device *device = + wl_container_of(listener, device, seat_focus_change); + // TODO: maybe make previous offers inert, or set a NULL selection for + // previous client? + device_send_selection(device); +} + +static void device_handle_seat_set_primary_selection( + struct wl_listener *listener, void *data) { + struct wlr_primary_selection_v1_device *device = + wl_container_of(listener, device, seat_set_primary_selection); + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &device->offers) { + destroy_offer(resource); + } + + device_send_selection(device); +} + +static struct wlr_primary_selection_v1_device *get_or_create_device( + struct wlr_primary_selection_v1_device_manager *manager, + struct wlr_seat *seat) { + struct wlr_primary_selection_v1_device *device; + wl_list_for_each(device, &manager->devices, link) { + if (device->seat == seat) { + return device; + } + } + + device = calloc(1, sizeof(*device)); + if (device == NULL) { + return NULL; + } + device->manager = manager; + device->seat = seat; + + wl_list_init(&device->resources); + wl_list_insert(&manager->devices, &device->link); + + wl_list_init(&device->offers); + + device->seat_destroy.notify = device_handle_seat_destroy; + wl_signal_add(&seat->events.destroy, &device->seat_destroy); + + device->seat_focus_change.notify = device_handle_seat_focus_change; + wl_signal_add(&seat->keyboard_state.events.focus_change, + &device->seat_focus_change); + + device->seat_set_primary_selection.notify = + device_handle_seat_set_primary_selection; + wl_signal_add(&seat->events.set_primary_selection, + &device->seat_set_primary_selection); + + return device; +} + +static void device_destroy(struct wlr_primary_selection_v1_device *device) { + if (device == NULL) { + return; + } + wl_list_remove(&device->link); + wl_list_remove(&device->seat_destroy.link); + wl_list_remove(&device->seat_focus_change.link); + wl_list_remove(&device->seat_set_primary_selection.link); + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &device->offers) { + destroy_offer(resource); + } + wl_resource_for_each_safe(resource, resource_tmp, &device->resources) { + // Make the resource inert + wl_resource_set_user_data(resource, NULL); + + struct wl_list *link = wl_resource_get_link(resource); + wl_list_remove(link); + wl_list_init(link); + } + free(device); +} + + +static const struct zwp_primary_selection_device_manager_v1_interface + device_manager_impl; + +static struct wlr_primary_selection_v1_device_manager *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_primary_selection_device_manager_v1_interface, &device_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void device_manager_handle_create_source(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id) { + struct client_data_source *source = calloc(1, sizeof(*source)); + if (source == NULL) { + wl_client_post_no_memory(client); + return; + } + wlr_primary_selection_source_init(&source->source, &client_source_impl); + + uint32_t version = wl_resource_get_version(manager_resource); + source->resource = wl_resource_create(client, + &zwp_primary_selection_source_v1_interface, version, id); + if (source->resource == NULL) { + free(source); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(source->resource, &source_impl, source, + source_resource_handle_destroy); +} + +static void device_manager_handle_get_device(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *seat_resource) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + struct wlr_primary_selection_v1_device_manager *manager = + manager_from_resource(manager_resource); + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwp_primary_selection_device_v1_interface, version, id); + if (resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(resource, &device_impl, NULL, + device_handle_resource_destroy); + wl_list_init(wl_resource_get_link(resource)); + if (seat_client == NULL) { + return; + } + + struct wlr_primary_selection_v1_device *device = + get_or_create_device(manager, seat_client->seat); + if (device == NULL) { + wl_resource_destroy(resource); + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_user_data(resource, device); + wl_list_insert(&device->resources, wl_resource_get_link(resource)); + + if (device->seat->keyboard_state.focused_client == seat_client) { + device_resource_send_selection(resource, + device->seat->primary_selection_source); + } +} + +static void device_manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwp_primary_selection_device_manager_v1_interface + device_manager_impl = { + .create_source = device_manager_handle_create_source, + .get_device = device_manager_handle_get_device, + .destroy = device_manager_handle_destroy, +}; + + +static void primary_selection_device_manager_bind(struct wl_client *client, + void *data, uint32_t version, uint32_t id) { + struct wlr_primary_selection_v1_device_manager *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwp_primary_selection_device_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &device_manager_impl, manager, + NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_primary_selection_v1_device_manager *manager = + wl_container_of(listener, manager, display_destroy); + + struct wlr_primary_selection_v1_device *device, *tmp; + wl_list_for_each_safe(device, tmp, &manager->devices, link) { + device_destroy(device); + } + + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_primary_selection_v1_device_manager * + wlr_primary_selection_v1_device_manager_create( + struct wl_display *display) { + struct wlr_primary_selection_v1_device_manager *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + manager->global = wl_global_create(display, + &zwp_primary_selection_device_manager_v1_interface, DEVICE_MANAGER_VERSION, + manager, primary_selection_device_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_list_init(&manager->devices); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_region.c b/types/wlr_region.c new file mode 100644 index 0000000..59f6dfc --- /dev/null +++ b/types/wlr_region.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include +#include "types/wlr_region.h" + +static const struct wl_region_interface region_impl; + +static pixman_region32_t *region_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_region_interface, + ®ion_impl)); + return wl_resource_get_user_data(resource); +} + +const pixman_region32_t *wlr_region_from_resource(struct wl_resource *resource) { + return region_from_resource(resource); +} + +static void region_add(struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) { + pixman_region32_t *region = region_from_resource(resource); + pixman_region32_union_rect(region, region, x, y, width, height); +} + +static void region_subtract(struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) { + pixman_region32_t *region = region_from_resource(resource); + pixman_region32_union_rect(region, region, x, y, width, height); + + pixman_region32_t rect; + pixman_region32_init_rect(&rect, x, y, width, height); + pixman_region32_subtract(region, region, &rect); + pixman_region32_fini(&rect); +} + +static void region_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_region_interface region_impl = { + .destroy = region_destroy, + .add = region_add, + .subtract = region_subtract, +}; + +static void region_handle_resource_destroy(struct wl_resource *resource) { + pixman_region32_t *reg = region_from_resource(resource); + pixman_region32_fini(reg); + free(reg); +} + +struct wl_resource *region_create(struct wl_client *client, + uint32_t version, uint32_t id) { + pixman_region32_t *region = calloc(1, sizeof(*region)); + if (region == NULL) { + wl_client_post_no_memory(client); + return NULL; + } + + pixman_region32_init(region); + + struct wl_resource *region_resource = wl_resource_create(client, + &wl_region_interface, version, id); + if (region_resource == NULL) { + free(region); + wl_client_post_no_memory(client); + return NULL; + } + wl_resource_set_implementation(region_resource, ®ion_impl, region, + region_handle_resource_destroy); + + return region_resource; +} diff --git a/types/wlr_relative_pointer_v1.c b/types/wlr_relative_pointer_v1.c new file mode 100644 index 0000000..94fb155 --- /dev/null +++ b/types/wlr_relative_pointer_v1.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "relative-pointer-unstable-v1-protocol.h" + +#define RELATIVE_POINTER_MANAGER_VERSION 1 + +static const struct zwp_relative_pointer_manager_v1_interface relative_pointer_manager_v1_impl; +static const struct zwp_relative_pointer_v1_interface relative_pointer_v1_impl; + +struct wlr_relative_pointer_v1 *wlr_relative_pointer_v1_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_relative_pointer_v1_interface, + &relative_pointer_v1_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_relative_pointer_manager_v1 *relative_pointer_manager_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_relative_pointer_manager_v1_interface, + &relative_pointer_manager_v1_impl)); + return wl_resource_get_user_data(resource); +} + +static void relative_pointer_destroy(struct wlr_relative_pointer_v1 *relative_pointer) { + wl_signal_emit_mutable(&relative_pointer->events.destroy, relative_pointer); + + wl_list_remove(&relative_pointer->link); + wl_list_remove(&relative_pointer->seat_destroy.link); + wl_list_remove(&relative_pointer->pointer_destroy.link); + + wl_resource_set_user_data(relative_pointer->resource, NULL); + free(relative_pointer); +} + +static void relative_pointer_v1_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_relative_pointer_v1 *relative_pointer = + wlr_relative_pointer_v1_from_resource(resource); + if (relative_pointer == NULL) { + return; + } + relative_pointer_destroy(relative_pointer); +} + +static void relative_pointer_v1_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void relative_pointer_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_relative_pointer_v1 *relative_pointer = + wl_container_of(listener, relative_pointer, seat_destroy); + + relative_pointer_destroy(relative_pointer); +} + +static void relative_pointer_handle_pointer_destroy(struct wl_listener *listener, + void *data) { + struct wlr_relative_pointer_v1 *relative_pointer = + wl_container_of(listener, relative_pointer, pointer_destroy); + + relative_pointer_destroy(relative_pointer); +} + +static void relative_pointer_manager_v1_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void relative_pointer_manager_v1_handle_get_relative_pointer(struct wl_client *client, + struct wl_resource *resource, uint32_t id, struct wl_resource *pointer) { + struct wlr_relative_pointer_manager_v1 *manager = + relative_pointer_manager_from_resource(resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_pointer_resource(pointer); + + struct wl_resource *relative_pointer_resource = wl_resource_create(client, + &zwp_relative_pointer_v1_interface, wl_resource_get_version(resource), id); + if (relative_pointer_resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(relative_pointer_resource, &relative_pointer_v1_impl, + NULL, relative_pointer_v1_handle_resource_destroy); + + if (seat_client == NULL) { + // Leave the resource inert + return; + } + + struct wlr_relative_pointer_v1 *relative_pointer = calloc(1, sizeof(*relative_pointer)); + if (relative_pointer == NULL) { + wl_client_post_no_memory(client); + return; + } + + relative_pointer->resource = relative_pointer_resource; + relative_pointer->pointer_resource = pointer; + + relative_pointer->seat = seat_client->seat; + wl_signal_add(&relative_pointer->seat->events.destroy, + &relative_pointer->seat_destroy); + relative_pointer->seat_destroy.notify = relative_pointer_handle_seat_destroy; + + wl_signal_init(&relative_pointer->events.destroy); + + wl_resource_set_user_data(relative_pointer_resource, relative_pointer); + + wl_list_insert(&manager->relative_pointers, &relative_pointer->link); + + wl_resource_add_destroy_listener(relative_pointer->pointer_resource, + &relative_pointer->pointer_destroy); + relative_pointer->pointer_destroy.notify = relative_pointer_handle_pointer_destroy; + + wl_signal_emit_mutable(&manager->events.new_relative_pointer, + relative_pointer); +} + +static void relative_pointer_manager_v1_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_relative_pointer_manager_v1 *manager = data; + + struct wl_resource *manager_resource = wl_resource_create(wl_client, + &zwp_relative_pointer_manager_v1_interface, version, id); + if (manager_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_resource_set_implementation(manager_resource, &relative_pointer_manager_v1_impl, + manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_relative_pointer_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy_listener); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy_listener.link); + wl_global_destroy(manager->global); + free(manager); +} + +static const struct zwp_relative_pointer_manager_v1_interface relative_pointer_manager_v1_impl = { + .destroy = relative_pointer_manager_v1_handle_destroy, + .get_relative_pointer = relative_pointer_manager_v1_handle_get_relative_pointer, +}; + +static const struct zwp_relative_pointer_v1_interface relative_pointer_v1_impl = { + .destroy = relative_pointer_v1_handle_destroy, +}; + +struct wlr_relative_pointer_manager_v1 *wlr_relative_pointer_manager_v1_create(struct wl_display *display) { + struct wlr_relative_pointer_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + wl_list_init(&manager->relative_pointers); + + manager->global = wl_global_create(display, + &zwp_relative_pointer_manager_v1_interface, RELATIVE_POINTER_MANAGER_VERSION, + manager, relative_pointer_manager_v1_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.destroy); + wl_signal_init(&manager->events.new_relative_pointer); + + manager->display_destroy_listener.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy_listener); + + return manager; +} + +void wlr_relative_pointer_manager_v1_send_relative_motion( + struct wlr_relative_pointer_manager_v1 *manager, struct wlr_seat *seat, + uint64_t time_usec, double dx, double dy, + double dx_unaccel, double dy_unaccel) { + struct wlr_seat_client *focused = seat->pointer_state.focused_client; + if (focused == NULL) { + return; + } + + struct wlr_relative_pointer_v1 *pointer; + wl_list_for_each(pointer, &manager->relative_pointers, link) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_pointer_resource(pointer->pointer_resource); + if (!pointer->seat || seat != pointer->seat || focused != seat_client) { + continue; + } + + zwp_relative_pointer_v1_send_relative_motion(pointer->resource, + (uint32_t)(time_usec >> 32), (uint32_t)time_usec, + wl_fixed_from_double(dx), wl_fixed_from_double(dy), + wl_fixed_from_double(dx_unaccel), wl_fixed_from_double(dy_unaccel)); + } +} diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c new file mode 100644 index 0000000..34f6c5b --- /dev/null +++ b/types/wlr_screencopy_v1.c @@ -0,0 +1,720 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wlr-screencopy-unstable-v1-protocol.h" +#include "render/pixel_format.h" +#include "render/wlr_renderer.h" + +#define SCREENCOPY_MANAGER_VERSION 3 + +struct screencopy_damage { + struct wl_list link; + struct wlr_output *output; + struct pixman_region32 damage; + struct wl_listener output_precommit; + struct wl_listener output_destroy; +}; + +static const struct zwlr_screencopy_frame_v1_interface frame_impl; + +static struct screencopy_damage *screencopy_damage_find( + struct wlr_screencopy_v1_client *client, + struct wlr_output *output) { + struct screencopy_damage *damage; + + wl_list_for_each(damage, &client->damages, link) { + if (damage->output == output) { + return damage; + } + } + + return NULL; +} + +static void screencopy_damage_accumulate(struct screencopy_damage *damage, + const struct wlr_output_state *state) { + struct pixman_region32 *region = &damage->damage; + struct wlr_output *output = damage->output; + + if (state->committed & WLR_OUTPUT_STATE_DAMAGE) { + // If the compositor submitted damage, copy it over + pixman_region32_union(region, region, &state->damage); + pixman_region32_intersect_rect(region, region, 0, 0, + output->width, output->height); + } else if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + // If the compositor did not submit damage but did submit a buffer + // damage everything + pixman_region32_union_rect(region, region, 0, 0, + output->width, output->height); + } +} + +static void screencopy_damage_handle_output_precommit( + struct wl_listener *listener, void *data) { + struct screencopy_damage *damage = + wl_container_of(listener, damage, output_precommit); + const struct wlr_output_event_precommit *event = data; + screencopy_damage_accumulate(damage, event->state); +} + +static void screencopy_damage_destroy(struct screencopy_damage *damage) { + wl_list_remove(&damage->output_destroy.link); + wl_list_remove(&damage->output_precommit.link); + wl_list_remove(&damage->link); + pixman_region32_fini(&damage->damage); + free(damage); +} + +static void screencopy_damage_handle_output_destroy( + struct wl_listener *listener, void *data) { + struct screencopy_damage *damage = + wl_container_of(listener, damage, output_destroy); + screencopy_damage_destroy(damage); +} + +static struct screencopy_damage *screencopy_damage_create( + struct wlr_screencopy_v1_client *client, + struct wlr_output *output) { + struct screencopy_damage *damage = calloc(1, sizeof(*damage)); + if (!damage) { + return NULL; + } + + damage->output = output; + pixman_region32_init_rect(&damage->damage, 0, 0, output->width, + output->height); + wl_list_insert(&client->damages, &damage->link); + + wl_signal_add(&output->events.precommit, &damage->output_precommit); + damage->output_precommit.notify = + screencopy_damage_handle_output_precommit; + + wl_signal_add(&output->events.destroy, &damage->output_destroy); + damage->output_destroy.notify = screencopy_damage_handle_output_destroy; + + return damage; +} + +static struct screencopy_damage *screencopy_damage_get_or_create( + struct wlr_screencopy_v1_client *client, + struct wlr_output *output) { + struct screencopy_damage *damage = screencopy_damage_find(client, output); + return damage ? damage : screencopy_damage_create(client, output); +} + +static void client_unref(struct wlr_screencopy_v1_client *client) { + assert(client->ref > 0); + + if (--client->ref != 0) { + return; + } + + struct screencopy_damage *damage, *tmp_damage; + wl_list_for_each_safe(damage, tmp_damage, &client->damages, link) { + screencopy_damage_destroy(damage); + } + + free(client); +} + +static struct wlr_screencopy_frame_v1 *frame_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_screencopy_frame_v1_interface, &frame_impl)); + return wl_resource_get_user_data(resource); +} + +static void frame_destroy(struct wlr_screencopy_frame_v1 *frame) { + if (frame == NULL) { + return; + } + if (frame->output != NULL && frame->buffer != NULL) { + wlr_output_lock_attach_render(frame->output, false); + if (frame->cursor_locked) { + wlr_output_lock_software_cursors(frame->output, false); + } + } + wl_list_remove(&frame->link); + wl_list_remove(&frame->output_commit.link); + wl_list_remove(&frame->output_destroy.link); + wl_list_remove(&frame->output_enable.link); + // Make the frame resource inert + wl_resource_set_user_data(frame->resource, NULL); + wlr_buffer_unlock(frame->buffer); + client_unref(frame->client); + free(frame); +} + +static void frame_send_damage(struct wlr_screencopy_frame_v1 *frame) { + if (!frame->with_damage) { + return; + } + + struct screencopy_damage *damage = + screencopy_damage_get_or_create(frame->client, frame->output); + if (damage == NULL) { + return; + } + + // TODO: send fine-grained damage events + struct pixman_box32 *damage_box = + pixman_region32_extents(&damage->damage); + + int damage_x = damage_box->x1; + int damage_y = damage_box->y1; + int damage_width = damage_box->x2 - damage_box->x1; + int damage_height = damage_box->y2 - damage_box->y1; + + zwlr_screencopy_frame_v1_send_damage(frame->resource, + damage_x, damage_y, damage_width, damage_height); + + pixman_region32_clear(&damage->damage); +} + +static void frame_send_ready(struct wlr_screencopy_frame_v1 *frame, + struct timespec *when) { + time_t tv_sec = when->tv_sec; + uint32_t tv_sec_hi = (sizeof(tv_sec) > 4) ? tv_sec >> 32 : 0; + uint32_t tv_sec_lo = tv_sec & 0xFFFFFFFF; + zwlr_screencopy_frame_v1_send_ready(frame->resource, + tv_sec_hi, tv_sec_lo, when->tv_nsec); +} + +static bool frame_shm_copy(struct wlr_screencopy_frame_v1 *frame, + struct wlr_buffer *src_buffer) { + struct wlr_output *output = frame->output; + struct wlr_renderer *renderer = output->renderer; + assert(renderer); + + void *data; + uint32_t format; + size_t stride; + if (!wlr_buffer_begin_data_ptr_access(frame->buffer, + WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) { + return false; + } + + bool ok = false; + + struct wlr_texture *texture = wlr_texture_from_buffer(renderer, src_buffer); + if (!texture) { + goto out; + } + + ok = wlr_texture_read_pixels(texture, &(struct wlr_texture_read_pixels_options) { + .data = data, + .format = format, + .stride = stride, + .src_box = frame->box, + }); + + wlr_texture_destroy(texture); + +out: + wlr_buffer_end_data_ptr_access(frame->buffer); + return ok; +} + +static bool frame_dma_copy(struct wlr_screencopy_frame_v1 *frame, + struct wlr_buffer *src_buffer) { + struct wlr_buffer *dst_buffer = frame->buffer; + struct wlr_output *output = frame->output; + struct wlr_renderer *renderer = output->renderer; + assert(renderer); + + + struct wlr_texture *src_tex = + wlr_texture_from_buffer(renderer, src_buffer); + if (src_tex == NULL) { + return false; + } + + bool ok = false; + + struct wlr_render_pass *pass = + wlr_renderer_begin_buffer_pass(renderer, dst_buffer, NULL); + if (!pass) { + goto out; + } + + wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options) { + .texture = src_tex, + .blend_mode = WLR_RENDER_BLEND_MODE_NONE, + .dst_box = (struct wlr_box){ + .width = dst_buffer->width, + .height = dst_buffer->height, + }, + .src_box = (struct wlr_fbox){ + .x = frame->box.x, + .y = frame->box.y, + .width = frame->box.width, + .height = frame->box.height, + }, + }); + + ok = wlr_render_pass_submit(pass); + +out: + wlr_texture_destroy(src_tex); + return ok; +} + +static void frame_handle_output_commit(struct wl_listener *listener, + void *data) { + struct wlr_screencopy_frame_v1 *frame = + wl_container_of(listener, frame, output_commit); + struct wlr_output_event_commit *event = data; + struct wlr_output *output = frame->output; + struct wlr_renderer *renderer = output->renderer; + assert(renderer); + + if (!(event->state->committed & WLR_OUTPUT_STATE_BUFFER)) { + return; + } + + if (!frame->buffer) { + return; + } + + if (frame->with_damage) { + struct screencopy_damage *damage = + screencopy_damage_get_or_create(frame->client, output); + if (damage && !pixman_region32_not_empty(&damage->damage)) { + return; + } + } + + wl_list_remove(&frame->output_commit.link); + wl_list_init(&frame->output_commit.link); + + struct wlr_buffer *src_buffer = event->state->buffer; + if (frame->box.x < 0 || frame->box.y < 0 || + frame->box.x + frame->box.width > src_buffer->width || + frame->box.y + frame->box.height > src_buffer->height) { + goto err; + } + + switch (frame->buffer_cap) { + case WLR_BUFFER_CAP_DMABUF: + if (!frame_dma_copy(frame, src_buffer)) { + goto err; + } + break; + case WLR_BUFFER_CAP_DATA_PTR: + if (!frame_shm_copy(frame, src_buffer)) { + goto err; + } + break; + default: + abort(); // unreachable + } + + zwlr_screencopy_frame_v1_send_flags(frame->resource, 0); + frame_send_damage(frame); + frame_send_ready(frame, event->when); + frame_destroy(frame); + return; + +err: + zwlr_screencopy_frame_v1_send_failed(frame->resource); + frame_destroy(frame); +} + +static void frame_handle_output_enable(struct wl_listener *listener, + void *data) { + struct wlr_screencopy_frame_v1 *frame = + wl_container_of(listener, frame, output_enable); + if (!frame->output->enabled) { + zwlr_screencopy_frame_v1_send_failed(frame->resource); + frame_destroy(frame); + } +} + +static void frame_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_screencopy_frame_v1 *frame = + wl_container_of(listener, frame, output_destroy); + zwlr_screencopy_frame_v1_send_failed(frame->resource); + frame_destroy(frame); +} + +static void frame_handle_copy(struct wl_client *wl_client, + struct wl_resource *frame_resource, + struct wl_resource *buffer_resource) { + struct wlr_screencopy_frame_v1 *frame = frame_from_resource(frame_resource); + if (frame == NULL) { + return; + } + + struct wlr_output *output = frame->output; + + if (!output->enabled) { + zwlr_screencopy_frame_v1_send_failed(frame->resource); + frame_destroy(frame); + return; + } + + struct wlr_buffer *buffer = wlr_buffer_try_from_resource(buffer_resource); + if (buffer == NULL) { + wl_resource_post_error(frame->resource, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, + "invalid buffer"); + return; + } + + if (buffer->width != frame->box.width || buffer->height != frame->box.height) { + wl_resource_post_error(frame->resource, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, + "invalid buffer dimensions"); + return; + } + + if (frame->buffer != NULL) { + wl_resource_post_error(frame->resource, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, + "frame already used"); + return; + } + + enum wlr_buffer_cap cap; + struct wlr_dmabuf_attributes dmabuf; + void *data; + uint32_t format; + size_t stride; + if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) { + cap = WLR_BUFFER_CAP_DMABUF; + + if (dmabuf.format != frame->dmabuf_format) { + wl_resource_post_error(frame->resource, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, + "invalid buffer format"); + return; + } + } else if (wlr_buffer_begin_data_ptr_access(buffer, + WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) { + wlr_buffer_end_data_ptr_access(buffer); + + cap = WLR_BUFFER_CAP_DATA_PTR; + + if (format != frame->shm_format) { + wl_resource_post_error(frame->resource, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, + "invalid buffer format"); + return; + } + if (stride != (size_t)frame->shm_stride) { + wl_resource_post_error(frame->resource, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, + "invalid buffer stride"); + return; + } + } else { + wl_resource_post_error(frame->resource, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, + "unsupported buffer type"); + return; + } + + frame->buffer = buffer; + frame->buffer_cap = cap; + + wl_signal_add(&output->events.commit, &frame->output_commit); + frame->output_commit.notify = frame_handle_output_commit; + + wl_signal_add(&output->events.destroy, &frame->output_enable); + frame->output_enable.notify = frame_handle_output_enable; + + // Request a frame because we can't assume that the current front buffer is still usable. It may + // have been released already, and we shouldn't lock it here because compositors want to render + // into the least damaged buffer. + wlr_output_update_needs_frame(output); + + wlr_output_lock_attach_render(output, true); + if (frame->overlay_cursor) { + wlr_output_lock_software_cursors(output, true); + frame->cursor_locked = true; + } +} + +static void frame_handle_copy_with_damage(struct wl_client *wl_client, + struct wl_resource *frame_resource, + struct wl_resource *buffer_resource) { + struct wlr_screencopy_frame_v1 *frame = frame_from_resource(frame_resource); + if (frame == NULL) { + return; + } + frame->with_damage = true; + frame_handle_copy(wl_client, frame_resource, buffer_resource); +} + +static void frame_handle_destroy(struct wl_client *wl_client, + struct wl_resource *frame_resource) { + wl_resource_destroy(frame_resource); +} + +static const struct zwlr_screencopy_frame_v1_interface frame_impl = { + .copy = frame_handle_copy, + .destroy = frame_handle_destroy, + .copy_with_damage = frame_handle_copy_with_damage, +}; + +static void frame_handle_resource_destroy(struct wl_resource *frame_resource) { + struct wlr_screencopy_frame_v1 *frame = frame_from_resource(frame_resource); + frame_destroy(frame); +} + + +static const struct zwlr_screencopy_manager_v1_interface manager_impl; + +static struct wlr_screencopy_v1_client *client_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_screencopy_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void capture_output(struct wl_client *wl_client, + struct wlr_screencopy_v1_client *client, uint32_t version, + uint32_t id, int32_t overlay_cursor, struct wlr_output *output, + const struct wlr_box *box) { + struct wlr_screencopy_frame_v1 *frame = calloc(1, sizeof(*frame)); + if (frame == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + frame->output = output; + frame->overlay_cursor = !!overlay_cursor; + + frame->resource = wl_resource_create(wl_client, + &zwlr_screencopy_frame_v1_interface, version, id); + if (frame->resource == NULL) { + free(frame); + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(frame->resource, &frame_impl, frame, + frame_handle_resource_destroy); + + if (output == NULL) { + wl_resource_set_user_data(frame->resource, NULL); + zwlr_screencopy_frame_v1_send_failed(frame->resource); + free(frame); + return; + } + + frame->client = client; + client->ref++; + + wl_list_insert(&client->manager->frames, &frame->link); + + wl_list_init(&frame->output_commit.link); + wl_list_init(&frame->output_enable.link); + + wl_signal_add(&output->events.destroy, &frame->output_destroy); + frame->output_destroy.notify = frame_handle_output_destroy; + + if (output == NULL || !output->enabled) { + goto error; + } + + struct wlr_renderer *renderer = output->renderer; + assert(renderer); + + if (!wlr_output_configure_primary_swapchain(output, NULL, &output->swapchain)) { + goto error; + } + + int buffer_age; + struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain, &buffer_age); + if (buffer == NULL) { + goto error; + } + + struct wlr_texture *texture = wlr_texture_from_buffer(renderer, buffer); + wlr_buffer_unlock(buffer); + if (!texture) { + goto error; + } + + frame->shm_format = wlr_texture_preferred_read_format(texture); + wlr_texture_destroy(texture); + + if (frame->shm_format == DRM_FORMAT_INVALID) { + wlr_log(WLR_ERROR, + "Failed to capture output: no read format supported by renderer"); + goto error; + } + const struct wlr_pixel_format_info *shm_info = + drm_get_pixel_format_info(frame->shm_format); + if (!shm_info) { + wlr_log(WLR_ERROR, + "Failed to capture output: no pixel format info matching read format"); + goto error; + } + + if (output->allocator && + (output->allocator->buffer_caps & WLR_BUFFER_CAP_DMABUF)) { + frame->dmabuf_format = output->render_format; + } else { + frame->dmabuf_format = DRM_FORMAT_INVALID; + } + + struct wlr_box buffer_box = {0}; + if (box == NULL) { + buffer_box.width = output->width; + buffer_box.height = output->height; + } else { + int ow, oh; + wlr_output_effective_resolution(output, &ow, &oh); + + buffer_box = *box; + + wlr_box_transform(&buffer_box, &buffer_box, + wlr_output_transform_invert(output->transform), ow, oh); + buffer_box.x *= output->scale; + buffer_box.y *= output->scale; + buffer_box.width *= output->scale; + buffer_box.height *= output->scale; + } + + frame->box = buffer_box; + frame->shm_stride = pixel_format_info_min_stride(shm_info, buffer_box.width); + + zwlr_screencopy_frame_v1_send_buffer(frame->resource, + convert_drm_format_to_wl_shm(frame->shm_format), + buffer_box.width, buffer_box.height, frame->shm_stride); + + if (version >= 3) { + if (frame->dmabuf_format != DRM_FORMAT_INVALID) { + zwlr_screencopy_frame_v1_send_linux_dmabuf( + frame->resource, frame->dmabuf_format, + buffer_box.width, buffer_box.height); + } + + zwlr_screencopy_frame_v1_send_buffer_done(frame->resource); + } + + return; + +error: + zwlr_screencopy_frame_v1_send_failed(frame->resource); + frame_destroy(frame); +} + +static void manager_handle_capture_output(struct wl_client *wl_client, + struct wl_resource *manager_resource, uint32_t id, + int32_t overlay_cursor, struct wl_resource *output_resource) { + struct wlr_screencopy_v1_client *client = + client_from_resource(manager_resource); + uint32_t version = wl_resource_get_version(manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + capture_output(wl_client, client, version, id, overlay_cursor, output, + NULL); +} + +static void manager_handle_capture_output_region(struct wl_client *wl_client, + struct wl_resource *manager_resource, uint32_t id, + int32_t overlay_cursor, struct wl_resource *output_resource, + int32_t x, int32_t y, int32_t width, int32_t height) { + struct wlr_screencopy_v1_client *client = + client_from_resource(manager_resource); + uint32_t version = wl_resource_get_version(manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + struct wlr_box box = { + .x = x, + .y = y, + .width = width, + .height = height, + }; + capture_output(wl_client, client, version, id, overlay_cursor, output, + &box); +} + +static void manager_handle_destroy(struct wl_client *wl_client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwlr_screencopy_manager_v1_interface manager_impl = { + .capture_output = manager_handle_capture_output, + .capture_output_region = manager_handle_capture_output_region, + .destroy = manager_handle_destroy, +}; + +static void manager_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_screencopy_v1_client *client = + client_from_resource(resource); + client_unref(client); +} + +static void manager_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_screencopy_manager_v1 *manager = data; + + struct wlr_screencopy_v1_client *client = calloc(1, sizeof(*client)); + if (client == NULL) { + goto failure; + } + + struct wl_resource *resource = wl_resource_create(wl_client, + &zwlr_screencopy_manager_v1_interface, version, id); + if (resource == NULL) { + goto failure; + } + + client->ref = 1; + client->manager = manager; + wl_list_init(&client->damages); + + wl_resource_set_implementation(resource, &manager_impl, client, + manager_handle_resource_destroy); + + return; +failure: + free(client); + wl_client_post_no_memory(wl_client); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_screencopy_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_screencopy_manager_v1 *wlr_screencopy_manager_v1_create( + struct wl_display *display) { + struct wlr_screencopy_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &zwlr_screencopy_manager_v1_interface, SCREENCOPY_MANAGER_VERSION, + manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + wl_list_init(&manager->frames); + + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_security_context_v1.c b/types/wlr_security_context_v1.c new file mode 100644 index 0000000..a43af9e --- /dev/null +++ b/types/wlr_security_context_v1.c @@ -0,0 +1,442 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "security-context-v1-protocol.h" + +#define SECURITY_CONTEXT_MANAGER_V1_VERSION 1 + +struct wlr_security_context_v1 { + struct wlr_security_context_manager_v1 *manager; + struct wlr_security_context_v1_state state; + struct wl_list link; // wlr_security_context_manager_v1.contexts + int listen_fd, close_fd; + struct wl_event_source *listen_source, *close_source; +}; + +struct wlr_security_context_v1_client { + struct wlr_security_context_v1_state state; + struct wl_listener destroy; +}; + +static void resource_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wp_security_context_manager_v1_interface manager_impl; +static const struct wp_security_context_v1_interface security_context_impl; + +static struct wlr_security_context_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_security_context_manager_v1_interface, &manager_impl)); + struct wlr_security_context_manager_v1 *manager = + wl_resource_get_user_data(resource); + assert(manager != NULL); + return manager; +} + +/** + * Get a struct wlr_security_context_v1 from a struct wl_resource. + * + * NULL is returned if the security context has been committed. + */ +static struct wlr_security_context_v1 *security_context_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_security_context_v1_interface, &security_context_impl)); + return wl_resource_get_user_data(resource); +} + +static void security_context_state_finish(struct wlr_security_context_v1_state *state) { + free(state->app_id); + free(state->sandbox_engine); + free(state->instance_id); +} + +static bool copy_state_field(char **dst, const char *src) { + if (src == NULL) { + return true; + } + *dst = strdup(src); + return *dst != NULL; +} + +static bool security_context_state_copy(struct wlr_security_context_v1_state *dst, + const struct wlr_security_context_v1_state *src) { + bool ok = copy_state_field(&dst->app_id, src->app_id) && + copy_state_field(&dst->sandbox_engine, src->sandbox_engine) && + copy_state_field(&dst->instance_id, src->instance_id); + if (!ok) { + security_context_state_finish(dst); + } + return ok; +} + +static void security_context_destroy( + struct wlr_security_context_v1 *security_context) { + if (security_context == NULL) { + return; + } + + if (security_context->listen_source != NULL) { + wl_event_source_remove(security_context->listen_source); + } + if (security_context->close_source != NULL) { + wl_event_source_remove(security_context->close_source); + } + + close(security_context->listen_fd); + close(security_context->close_fd); + + security_context_state_finish(&security_context->state); + wl_list_remove(&security_context->link); + free(security_context); +} + +static void security_context_client_destroy( + struct wlr_security_context_v1_client *security_context_client) { + wl_list_remove(&security_context_client->destroy.link); + security_context_state_finish(&security_context_client->state); + free(security_context_client); +} + +static void security_context_client_handle_destroy(struct wl_listener *listener, + void *data) { + struct wlr_security_context_v1_client *security_context_client = + wl_container_of(listener, security_context_client, destroy); + security_context_client_destroy(security_context_client); +} + +static int security_context_handle_listen_fd_event(int listen_fd, uint32_t mask, + void *data) { + struct wlr_security_context_v1 *security_context = data; + + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { + security_context_destroy(security_context); + return 0; + } + + if (mask & WL_EVENT_READABLE) { + int client_fd = accept(listen_fd, NULL, NULL); + if (client_fd < 0) { + wlr_log_errno(WLR_ERROR, "accept failed"); + return 0; + } + + struct wlr_security_context_v1_client *security_context_client = + calloc(1, sizeof(*security_context_client)); + if (security_context_client == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + close(client_fd); + return 0; + } + + struct wl_display *display = + wl_global_get_display(security_context->manager->global); + struct wl_client *client = wl_client_create(display, client_fd); + if (client == NULL) { + wlr_log(WLR_ERROR, "wl_client_create failed"); + close(client_fd); + free(security_context_client); + return 0; + } + + security_context_client->destroy.notify = security_context_client_handle_destroy; + wl_client_add_destroy_listener(client, &security_context_client->destroy); + + if (!security_context_state_copy(&security_context_client->state, + &security_context->state)) { + security_context_client_destroy(security_context_client); + wl_client_post_no_memory(client); + return 0; + } + } + + return 0; +} + +static int security_context_handle_close_fd_event(int fd, uint32_t mask, + void *data) { + struct wlr_security_context_v1 *security_context = data; + + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { + security_context_destroy(security_context); + } + + return 0; +} + +static void security_context_handle_commit(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_security_context_v1 *security_context = + security_context_from_resource(resource); + if (security_context == NULL) { + wl_resource_post_error(resource, + WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_USED, + "Security context has already been committed"); + return; + } + + // In theory the compositor should prevent this with a global filter, but + // let's make sure it doesn't happen. + if (wlr_security_context_manager_v1_lookup_client(security_context->manager, + client) != NULL) { + wl_resource_post_error(resource, + WP_SECURITY_CONTEXT_MANAGER_V1_ERROR_NESTED, + "Nested security contexts are forbidden"); + return; + } + + struct wl_display *display = wl_client_get_display(client); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + + security_context->listen_source = wl_event_loop_add_fd(loop, + security_context->listen_fd, WL_EVENT_READABLE, + security_context_handle_listen_fd_event, security_context); + if (security_context->listen_source == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + security_context->close_source = wl_event_loop_add_fd(loop, + security_context->close_fd, 0, security_context_handle_close_fd_event, + security_context); + if (security_context->close_source == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + wl_resource_set_user_data(resource, NULL); + + struct wlr_security_context_v1_commit_event event = { + .state = &security_context->state, + .parent_client = client, + }; + wl_signal_emit_mutable(&security_context->manager->events.commit, &event); +} + +static void security_context_handle_set_sandbox_engine(struct wl_client *client, + struct wl_resource *resource, const char *sandbox_engine) { + struct wlr_security_context_v1 *security_context = + security_context_from_resource(resource); + if (security_context == NULL) { + wl_resource_post_error(resource, + WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_USED, + "Security context has already been committed"); + return; + } + + if (security_context->state.sandbox_engine != NULL) { + wl_resource_post_error(resource, + WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_SET, + "Sandbox engine has already been set"); + return; + } + + security_context->state.sandbox_engine = strdup(sandbox_engine); + if (security_context->state.sandbox_engine == NULL) { + wl_resource_post_no_memory(resource); + } +} + +static void security_context_handle_set_app_id(struct wl_client *client, + struct wl_resource *resource, const char *app_id) { + struct wlr_security_context_v1 *security_context = + security_context_from_resource(resource); + if (security_context == NULL) { + wl_resource_post_error(resource, + WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_USED, + "Security context has already been committed"); + return; + } + + if (security_context->state.app_id != NULL) { + wl_resource_post_error(resource, + WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_SET, + "App ID has already been set"); + return; + } + + security_context->state.app_id = strdup(app_id); + if (security_context->state.app_id == NULL) { + wl_resource_post_no_memory(resource); + } +} + +static void security_context_handle_set_instance_id(struct wl_client *client, + struct wl_resource *resource, const char *instance_id) { + struct wlr_security_context_v1 *security_context = + security_context_from_resource(resource); + if (security_context == NULL) { + wl_resource_post_error(resource, + WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_USED, + "Security context has already been committed"); + return; + } + + if (security_context->state.instance_id != NULL) { + wl_resource_post_error(resource, + WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_SET, + "Instance ID has already been set"); + return; + } + + security_context->state.instance_id = strdup(instance_id); + if (security_context->state.instance_id == NULL) { + wl_resource_post_no_memory(resource); + } +} + +static const struct wp_security_context_v1_interface security_context_impl = { + .destroy = resource_handle_destroy, + .commit = security_context_handle_commit, + .set_sandbox_engine = security_context_handle_set_sandbox_engine, + .set_app_id = security_context_handle_set_app_id, + .set_instance_id = security_context_handle_set_instance_id, +}; + +static void security_context_resource_destroy(struct wl_resource *resource) { + struct wlr_security_context_v1 *security_context = + security_context_from_resource(resource); + security_context_destroy(security_context); +} + +static void manager_handle_create_listener(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + int listen_fd, int close_fd) { + struct wlr_security_context_manager_v1 *manager = + manager_from_resource(manager_resource); + + struct stat stat_buf = {0}; + if (fstat(listen_fd, &stat_buf) != 0) { + wlr_log_errno(WLR_ERROR, "fstat failed on listen FD"); + wl_resource_post_error(manager_resource, + WP_SECURITY_CONTEXT_MANAGER_V1_ERROR_INVALID_LISTEN_FD, + "Invalid listen_fd"); + return; + } else if (!S_ISSOCK(stat_buf.st_mode)) { + wl_resource_post_error(manager_resource, + WP_SECURITY_CONTEXT_MANAGER_V1_ERROR_INVALID_LISTEN_FD, + "listen_fd is not a socket"); + return; + } + + int accept_conn = 0; + socklen_t accept_conn_size = sizeof(accept_conn); + if (getsockopt(listen_fd, SOL_SOCKET, SO_ACCEPTCONN, &accept_conn, + &accept_conn_size) != 0) { + wlr_log_errno(WLR_ERROR, "getsockopt failed on listen FD"); + wl_resource_post_error(manager_resource, + WP_SECURITY_CONTEXT_MANAGER_V1_ERROR_INVALID_LISTEN_FD, + "Invalid listen_fd"); + return; + } else if (accept_conn == 0) { + wl_resource_post_error(manager_resource, + WP_SECURITY_CONTEXT_MANAGER_V1_ERROR_INVALID_LISTEN_FD, + "listen_fd is not a listening socket"); + return; + } + + struct wlr_security_context_v1 *security_context = + calloc(1, sizeof(*security_context)); + if (security_context == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + security_context->manager = manager; + security_context->listen_fd = listen_fd; + security_context->close_fd = close_fd; + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &wp_security_context_v1_interface, version, id); + if (resource == NULL) { + free(security_context); + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(resource, &security_context_impl, + security_context, security_context_resource_destroy); + + wl_list_insert(&manager->contexts, &security_context->link); +} + +static const struct wp_security_context_manager_v1_interface manager_impl = { + .destroy = resource_handle_destroy, + .create_listener = manager_handle_create_listener, +}; + +static void manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_security_context_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_security_context_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_security_context_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + assert(wl_list_empty(&manager->events.commit.listener_list)); + + struct wlr_security_context_v1 *security_context, *tmp; + wl_list_for_each_safe(security_context, tmp, &manager->contexts, link) { + security_context_destroy(security_context); + } + + wl_global_destroy(manager->global); + wl_list_remove(&manager->display_destroy.link); + free(manager); +} + +struct wlr_security_context_manager_v1 *wlr_security_context_manager_v1_create( + struct wl_display *display) { + struct wlr_security_context_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &wp_security_context_manager_v1_interface, + SECURITY_CONTEXT_MANAGER_V1_VERSION, manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_list_init(&manager->contexts); + wl_signal_init(&manager->events.destroy); + wl_signal_init(&manager->events.commit); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +const struct wlr_security_context_v1_state *wlr_security_context_manager_v1_lookup_client( + struct wlr_security_context_manager_v1 *manager, struct wl_client *client) { + struct wl_listener *listener = wl_client_get_destroy_listener(client, + security_context_client_handle_destroy); + if (listener == NULL) { + return NULL; + } + + struct wlr_security_context_v1_client *security_context_client = + wl_container_of(listener, security_context_client, destroy); + return &security_context_client->state; +} diff --git a/types/wlr_server_decoration.c b/types/wlr_server_decoration.c new file mode 100644 index 0000000..3d3d1ff --- /dev/null +++ b/types/wlr_server_decoration.c @@ -0,0 +1,195 @@ +#include +#include +#include +#include +#include +#include "server-decoration-protocol.h" + +static const struct org_kde_kwin_server_decoration_interface + server_decoration_impl; + +static struct wlr_server_decoration *decoration_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &org_kde_kwin_server_decoration_interface, &server_decoration_impl)); + return wl_resource_get_user_data(resource); +} + +static void server_decoration_handle_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void server_decoration_handle_request_mode(struct wl_client *client, + struct wl_resource *resource, uint32_t mode) { + struct wlr_server_decoration *decoration = + decoration_from_resource(resource); + if (decoration == NULL || decoration->mode == mode) { + return; + } + decoration->mode = mode; + wl_signal_emit_mutable(&decoration->events.mode, decoration); + org_kde_kwin_server_decoration_send_mode(decoration->resource, + decoration->mode); +} + +static void server_decoration_destroy( + struct wlr_server_decoration *decoration) { + wl_signal_emit_mutable(&decoration->events.destroy, decoration); + wl_list_remove(&decoration->surface_destroy_listener.link); + wl_resource_set_user_data(decoration->resource, NULL); + wl_list_remove(&decoration->link); + free(decoration); +} + +static void server_decoration_destroy_resource(struct wl_resource *resource) { + struct wlr_server_decoration *decoration = + decoration_from_resource(resource); + if (decoration != NULL) { + server_decoration_destroy(decoration); + } +} + +static void server_decoration_handle_surface_destroy( + struct wl_listener *listener, void *data) { + struct wlr_server_decoration *decoration = + wl_container_of(listener, decoration, surface_destroy_listener); + server_decoration_destroy(decoration); +} + +static const struct org_kde_kwin_server_decoration_interface + server_decoration_impl = { + .release = server_decoration_handle_release, + .request_mode = server_decoration_handle_request_mode, +}; + +static const struct org_kde_kwin_server_decoration_manager_interface + server_decoration_manager_impl; + +static struct wlr_server_decoration_manager *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &org_kde_kwin_server_decoration_manager_interface, + &server_decoration_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void server_decoration_manager_handle_create(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_server_decoration_manager *manager = + manager_from_resource(manager_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_server_decoration *decoration = calloc(1, sizeof(*decoration)); + if (decoration == NULL) { + wl_client_post_no_memory(client); + return; + } + decoration->surface = surface; + decoration->mode = manager->default_mode; + + int version = wl_resource_get_version(manager_resource); + decoration->resource = wl_resource_create(client, + &org_kde_kwin_server_decoration_interface, version, id); + if (decoration->resource == NULL) { + free(decoration); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(decoration->resource, + &server_decoration_impl, decoration, + server_decoration_destroy_resource); + + wlr_log(WLR_DEBUG, "new server_decoration %p (res %p)", decoration, + decoration->resource); + + wl_signal_init(&decoration->events.destroy); + wl_signal_init(&decoration->events.mode); + + wl_signal_add(&surface->events.destroy, + &decoration->surface_destroy_listener); + decoration->surface_destroy_listener.notify = + server_decoration_handle_surface_destroy; + + wl_list_insert(&manager->decorations, &decoration->link); + + org_kde_kwin_server_decoration_send_mode(decoration->resource, + decoration->mode); + + wl_signal_emit_mutable(&manager->events.new_decoration, decoration); +} + +static const struct org_kde_kwin_server_decoration_manager_interface + server_decoration_manager_impl = { + .create = server_decoration_manager_handle_create, +}; + +void wlr_server_decoration_manager_set_default_mode( + struct wlr_server_decoration_manager *manager, uint32_t default_mode) { + manager->default_mode = default_mode; + + struct wl_resource *resource; + wl_resource_for_each(resource, &manager->resources) { + org_kde_kwin_server_decoration_manager_send_default_mode(resource, + manager->default_mode); + } +} + +static void server_decoration_manager_destroy_resource( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void server_decoration_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_server_decoration_manager *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &org_kde_kwin_server_decoration_manager_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &server_decoration_manager_impl, + manager, server_decoration_manager_destroy_resource); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); + + org_kde_kwin_server_decoration_manager_send_default_mode(resource, + manager->default_mode); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_server_decoration_manager *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_server_decoration_manager *wlr_server_decoration_manager_create( + struct wl_display *display) { + struct wlr_server_decoration_manager *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + manager->global = wl_global_create(display, + &org_kde_kwin_server_decoration_manager_interface, 1, manager, + server_decoration_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + manager->default_mode = ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_NONE; + wl_list_init(&manager->resources); + wl_list_init(&manager->decorations); + wl_signal_init(&manager->events.new_decoration); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_session_lock_v1.c b/types/wlr_session_lock_v1.c new file mode 100644 index 0000000..73874f8 --- /dev/null +++ b/types/wlr_session_lock_v1.c @@ -0,0 +1,478 @@ +#include +#include +#include +#include +#include +#include +#include +#include "ext-session-lock-v1-protocol.h" + +// Note: ext_session_lock_surface_v1 objects become inert +// when the corresponding ext_session_lock_v1 is destroyed + +#define SESSION_LOCK_VERSION 1 + +static void resource_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct ext_session_lock_manager_v1_interface lock_manager_implementation; +static const struct ext_session_lock_v1_interface lock_implementation; +static const struct ext_session_lock_surface_v1_interface lock_surface_implementation; + +static void lock_surface_destroy(struct wlr_session_lock_surface_v1 *lock_surface) { + wlr_surface_unmap(lock_surface->surface); + + wl_signal_emit_mutable(&lock_surface->events.destroy, NULL); + + wl_list_remove(&lock_surface->link); + + struct wlr_session_lock_surface_v1_configure *configure, *tmp; + wl_list_for_each_safe(configure, tmp, &lock_surface->configure_list, link) { + wl_list_remove(&configure->link); + free(configure); + } + + assert(wl_list_empty(&lock_surface->events.destroy.listener_list)); + + wl_list_remove(&lock_surface->output_destroy.link); + wlr_surface_synced_finish(&lock_surface->synced); + wl_resource_set_user_data(lock_surface->resource, NULL); + free(lock_surface); +} + +static struct wlr_session_lock_manager_v1 *lock_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_session_lock_manager_v1_interface, &lock_manager_implementation)); + struct wlr_session_lock_manager_v1 *lock_manager = + wl_resource_get_user_data(resource); + assert(lock_manager != NULL); + return lock_manager; +} + +static struct wlr_session_lock_v1 *lock_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_session_lock_v1_interface, &lock_implementation)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_session_lock_surface_v1 *lock_surface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_session_lock_surface_v1_interface, &lock_surface_implementation)); + return wl_resource_get_user_data(resource); +} + +static const struct wlr_surface_role lock_surface_role; + +struct wlr_session_lock_surface_v1 *wlr_session_lock_surface_v1_try_from_wlr_surface( + struct wlr_surface *surface) { + if (surface->role != &lock_surface_role || surface->role_resource == NULL) { + return NULL; + } + return lock_surface_from_resource(surface->role_resource); +} + +uint32_t wlr_session_lock_surface_v1_configure( + struct wlr_session_lock_surface_v1 *lock_surface, + uint32_t width, uint32_t height) { + struct wlr_session_lock_surface_v1_configure *configure = calloc(1, sizeof(*configure)); + if (configure == NULL) { + wl_resource_post_no_memory(lock_surface->resource); + return lock_surface->pending.configure_serial; + } + + struct wl_display *display = + wl_client_get_display(wl_resource_get_client(lock_surface->resource)); + + configure->width = width; + configure->height = height; + configure->serial = wl_display_next_serial(display); + + wl_list_insert(lock_surface->configure_list.prev, &configure->link); + + ext_session_lock_surface_v1_send_configure(lock_surface->resource, + configure->serial, configure->width, configure->height); + + return configure->serial; +} + +static void lock_surface_handle_ack_configure(struct wl_client *client, + struct wl_resource *resource, uint32_t serial) { + struct wlr_session_lock_surface_v1 *lock_surface = + lock_surface_from_resource(resource); + if (lock_surface == NULL) { + return; + } + + // First find the ack'ed configure + bool found = false; + struct wlr_session_lock_surface_v1_configure *configure, *tmp; + wl_list_for_each(configure, &lock_surface->configure_list, link) { + if (configure->serial == serial) { + found = true; + break; + } + } + if (!found) { + wl_resource_post_error(resource, + EXT_SESSION_LOCK_SURFACE_V1_ERROR_INVALID_SERIAL, + "ack_configure serial %" PRIu32 + " does not match any configure serial", serial); + return; + } + // Then remove old configures from the list + wl_list_for_each_safe(configure, tmp, &lock_surface->configure_list, link) { + if (configure->serial == serial) { + break; + } + wl_list_remove(&configure->link); + free(configure); + } + + lock_surface->pending.configure_serial = configure->serial; + lock_surface->pending.width = configure->width; + lock_surface->pending.height = configure->height; + + lock_surface->configured = true; + + wl_list_remove(&configure->link); + free(configure); +} + +static const struct ext_session_lock_surface_v1_interface lock_surface_implementation = { + .destroy = resource_handle_destroy, + .ack_configure = lock_surface_handle_ack_configure, +}; + +static void lock_surface_role_client_commit(struct wlr_surface *surface) { + struct wlr_session_lock_surface_v1 *lock_surface = + wlr_session_lock_surface_v1_try_from_wlr_surface(surface); + if (lock_surface == NULL) { + return; + } + + if (!wlr_surface_state_has_buffer(&surface->pending)) { + wlr_surface_reject_pending(surface, lock_surface->resource, + EXT_SESSION_LOCK_SURFACE_V1_ERROR_NULL_BUFFER, + "session lock surface is committed with a null buffer"); + return; + } + + if (!lock_surface->configured) { + wlr_surface_reject_pending(surface, lock_surface->resource, + EXT_SESSION_LOCK_SURFACE_V1_ERROR_COMMIT_BEFORE_FIRST_ACK, + "session lock surface has never been configured"); + return; + } + + if ((uint32_t)surface->pending.width != lock_surface->pending.width || + (uint32_t)surface->pending.height != lock_surface->pending.height) { + wlr_surface_reject_pending(surface, lock_surface->resource, + EXT_SESSION_LOCK_SURFACE_V1_ERROR_DIMENSIONS_MISMATCH, + "committed surface dimensions do not match last acked configure"); + return; + } +} + +static void lock_surface_role_commit(struct wlr_surface *surface) { + struct wlr_session_lock_surface_v1 *lock_surface = + wlr_session_lock_surface_v1_try_from_wlr_surface(surface); + if (lock_surface == NULL) { + return; + } + + wlr_surface_map(surface); +} + +static void lock_surface_role_destroy(struct wlr_surface *surface) { + struct wlr_session_lock_surface_v1 *lock_surface = + wlr_session_lock_surface_v1_try_from_wlr_surface(surface); + if (lock_surface == NULL) { + return; + } + lock_surface_destroy(lock_surface); +} + +static const struct wlr_surface_role lock_surface_role = { + .name = "ext_session_lock_surface_v1", + .client_commit = lock_surface_role_client_commit, + .commit = lock_surface_role_commit, + .destroy = lock_surface_role_destroy, +}; + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_session_lock_surface_v1_state), +}; + +static void lock_surface_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_session_lock_surface_v1 *lock_surface = + wl_container_of(listener, lock_surface, output_destroy); + lock_surface_destroy(lock_surface); +} + +static void lock_handle_get_lock_surface(struct wl_client *client, + struct wl_resource *lock_resource, uint32_t id, + struct wl_resource *surface_resource, + struct wl_resource *output_resource) { + // We always need to create a lock surface resource to stay in sync + // with the client, even if the lock resource or output resource is + // inert. For example, if the compositor denies the lock and immediately + // calls wlr_session_lock_v1_destroy() the client may have already sent + // get_lock_surface requests. + struct wl_resource *lock_surface_resource = wl_resource_create( + client, &ext_session_lock_surface_v1_interface, + wl_resource_get_version(lock_resource), id); + if (lock_surface_resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + // Leave the lock surface resource inert for now, we will set the + // user data at the end of this function if everything is successful. + wl_resource_set_implementation(lock_surface_resource, + &lock_surface_implementation, NULL, NULL); + + struct wlr_session_lock_v1 *lock = lock_from_resource(lock_resource); + if (lock == NULL) { + return; + } + + struct wlr_output *output = wlr_output_from_resource(output_resource); + if (output == NULL) { + return; + } + + struct wlr_session_lock_surface_v1 *other; + wl_list_for_each(other, &lock->surfaces, link) { + if (other->output == output) { + wl_resource_post_error(lock_resource, + EXT_SESSION_LOCK_V1_ERROR_DUPLICATE_OUTPUT, + "session lock surface already created for the given output"); + return; + } + } + + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + if (wlr_surface_has_buffer(surface)) { + wl_resource_post_error(lock_resource, + EXT_SESSION_LOCK_V1_ERROR_ALREADY_CONSTRUCTED, + "surface already has a buffer attached"); + return; + } + + struct wlr_session_lock_surface_v1 *lock_surface = calloc(1, sizeof(*lock_surface)); + if (lock_surface == NULL) { + wl_client_post_no_memory(client); + return; + } + + if (!wlr_surface_set_role(surface, &lock_surface_role, + lock_resource, EXT_SESSION_LOCK_V1_ERROR_ROLE)) { + free(lock_surface); + return; + } + + if (!wlr_surface_synced_init(&lock_surface->synced, surface, + &surface_synced_impl, &lock_surface->pending, &lock_surface->current)) { + free(lock_surface); + wl_client_post_no_memory(client); + return; + } + + lock_surface->resource = lock_surface_resource; + wl_resource_set_user_data(lock_surface_resource, lock_surface); + + wlr_surface_set_role_object(surface, lock_surface_resource); + + wl_list_insert(&lock->surfaces, &lock_surface->link); + + lock_surface->output = output; + lock_surface->surface = surface; + + wl_list_init(&lock_surface->configure_list); + + wl_signal_init(&lock_surface->events.destroy); + + wl_signal_add(&output->events.destroy, &lock_surface->output_destroy); + lock_surface->output_destroy.notify = lock_surface_handle_output_destroy; + + wl_signal_emit_mutable(&lock->events.new_surface, lock_surface); +} + +static void lock_handle_unlock_and_destroy(struct wl_client *client, + struct wl_resource *lock_resource) { + struct wlr_session_lock_v1 *lock = lock_from_resource(lock_resource); + if (lock == NULL) { + // This can happen if the compositor sent the locked event and + // later the finished event as the lock is destroyed when the + // finished event is sent. + wl_resource_destroy(lock_resource); + return; + } + + if (!lock->locked_sent) { + wl_resource_post_error(lock_resource, + EXT_SESSION_LOCK_V1_ERROR_INVALID_UNLOCK, + "the locked event was never sent"); + return; + } + + wl_signal_emit_mutable(&lock->events.unlock, NULL); + + wl_resource_destroy(lock_resource); +} + +static void lock_handle_destroy(struct wl_client *client, + struct wl_resource *lock_resource) { + struct wlr_session_lock_v1 *lock = lock_from_resource(lock_resource); + if (lock == NULL) { + // The compositor sent the finished event and destroyed the lock. + wl_resource_destroy(lock_resource); + return; + } + + if (lock->locked_sent) { + wl_resource_post_error(lock_resource, + EXT_SESSION_LOCK_V1_ERROR_INVALID_DESTROY, + "the session lock may not be destroyed while locked"); + } else { + wl_resource_post_error(lock_resource, + EXT_SESSION_LOCK_V1_ERROR_INVALID_DESTROY, + "the finished event was never sent"); + } +} + +static const struct ext_session_lock_v1_interface lock_implementation = { + .destroy = lock_handle_destroy, + .get_lock_surface = lock_handle_get_lock_surface, + .unlock_and_destroy = lock_handle_unlock_and_destroy, +}; + +void wlr_session_lock_v1_send_locked(struct wlr_session_lock_v1 *lock) { + assert(!lock->locked_sent); + lock->locked_sent = true; + ext_session_lock_v1_send_locked(lock->resource); +} + +static void lock_destroy(struct wlr_session_lock_v1 *lock) { + struct wlr_session_lock_surface_v1 *lock_surface, *tmp; + wl_list_for_each_safe(lock_surface, tmp, &lock->surfaces, link) { + lock_surface_destroy(lock_surface); + } + assert(wl_list_empty(&lock->surfaces)); + + wl_signal_emit_mutable(&lock->events.destroy, NULL); + + assert(wl_list_empty(&lock->events.new_surface.listener_list)); + assert(wl_list_empty(&lock->events.unlock.listener_list)); + assert(wl_list_empty(&lock->events.destroy.listener_list)); + + wl_resource_set_user_data(lock->resource, NULL); + free(lock); +} + +void wlr_session_lock_v1_destroy(struct wlr_session_lock_v1 *lock) { + ext_session_lock_v1_send_finished(lock->resource); + lock_destroy(lock); +} + +static void lock_resource_destroy(struct wl_resource *lock_resource) { + struct wlr_session_lock_v1 *lock = lock_from_resource(lock_resource); + if (lock != NULL) { + lock_destroy(lock); + } +} + +static void lock_manager_handle_lock(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id) { + struct wlr_session_lock_manager_v1 *lock_manager = + lock_manager_from_resource(manager_resource); + + struct wlr_session_lock_v1 *lock = calloc(1, sizeof(*lock)); + if (lock == NULL) { + wl_client_post_no_memory(client); + return; + } + + lock->resource = wl_resource_create(client, &ext_session_lock_v1_interface, + wl_resource_get_version(manager_resource), id); + if (lock->resource == NULL) { + free(lock); + wl_client_post_no_memory(client); + return; + } + + wl_list_init(&lock->surfaces); + + wl_signal_init(&lock->events.new_surface); + wl_signal_init(&lock->events.unlock); + wl_signal_init(&lock->events.destroy); + + wl_resource_set_implementation(lock->resource, &lock_implementation, + lock, lock_resource_destroy); + + wl_signal_emit_mutable(&lock_manager->events.new_lock, lock); +} + +static const struct ext_session_lock_manager_v1_interface lock_manager_implementation = { + .destroy = resource_handle_destroy, + .lock = lock_manager_handle_lock, +}; + +static void lock_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_session_lock_manager_v1 *lock_manager = data; + + struct wl_resource *resource = wl_resource_create( + client, &ext_session_lock_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &lock_manager_implementation, + lock_manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_session_lock_manager_v1 *lock_manager = + wl_container_of(listener, lock_manager, display_destroy); + wl_signal_emit_mutable(&lock_manager->events.destroy, NULL); + wl_list_remove(&lock_manager->display_destroy.link); + + wl_global_destroy(lock_manager->global); + + assert(wl_list_empty(&lock_manager->events.new_lock.listener_list)); + assert(wl_list_empty(&lock_manager->events.destroy.listener_list)); + + free(lock_manager); +} + +struct wlr_session_lock_manager_v1 *wlr_session_lock_manager_v1_create(struct wl_display *display) { + struct wlr_session_lock_manager_v1 *lock_manager = calloc(1, sizeof(*lock_manager)); + if (lock_manager == NULL) { + return NULL; + } + + struct wl_global *global = wl_global_create(display, + &ext_session_lock_manager_v1_interface, SESSION_LOCK_VERSION, + lock_manager, lock_manager_bind); + if (global == NULL) { + free(lock_manager); + return NULL; + } + lock_manager->global = global; + + wl_signal_init(&lock_manager->events.new_lock); + wl_signal_init(&lock_manager->events.destroy); + + lock_manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &lock_manager->display_destroy); + + return lock_manager; +} diff --git a/types/wlr_shm.c b/types/wlr_shm.c new file mode 100644 index 0000000..6160c4b --- /dev/null +++ b/types/wlr_shm.c @@ -0,0 +1,570 @@ +#undef _POSIX_C_SOURCE +#define _DEFAULT_SOURCE // for MAP_ANONYMOUS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "render/pixel_format.h" + +#ifdef __STDC_NO_ATOMICS__ +#error "C11 atomics are required" +#endif +#if ATOMIC_POINTER_LOCK_FREE == 0 +#error "Lock-free C11 atomic pointers are required" +#endif + +#define SHM_VERSION 1 + +struct wlr_shm { + struct wl_global *global; + uint32_t *formats; + size_t formats_len; + + struct wl_listener display_destroy; +}; + +struct wlr_shm_pool { + struct wl_resource *resource; // may be NULL + struct wlr_shm *shm; + struct wl_list buffers; // wlr_shm_buffer.link + int fd; + struct wlr_shm_mapping *mapping; +}; + +/** + * A mapped space to a client-owned file. + * + * Clients may resize pools via wl_shm_pool.resize, in which case we need to + * re-map the FD with a larger size. However we might be at the same time still + * accessing the old mapping (via wlr_buffer_begin_data_ptr_access()). We need + * to keep the old mapping alive in that case. + */ +struct wlr_shm_mapping { + void *data; + size_t size; + bool dropped; // false while a wlr_shm_pool references this mapping +}; + +struct wlr_shm_sigbus_data { + struct wlr_shm_mapping *mapping; + struct sigaction prev_action; + struct wlr_shm_sigbus_data *_Atomic next; +}; + +struct wlr_shm_buffer { + struct wlr_buffer base; + struct wlr_shm_pool *pool; + uint32_t drm_format; + int32_t stride; + off_t offset; + struct wl_list link; // wlr_shm_pool.buffers + struct wl_resource *resource; // may be NULL + + struct wl_listener release; + + struct wlr_shm_sigbus_data sigbus_data; +}; + +// Needs to be a lock-free atomic because it's accessed from a signal handler +static struct wlr_shm_sigbus_data *_Atomic sigbus_data = NULL; + +static const struct wl_buffer_interface wl_buffer_impl; +static const struct wl_shm_pool_interface pool_impl; +static const struct wl_shm_interface shm_impl; + +static bool buffer_resource_is_instance(struct wl_resource *resource) { + return wl_resource_instance_of(resource, &wl_buffer_interface, + &wl_buffer_impl); +} + +static struct wlr_shm_buffer *buffer_from_resource(struct wl_resource *resource) { + assert(buffer_resource_is_instance(resource)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_buffer *buffer_base_from_resource(struct wl_resource *resource) { + return &buffer_from_resource(resource)->base; +} + +static struct wlr_shm_pool *pool_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_shm_pool_interface, + &pool_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_shm *shm_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_shm_interface, &shm_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_shm_mapping *mapping_create(int fd, size_t size) { + void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + wlr_log_errno(WLR_DEBUG, "mmap failed"); + return NULL; + } + + struct wlr_shm_mapping *mapping = calloc(1, sizeof(*mapping)); + if (mapping == NULL) { + munmap(data, size); + return NULL; + } + + mapping->data = data; + mapping->size = size; + return mapping; +} + +static void mapping_consider_destroy(struct wlr_shm_mapping *mapping) { + if (!mapping->dropped) { + return; + } + + for (struct wlr_shm_sigbus_data *cur = sigbus_data; cur != NULL; cur = cur->next) { + if (cur->mapping == mapping) { + return; + } + } + + munmap(mapping->data, mapping->size); + free(mapping); +} + +/** + * Indicate that this mapping is no longer used by its wlr_shm_pool owner. + * + * May destroy the mapping. + */ +static void mapping_drop(struct wlr_shm_mapping *mapping) { + if (mapping == NULL) { + return; + } + + mapping->dropped = true; + mapping_consider_destroy(mapping); +} + +static const struct wlr_buffer_resource_interface buffer_resource_interface = { + .name = "wl_shm", + .is_instance = buffer_resource_is_instance, + .from_resource = buffer_base_from_resource, +}; + +static void pool_consider_destroy(struct wlr_shm_pool *pool); + +static void buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct wlr_shm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + assert(buffer->resource == NULL); + wl_list_remove(&buffer->release.link); + wl_list_remove(&buffer->link); + pool_consider_destroy(buffer->pool); + free(buffer); +} + +static bool buffer_get_shm(struct wlr_buffer *wlr_buffer, + struct wlr_shm_attributes *attrs) { + struct wlr_shm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + *attrs = (struct wlr_shm_attributes){ + .fd = buffer->pool->fd, + .format = buffer->drm_format, + .width = buffer->base.width, + .height = buffer->base.height, + .stride = buffer->stride, + .offset = buffer->offset, + }; + return true; +} + +static void handle_sigbus(int sig, siginfo_t *info, void *context) { + assert(sigbus_data != NULL); + struct sigaction prev_action = sigbus_data->prev_action; + + // Check whether the offending address is inside of the wl_shm_pool's mapped + // space + uintptr_t addr = (uintptr_t)info->si_addr; + struct wlr_shm_mapping *mapping = NULL; + for (struct wlr_shm_sigbus_data *data = sigbus_data; data != NULL; data = data->next) { + uintptr_t mapping_start = (uintptr_t)data->mapping->data; + size_t mapping_size = data->mapping->size; + if (addr >= mapping_start && addr < mapping_start + mapping_size) { + mapping = data->mapping; + break; + } + } + if (mapping == NULL) { + goto reraise; + } + + // Replace the mapping with a new one which won't cause SIGBUS (instead, it + // will read as zeroes). Technically mmap() isn't part of the + // async-signal-safe functions... + if (mmap(mapping->data, mapping->size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED) { + goto reraise; + } + + return; + +reraise: + if (prev_action.sa_flags & SA_SIGINFO) { + prev_action.sa_sigaction(sig, info, context); + } else { + prev_action.sa_handler(sig); + } +} + +static bool buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, + uint32_t flags, void **data, uint32_t *format, size_t *stride) { + struct wlr_shm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + + if (!atomic_is_lock_free(&sigbus_data)) { + wlr_log(WLR_ERROR, "Lock-free atomic pointers are required"); + return false; + } + + // Install a SIGBUS handler. SIGBUS is triggered if the client shrinks the + // backing file, and then we try to access the mapping. + struct sigaction prev_action; + if (sigbus_data == NULL) { + struct sigaction new_action = { + .sa_sigaction = handle_sigbus, + .sa_flags = SA_SIGINFO | SA_NODEFER, + }; + if (sigaction(SIGBUS, &new_action, &prev_action) != 0) { + wlr_log_errno(WLR_ERROR, "sigaction failed"); + return false; + } + } else { + prev_action = sigbus_data->prev_action; + } + + struct wlr_shm_mapping *mapping = buffer->pool->mapping; + + buffer->sigbus_data = (struct wlr_shm_sigbus_data){ + .mapping = mapping, + .prev_action = prev_action, + .next = sigbus_data, + }; + sigbus_data = &buffer->sigbus_data; + + *data = (char *)mapping->data + buffer->offset; + *format = buffer->drm_format; + *stride = buffer->stride; + return true; +} + +static void buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { + struct wlr_shm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + + if (sigbus_data == &buffer->sigbus_data) { + sigbus_data = buffer->sigbus_data.next; + } else { + for (struct wlr_shm_sigbus_data *cur = sigbus_data; cur != NULL; cur = cur->next) { + if (cur->next == &buffer->sigbus_data) { + cur->next = buffer->sigbus_data.next; + break; + } + } + } + + if (sigbus_data == NULL) { + if (sigaction(SIGBUS, &buffer->sigbus_data.prev_action, NULL) != 0) { + wlr_log_errno(WLR_ERROR, "sigaction failed"); + } + } + + mapping_consider_destroy(buffer->sigbus_data.mapping); +} + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = buffer_destroy, + .get_shm = buffer_get_shm, + .begin_data_ptr_access = buffer_begin_data_ptr_access, + .end_data_ptr_access = buffer_end_data_ptr_access, +}; + +static void destroy_resource(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_buffer_interface wl_buffer_impl = { + .destroy = destroy_resource, +}; + +static void buffer_handle_release(struct wl_listener *listener, void *data) { + struct wlr_shm_buffer *buffer = wl_container_of(listener, buffer, release); + if (buffer->resource != NULL) { + wl_buffer_send_release(buffer->resource); + } +} + +static void buffer_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_shm_buffer *buffer = buffer_from_resource(resource); + buffer->resource = NULL; + wlr_buffer_drop(&buffer->base); +} + +static bool shm_has_format(struct wlr_shm *shm, uint32_t shm_format); + +static void pool_handle_create_buffer(struct wl_client *client, + struct wl_resource *pool_resource, uint32_t id, int32_t offset, + int32_t width, int32_t height, int32_t stride, uint32_t shm_format) { + struct wlr_shm_pool *pool = pool_from_resource(pool_resource); + + // Convert to uint64_t to avoid integer overflow + if (offset < 0 || width <= 0 || height <= 0 || stride < width || + offset + (uint64_t)stride * height > pool->mapping->size) { + wl_resource_post_error(pool_resource, WL_SHM_ERROR_INVALID_STRIDE, + "Invalid width, height or stride (%dx%d, %d)", + width, height, stride); + return; + } + + if (!shm_has_format(pool->shm, shm_format)) { + wl_resource_post_error(pool_resource, WL_SHM_ERROR_INVALID_FORMAT, + "Unsupported format"); + return; + } + + uint32_t drm_format = convert_wl_shm_format_to_drm(shm_format); + const struct wlr_pixel_format_info *format_info = + drm_get_pixel_format_info(drm_format); + if (format_info == NULL) { + wl_resource_post_error(pool_resource, WL_SHM_ERROR_INVALID_FORMAT, + "Unknown format"); + return; + } + if (!pixel_format_info_check_stride(format_info, stride, width)) { + wl_resource_post_error(pool_resource, WL_SHM_ERROR_INVALID_STRIDE, + "Invalid stride (%d)", stride); + return; + } + + struct wlr_shm_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + wl_resource_post_no_memory(pool_resource); + return; + } + + buffer->resource = wl_resource_create(client, &wl_buffer_interface, 1, id); + if (buffer->resource == NULL) { + free(buffer); + wl_resource_post_no_memory(pool_resource); + return; + } + + buffer->pool = pool; + buffer->offset = offset; + buffer->stride = stride; + buffer->drm_format = drm_format; + wlr_buffer_init(&buffer->base, &buffer_impl, width, height); + wl_resource_set_implementation(buffer->resource, + &wl_buffer_impl, buffer, buffer_handle_resource_destroy); + + wl_list_insert(&pool->buffers, &buffer->link); + + buffer->release.notify = buffer_handle_release; + wl_signal_add(&buffer->base.events.release, &buffer->release); +} + +static void pool_handle_resize(struct wl_client *client, + struct wl_resource *pool_resource, int32_t size) { + struct wlr_shm_pool *pool = pool_from_resource(pool_resource); + + if (size <= 0 || (size_t)size < pool->mapping->size) { + wl_resource_post_error(pool_resource, WL_SHM_ERROR_INVALID_STRIDE, + "Shrinking a pool (%zu to %d) is forbidden", + pool->mapping->size, size); + return; + } + + struct wlr_shm_mapping *mapping = mapping_create(pool->fd, size); + if (mapping == NULL) { + wl_resource_post_error(pool_resource, WL_SHM_ERROR_INVALID_FD, + "Failed to create memory mapping"); + return; + } + + mapping_drop(pool->mapping); + pool->mapping = mapping; +} + +static const struct wl_shm_pool_interface pool_impl = { + .create_buffer = pool_handle_create_buffer, + .destroy = destroy_resource, + .resize = pool_handle_resize, +}; + +static void pool_consider_destroy(struct wlr_shm_pool *pool) { + if (pool->resource != NULL || !wl_list_empty(&pool->buffers)) { + return; + } + + mapping_drop(pool->mapping); + close(pool->fd); + free(pool); +} + +static void pool_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_shm_pool *pool = pool_from_resource(resource); + pool->resource = NULL; + pool_consider_destroy(pool); +} + +static void shm_handle_create_pool(struct wl_client *client, + struct wl_resource *shm_resource, uint32_t id, int fd, int32_t size) { + struct wlr_shm *shm = shm_from_resource(shm_resource); + + if (size <= 0) { + wl_resource_post_error(shm_resource, WL_SHM_ERROR_INVALID_STRIDE, + "Invalid size (%d)", size); + goto error_fd; + } + + struct wlr_shm_mapping *mapping = mapping_create(fd, size); + if (mapping == NULL) { + wl_resource_post_error(shm_resource, WL_SHM_ERROR_INVALID_FD, + "Failed to create memory mapping"); + goto error_fd; + } + + struct wlr_shm_pool *pool = calloc(1, sizeof(*pool)); + if (pool == NULL) { + wl_resource_post_no_memory(shm_resource); + goto error_mapping; + } + + uint32_t version = wl_resource_get_version(shm_resource); + pool->resource = + wl_resource_create(client, &wl_shm_pool_interface, version, id); + if (pool->resource == NULL) { + wl_resource_post_no_memory(shm_resource); + goto error_pool; + } + wl_resource_set_implementation(pool->resource, &pool_impl, pool, + pool_handle_resource_destroy); + + pool->mapping = mapping; + pool->shm = shm; + pool->fd = fd; + wl_list_init(&pool->buffers); + return; + +error_pool: + free(pool); +error_mapping: + mapping_drop(mapping); +error_fd: + close(fd); +} + +static const struct wl_shm_interface shm_impl = { + .create_pool = shm_handle_create_pool, +}; + +static void shm_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_shm *shm = data; + + struct wl_resource *resource = wl_resource_create(client, &wl_shm_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &shm_impl, shm, NULL); + + for (size_t i = 0; i < shm->formats_len; i++) { + wl_shm_send_format(resource, shm->formats[i]); + } +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_shm *shm = wl_container_of(listener, shm, display_destroy); + wl_list_remove(&shm->display_destroy.link); + wl_global_destroy(shm->global); + free(shm->formats); + free(shm); +} + +struct wlr_shm *wlr_shm_create(struct wl_display *display, uint32_t version, + const uint32_t *formats, size_t formats_len) { + // ARGB8888 and XRGB8888 must be supported per the wl_shm spec + bool has_argb8888 = false, has_xrgb8888 = false; + for (size_t i = 0; i < formats_len; i++) { + switch (formats[i]) { + case DRM_FORMAT_ARGB8888: + has_argb8888 = true; + break; + case DRM_FORMAT_XRGB8888: + has_xrgb8888 = true; + break; + } + } + assert(has_argb8888 && has_xrgb8888); + + struct wlr_shm *shm = calloc(1, sizeof(*shm)); + if (shm == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return NULL; + } + + shm->formats_len = formats_len; + shm->formats = malloc(formats_len * sizeof(uint32_t)); + if (shm->formats == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + free(shm); + return NULL; + } + for (size_t i = 0; i < formats_len; i++) { + shm->formats[i] = convert_drm_format_to_wl_shm(formats[i]); + } + + shm->global = wl_global_create(display, &wl_shm_interface, SHM_VERSION, + shm, shm_bind); + if (shm->global == NULL) { + wlr_log(WLR_ERROR, "wl_global_create failed"); + free(shm->formats); + free(shm); + return NULL; + } + + shm->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &shm->display_destroy); + + wlr_buffer_register_resource_interface(&buffer_resource_interface); + + return shm; +} + +struct wlr_shm *wlr_shm_create_with_renderer(struct wl_display *display, + uint32_t version, struct wlr_renderer *renderer) { + size_t formats_len; + const uint32_t *formats = + wlr_renderer_get_shm_texture_formats(renderer, &formats_len); + if (formats == NULL) { + wlr_log(WLR_ERROR, "Failed to initialize wl_shm: " + "cannot get renderer formats"); + return NULL; + } + + return wlr_shm_create(display, version, formats, formats_len); +} + +static bool shm_has_format(struct wlr_shm *shm, uint32_t shm_format) { + for (size_t i = 0; i < shm->formats_len; i++) { + if (shm->formats[i] == shm_format) { + return true; + } + } + return false; +} diff --git a/types/wlr_single_pixel_buffer_v1.c b/types/wlr_single_pixel_buffer_v1.c new file mode 100644 index 0000000..0ec8f73 --- /dev/null +++ b/types/wlr_single_pixel_buffer_v1.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include +#include "single-pixel-buffer-v1-protocol.h" + +#define SINGLE_PIXEL_MANAGER_VERSION 1 + +struct wlr_single_pixel_buffer_manager_v1 { + struct wl_global *global; + + struct wl_listener display_destroy; +}; + +struct wlr_single_pixel_buffer_v1 { + struct wlr_buffer base; + struct wl_resource *resource; + uint32_t r, g, b, a; + uint8_t argb8888[4]; // packed little-endian DRM_FORMAT_ARGB8888 + + struct wl_listener release; +}; + +static void destroy_resource(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_buffer_interface wl_buffer_impl = { + .destroy = destroy_resource, +}; + +static const struct wlr_buffer_impl buffer_impl; + +static bool buffer_resource_is_instance(struct wl_resource *resource) { + return wl_resource_instance_of(resource, &wl_buffer_interface, + &wl_buffer_impl); +} + +static struct wlr_single_pixel_buffer_v1 *single_pixel_buffer_v1_from_resource( + struct wl_resource *resource) { + assert(buffer_resource_is_instance(resource)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_buffer *buffer_from_resource( + struct wl_resource *resource) { + return &single_pixel_buffer_v1_from_resource(resource)->base; +} + +static const struct wlr_buffer_resource_interface buffer_resource_interface = { + .name = "single_pixel_buffer_v1", + .is_instance = buffer_resource_is_instance, + .from_resource = buffer_from_resource, +}; + +static void buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct wlr_single_pixel_buffer_v1 *buffer = + wl_container_of(wlr_buffer, buffer, base); + if (buffer->resource != NULL) { + wl_resource_set_user_data(buffer->resource, NULL); + } + wl_list_remove(&buffer->release.link); + free(buffer); +} + +static bool buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, + uint32_t flags, void **data, uint32_t *format, size_t *stride) { + struct wlr_single_pixel_buffer_v1 *buffer = + wl_container_of(wlr_buffer, buffer, base); + if (flags & ~WLR_BUFFER_DATA_PTR_ACCESS_READ) { + return false; // the buffer is read-only + } + *data = &buffer->argb8888; + *format = DRM_FORMAT_ARGB8888; + *stride = sizeof(buffer->argb8888); + return true; +} + +static void buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { + // This space is intentionally left blank +} + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = buffer_destroy, + .begin_data_ptr_access = buffer_begin_data_ptr_access, + .end_data_ptr_access = buffer_end_data_ptr_access, +}; + +static void buffer_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_single_pixel_buffer_v1 *buffer = single_pixel_buffer_v1_from_resource(resource); + buffer->resource = NULL; + wlr_buffer_drop(&buffer->base); +} + +static void buffer_handle_release(struct wl_listener *listener, void *data) { + struct wlr_single_pixel_buffer_v1 *buffer = wl_container_of(listener, buffer, release); + if (buffer->resource != NULL) { + wl_buffer_send_release(buffer->resource); + } +} + +static void manager_handle_create_u32_rgba_buffer(struct wl_client *client, + struct wl_resource *resource, uint32_t id, uint32_t r, uint32_t g, + uint32_t b, uint32_t a) { + struct wlr_single_pixel_buffer_v1 *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + wl_client_post_no_memory(client); + return; + } + + buffer->resource = wl_resource_create(client, &wl_buffer_interface, 1, id); + if (buffer->resource == NULL) { + wl_client_post_no_memory(client); + free(buffer); + return; + } + + wlr_buffer_init(&buffer->base, &buffer_impl, 1, 1); + wl_resource_set_implementation(buffer->resource, + &wl_buffer_impl, buffer, buffer_handle_resource_destroy); + + buffer->r = r; + buffer->g = g; + buffer->b = b; + buffer->a = a; + + double f = (double)0xFF / 0xFFFFFFFF; + buffer->argb8888[0] = (uint8_t)((double)buffer->b * f); + buffer->argb8888[1] = (uint8_t)((double)buffer->g * f); + buffer->argb8888[2] = (uint8_t)((double)buffer->r * f); + buffer->argb8888[3] = (uint8_t)((double)buffer->a * f); + + buffer->release.notify = buffer_handle_release; + wl_signal_add(&buffer->base.events.release, &buffer->release); +} + +static const struct wp_single_pixel_buffer_manager_v1_interface manager_impl = { + .destroy = destroy_resource, + .create_u32_rgba_buffer = manager_handle_create_u32_rgba_buffer, +}; + +static void manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wl_resource *resource = wl_resource_create(client, + &wp_single_pixel_buffer_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, NULL, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_single_pixel_buffer_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_single_pixel_buffer_manager_v1 *wlr_single_pixel_buffer_manager_v1_create( + struct wl_display *display) { + struct wlr_single_pixel_buffer_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &wp_single_pixel_buffer_manager_v1_interface, + SINGLE_PIXEL_MANAGER_VERSION, + NULL, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + wlr_buffer_register_resource_interface(&buffer_resource_interface); + + return manager; +} diff --git a/types/wlr_subcompositor.c b/types/wlr_subcompositor.c new file mode 100644 index 0000000..771b987 --- /dev/null +++ b/types/wlr_subcompositor.c @@ -0,0 +1,437 @@ +#include +#include +#include +#include +#include +#include "types/wlr_region.h" +#include "types/wlr_subcompositor.h" + +// Note: wl_subsurface becomes inert on parent surface destroy + +#define SUBCOMPOSITOR_VERSION 1 + +static bool subsurface_is_synchronized(struct wlr_subsurface *subsurface) { + while (subsurface != NULL) { + if (subsurface->synchronized) { + return true; + } + + subsurface = wlr_subsurface_try_from_wlr_surface(subsurface->parent); + } + + return false; +} + +static const struct wl_subsurface_interface subsurface_implementation; + +static void subsurface_destroy(struct wlr_subsurface *subsurface) { + if (subsurface->has_cache) { + wlr_surface_unlock_cached(subsurface->surface, subsurface->cached_seq); + } + + wlr_surface_unmap(subsurface->surface); + + wl_signal_emit_mutable(&subsurface->events.destroy, subsurface); + + wlr_surface_synced_finish(&subsurface->parent_synced); + + wl_list_remove(&subsurface->surface_client_commit.link); + wl_list_remove(&subsurface->parent_destroy.link); + + wl_resource_set_user_data(subsurface->resource, NULL); + free(subsurface); +} + +/** + * Get a wlr_subsurface from a wl_subsurface resource. + * + * Returns NULL if the subsurface is inert (e.g. the wl_surface object or the + * parent surface got destroyed). + */ +static struct wlr_subsurface *subsurface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_subsurface_interface, + &subsurface_implementation)); + return wl_resource_get_user_data(resource); +} + +static void subsurface_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void subsurface_handle_set_position(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y) { + struct wlr_subsurface *subsurface = subsurface_from_resource(resource); + if (subsurface == NULL) { + return; + } + + subsurface->pending.x = x; + subsurface->pending.y = y; +} + +static struct wlr_subsurface *subsurface_find_sibling( + struct wlr_subsurface *subsurface, struct wlr_surface *surface) { + struct wlr_surface *parent = subsurface->parent; + + struct wlr_subsurface *sibling; + wl_list_for_each(sibling, &parent->pending.subsurfaces_below, pending.link) { + if (sibling->surface == surface && sibling != subsurface) { + return sibling; + } + } + wl_list_for_each(sibling, &parent->pending.subsurfaces_above, pending.link) { + if (sibling->surface == surface && sibling != subsurface) { + return sibling; + } + } + + return NULL; +} + +static void subsurface_handle_place_above(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *sibling_resource) { + struct wlr_subsurface *subsurface = subsurface_from_resource(resource); + if (subsurface == NULL) { + return; + } + + struct wlr_surface *sibling_surface = + wlr_surface_from_resource(sibling_resource); + + struct wl_list *node; + if (sibling_surface == subsurface->parent) { + node = &subsurface->parent->pending.subsurfaces_above; + } else { + struct wlr_subsurface *sibling = + subsurface_find_sibling(subsurface, sibling_surface); + if (!sibling) { + wl_resource_post_error(subsurface->resource, + WL_SUBSURFACE_ERROR_BAD_SURFACE, + "%s: wl_surface@%" PRIu32 "is not a parent or sibling", + "place_above", wl_resource_get_id(sibling_resource)); + return; + } + node = &sibling->pending.link; + } + + wl_list_remove(&subsurface->pending.link); + wl_list_insert(node, &subsurface->pending.link); +} + +static void subsurface_handle_place_below(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *sibling_resource) { + struct wlr_subsurface *subsurface = subsurface_from_resource(resource); + if (subsurface == NULL) { + return; + } + + struct wlr_surface *sibling_surface = + wlr_surface_from_resource(sibling_resource); + + struct wl_list *node; + if (sibling_surface == subsurface->parent) { + node = &subsurface->parent->pending.subsurfaces_below; + } else { + struct wlr_subsurface *sibling = + subsurface_find_sibling(subsurface, sibling_surface); + if (!sibling) { + wl_resource_post_error(subsurface->resource, + WL_SUBSURFACE_ERROR_BAD_SURFACE, + "%s: wl_surface@%" PRIu32 " is not a parent or sibling", + "place_below", wl_resource_get_id(sibling_resource)); + return; + } + node = &sibling->pending.link; + } + + wl_list_remove(&subsurface->pending.link); + wl_list_insert(node->prev, &subsurface->pending.link); +} + +static void subsurface_handle_set_sync(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_subsurface *subsurface = subsurface_from_resource(resource); + if (subsurface == NULL) { + return; + } + + subsurface->synchronized = true; +} + +static void subsurface_handle_set_desync(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_subsurface *subsurface = subsurface_from_resource(resource); + if (subsurface == NULL) { + return; + } + + if (subsurface->synchronized) { + subsurface->synchronized = false; + + if (!subsurface_is_synchronized(subsurface) && + subsurface->has_cache) { + wlr_surface_unlock_cached(subsurface->surface, + subsurface->cached_seq); + subsurface->has_cache = false; + } + } +} + +static const struct wl_subsurface_interface subsurface_implementation = { + .destroy = subsurface_handle_destroy, + .set_position = subsurface_handle_set_position, + .place_above = subsurface_handle_place_above, + .place_below = subsurface_handle_place_below, + .set_sync = subsurface_handle_set_sync, + .set_desync = subsurface_handle_set_desync, +}; + +const struct wlr_surface_role subsurface_role; + +void subsurface_consider_map(struct wlr_subsurface *subsurface) { + if (subsurface->added && subsurface->parent->mapped && + wlr_surface_has_buffer(subsurface->surface)) { + wlr_surface_map(subsurface->surface); + } +} + +static void subsurface_role_commit(struct wlr_surface *surface) { + struct wlr_subsurface *subsurface = wlr_subsurface_try_from_wlr_surface(surface); + if (subsurface == NULL) { + return; + } + + subsurface_consider_map(subsurface); +} + +static void subsurface_role_destroy(struct wlr_surface *surface) { + struct wlr_subsurface *subsurface = wlr_subsurface_try_from_wlr_surface(surface); + if (subsurface == NULL) { + return; + } + + subsurface_destroy(subsurface); +} + +const struct wlr_surface_role subsurface_role = { + .name = "wl_subsurface", + .commit = subsurface_role_commit, + .destroy = subsurface_role_destroy, +}; + +static void surface_synced_init_state(void *_state) { + struct wlr_subsurface_parent_state *state = _state; + wl_list_init(&state->link); +} + +static void surface_synced_finish_state(void *_state) { + struct wlr_subsurface_parent_state *state = _state; + wl_list_remove(&state->link); +} + +static void surface_synced_move_state(void *_dst, void *_src) { + struct wlr_subsurface_parent_state *dst = _dst, *src = _src; + dst->x = src->x; + dst->y = src->y; + dst->synced = src->synced; + + // For the sake of simplicity, copying the position in list is done by the + // parent itself +} + +static struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_subsurface_parent_state), + .init_state = surface_synced_init_state, + .finish_state = surface_synced_finish_state, + .move_state = surface_synced_move_state, +}; + +static void subsurface_handle_parent_destroy(struct wl_listener *listener, + void *data) { + struct wlr_subsurface *subsurface = + wl_container_of(listener, subsurface, parent_destroy); + // Once the parent is destroyed, the client has no way to use the + // wl_subsurface object anymore, so we can destroy it. + subsurface_destroy(subsurface); +} + +static void subsurface_handle_surface_client_commit( + struct wl_listener *listener, void *data) { + struct wlr_subsurface *subsurface = + wl_container_of(listener, subsurface, surface_client_commit); + struct wlr_surface *surface = subsurface->surface; + + if (subsurface_is_synchronized(subsurface)) { + if (subsurface->has_cache) { + // We already lock a previous commit. The prevents any future + // commit to be applied before we release the previous commit. + return; + } + subsurface->has_cache = true; + subsurface->cached_seq = wlr_surface_lock_pending(surface); + } else if (subsurface->has_cache) { + wlr_surface_unlock_cached(surface, subsurface->cached_seq); + subsurface->has_cache = false; + } +} + +void subsurface_handle_parent_commit(struct wlr_subsurface *subsurface) { + if (subsurface->synchronized && subsurface->has_cache) { + wlr_surface_unlock_cached(subsurface->surface, subsurface->cached_seq); + subsurface->has_cache = false; + } + + if (!subsurface->added) { + subsurface->added = true; + wl_signal_emit_mutable(&subsurface->parent->events.new_subsurface, + subsurface); + subsurface_consider_map(subsurface); + } + + subsurface->previous.x = subsurface->current.x; + subsurface->previous.y = subsurface->current.y; +} + +struct wlr_subsurface *wlr_subsurface_try_from_wlr_surface(struct wlr_surface *surface) { + if (surface->role != &subsurface_role || surface->role_resource == NULL) { + return NULL; + } + return subsurface_from_resource(surface->role_resource); +} + +static void subcompositor_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void subcompositor_handle_get_subsurface(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *surface_resource, + struct wl_resource *parent_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + struct wlr_surface *parent = wlr_surface_from_resource(parent_resource); + + struct wlr_subsurface *subsurface = calloc(1, sizeof(*subsurface)); + if (!subsurface) { + wl_client_post_no_memory(client); + return; + } + + if (!wlr_surface_set_role(surface, &subsurface_role, + resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE)) { + free(subsurface); + return; + } + + if (wlr_surface_get_root_surface(parent) == surface) { + wl_resource_post_error(resource, + WL_SUBCOMPOSITOR_ERROR_BAD_PARENT, + "wl_subsurface@%" PRIu32 + " cannot be a parent of itself or its ancestor", id); + free(subsurface); + return; + } + + if (!wlr_surface_synced_init(&subsurface->parent_synced, parent, &surface_synced_impl, + &subsurface->pending, &subsurface->current)) { + free(subsurface); + wl_client_post_no_memory(client); + return; + } + + subsurface->synchronized = true; + subsurface->surface = surface; + subsurface->resource = wl_resource_create(client, &wl_subsurface_interface, + wl_resource_get_version(resource), id); + if (subsurface->resource == NULL) { + wlr_surface_synced_finish(&subsurface->parent_synced); + free(subsurface); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(subsurface->resource, + &subsurface_implementation, subsurface, NULL); + + // In surface_state_move() we commit sub-surface order. To do so we need to + // iterate over the list of sub-surfaces from a struct wlr_surface_state. + // Store a pointer to struct wlr_surface_synced to facilitate this. + subsurface->pending.synced = &subsurface->parent_synced; + subsurface->current.synced = &subsurface->parent_synced; + + struct wlr_surface_state *cached; + wl_list_for_each(cached, &parent->cached, cached_state_link) { + struct wlr_subsurface_parent_state *sub_state = + wlr_surface_synced_get_state(&subsurface->parent_synced, cached); + sub_state->synced = &subsurface->parent_synced; + } + + wlr_surface_set_role_object(surface, subsurface->resource); + + wl_signal_init(&subsurface->events.destroy); + + wl_signal_add(&surface->events.client_commit, + &subsurface->surface_client_commit); + subsurface->surface_client_commit.notify = + subsurface_handle_surface_client_commit; + + // link parent + subsurface->parent = parent; + wl_signal_add(&parent->events.destroy, &subsurface->parent_destroy); + subsurface->parent_destroy.notify = subsurface_handle_parent_destroy; + + wl_list_remove(&subsurface->pending.link); + wl_list_insert(parent->pending.subsurfaces_above.prev, + &subsurface->pending.link); +} + +static const struct wl_subcompositor_interface subcompositor_impl = { + .destroy = subcompositor_handle_destroy, + .get_subsurface = subcompositor_handle_get_subsurface, +}; + +static void subcompositor_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_subcompositor *subcompositor = data; + struct wl_resource *resource = + wl_resource_create(client, &wl_subcompositor_interface, 1, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &subcompositor_impl, + subcompositor, NULL); +} + +static void subcompositor_handle_display_destroy( + struct wl_listener *listener, void *data) { + struct wlr_subcompositor *subcompositor = + wl_container_of(listener, subcompositor, display_destroy); + wl_signal_emit_mutable(&subcompositor->events.destroy, NULL); + wl_list_remove(&subcompositor->display_destroy.link); + wl_global_destroy(subcompositor->global); + free(subcompositor); +} + +struct wlr_subcompositor *wlr_subcompositor_create(struct wl_display *display) { + struct wlr_subcompositor *subcompositor = calloc(1, sizeof(*subcompositor)); + if (!subcompositor) { + return NULL; + } + + subcompositor->global = wl_global_create(display, + &wl_subcompositor_interface, SUBCOMPOSITOR_VERSION, + subcompositor, subcompositor_bind); + if (!subcompositor->global) { + free(subcompositor); + return NULL; + } + + wl_signal_init(&subcompositor->events.destroy); + + subcompositor->display_destroy.notify = subcompositor_handle_display_destroy; + wl_display_add_destroy_listener(display, &subcompositor->display_destroy); + + return subcompositor; +} diff --git a/types/wlr_switch.c b/types/wlr_switch.c new file mode 100644 index 0000000..93033aa --- /dev/null +++ b/types/wlr_switch.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include +#include + +#include "interfaces/wlr_input_device.h" + +struct wlr_switch *wlr_switch_from_input_device( + struct wlr_input_device *input_device) { + assert(input_device->type == WLR_INPUT_DEVICE_SWITCH); + return wl_container_of(input_device, (struct wlr_switch *)NULL, base); +} + +void wlr_switch_init(struct wlr_switch *switch_device, + const struct wlr_switch_impl *impl, const char *name) { + *switch_device = (struct wlr_switch){ + .impl = impl, + }; + wlr_input_device_init(&switch_device->base, WLR_INPUT_DEVICE_SWITCH, name); + + wl_signal_init(&switch_device->events.toggle); +} + +void wlr_switch_finish(struct wlr_switch *switch_device) { + wlr_input_device_finish(&switch_device->base); +} diff --git a/types/wlr_tablet_pad.c b/types/wlr_tablet_pad.c new file mode 100644 index 0000000..2c4c04a --- /dev/null +++ b/types/wlr_tablet_pad.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "interfaces/wlr_input_device.h" + +struct wlr_tablet_pad *wlr_tablet_pad_from_input_device( + struct wlr_input_device *input_device) { + assert(input_device->type == WLR_INPUT_DEVICE_TABLET_PAD); + return wl_container_of(input_device, (struct wlr_tablet_pad *)NULL, base); +} + +void wlr_tablet_pad_init(struct wlr_tablet_pad *pad, + const struct wlr_tablet_pad_impl *impl, const char *name) { + *pad = (struct wlr_tablet_pad){ + .impl = impl, + }; + wlr_input_device_init(&pad->base, WLR_INPUT_DEVICE_TABLET_PAD, name); + + wl_signal_init(&pad->events.button); + wl_signal_init(&pad->events.ring); + wl_signal_init(&pad->events.strip); + wl_signal_init(&pad->events.attach_tablet); + + wl_list_init(&pad->groups); + wl_array_init(&pad->paths); +} + +void wlr_tablet_pad_finish(struct wlr_tablet_pad *pad) { + wlr_input_device_finish(&pad->base); + + char **path_ptr; + wl_array_for_each(path_ptr, &pad->paths) { + free(*path_ptr); + } + wl_array_release(&pad->paths); + + /* TODO: wlr_tablet_pad should own its wlr_tablet_pad_group */ + if (!wl_list_empty(&pad->groups)) { + wlr_log(WLR_ERROR, "wlr_tablet_pad groups is not empty"); + } +} diff --git a/types/wlr_tablet_tool.c b/types/wlr_tablet_tool.c new file mode 100644 index 0000000..77f4631 --- /dev/null +++ b/types/wlr_tablet_tool.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include +#include + +#include "interfaces/wlr_input_device.h" + +struct wlr_tablet *wlr_tablet_from_input_device( + struct wlr_input_device *input_device) { + assert(input_device->type == WLR_INPUT_DEVICE_TABLET); + return wl_container_of(input_device, (struct wlr_tablet *)NULL, base); +} + +void wlr_tablet_init(struct wlr_tablet *tablet, + const struct wlr_tablet_impl *impl, const char *name) { + *tablet = (struct wlr_tablet){ + .impl = impl, + }; + wlr_input_device_init(&tablet->base, WLR_INPUT_DEVICE_TABLET, name); + + wl_signal_init(&tablet->events.axis); + wl_signal_init(&tablet->events.proximity); + wl_signal_init(&tablet->events.tip); + wl_signal_init(&tablet->events.button); + wl_array_init(&tablet->paths); +} + +void wlr_tablet_finish(struct wlr_tablet *tablet) { + wlr_input_device_finish(&tablet->base); + + char **path_ptr; + wl_array_for_each(path_ptr, &tablet->paths) { + free(*path_ptr); + } + wl_array_release(&tablet->paths); +} diff --git a/types/wlr_tearing_control_v1.c b/types/wlr_tearing_control_v1.c new file mode 100644 index 0000000..cd7cd5a --- /dev/null +++ b/types/wlr_tearing_control_v1.c @@ -0,0 +1,223 @@ + +#include +#include +#include +#include +#include +#include + +#include "tearing-control-v1-protocol.h" + +#define TEARING_CONTROL_MANAGER_VERSION 1 + +static const struct wp_tearing_control_manager_v1_interface tearing_impl; +static const struct wp_tearing_control_v1_interface tearing_control_impl; + +static struct wlr_tearing_control_manager_v1 *tearing_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_tearing_control_manager_v1_interface, + &tearing_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_tearing_control_v1 *tearing_surface_hint_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_tearing_control_v1_interface, + &tearing_control_impl)); + return wl_resource_get_user_data(resource); +} + +static void destroy_tearing_hint(struct wlr_tearing_control_v1 *hint) { + if (hint == NULL) { + return; + } + + wl_signal_emit_mutable(&hint->events.destroy, NULL); + + wl_list_remove(&hint->link); + wl_resource_set_user_data(hint->resource, NULL); + + wlr_addon_finish(&hint->addon); + wlr_surface_synced_finish(&hint->synced); + wl_list_remove(&hint->surface_commit.link); + + free(hint); +} + +static void surface_addon_destroy(struct wlr_addon *addon) { + struct wlr_tearing_control_v1 *hint = wl_container_of(addon, hint, addon); + + destroy_tearing_hint(hint); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "wp_tearing_control_v1", + .destroy = surface_addon_destroy, +}; + +static void resource_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void destroy_tearing_resource_impl(struct wl_resource *resource) { + struct wlr_tearing_control_v1 *hint = tearing_surface_hint_from_resource(resource); + destroy_tearing_hint(hint); +} + +static void tearing_control_handle_set_presentation_hint(struct wl_client *client, + struct wl_resource *resource, uint32_t hint) { + struct wlr_tearing_control_v1 *surface_hint = + tearing_surface_hint_from_resource(resource); + surface_hint->pending = hint; +} + +static const struct wp_tearing_control_v1_interface tearing_control_impl = { + .destroy = resource_handle_destroy, + .set_presentation_hint = tearing_control_handle_set_presentation_hint, +}; + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(enum wp_tearing_control_v1_presentation_hint), +}; + +static void hint_handle_surface_commit(struct wl_listener *listener, void *data) { + struct wlr_tearing_control_v1 *hint = wl_container_of(listener, hint, surface_commit); + + if (hint->current != hint->previous) { + wl_signal_emit_mutable(&hint->events.set_hint, NULL); + } + + hint->previous = hint->current; +} + +static void tearing_control_manager_handle_get_tearing_control( + struct wl_client *client, struct wl_resource *resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_tearing_control_manager_v1 *manager = tearing_manager_from_resource(resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + if (wlr_addon_find(&surface->addons, manager, &surface_addon_impl) != NULL) { + wl_resource_post_error(resource, + WP_TEARING_CONTROL_MANAGER_V1_ERROR_TEARING_CONTROL_EXISTS, + "Tearing control object already exists!"); + return; + } + + struct wlr_tearing_control_v1 *hint = calloc(1, sizeof(*hint)); + if (!hint) { + wl_client_post_no_memory(client); + return; + } + + if (!wlr_surface_synced_init(&hint->synced, surface, + &surface_synced_impl, &hint->pending, &hint->current)) { + free(hint); + wl_client_post_no_memory(client); + return; + } + + struct wl_resource *created_resource = + wl_resource_create(client, &wp_tearing_control_v1_interface, + wl_resource_get_version(resource), id); + + if (created_resource == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + wl_resource_set_implementation(created_resource, &tearing_control_impl, + hint, destroy_tearing_resource_impl); + + hint->client = client; + hint->resource = created_resource; + hint->surface = surface; + wlr_addon_init(&hint->addon, &hint->surface->addons, manager, &surface_addon_impl); + + wl_signal_init(&hint->events.set_hint); + wl_signal_init(&hint->events.destroy); + + hint->surface_commit.notify = hint_handle_surface_commit; + wl_signal_add(&surface->events.commit, &hint->surface_commit); + + wl_list_insert(&manager->surface_hints, &hint->link); + + wl_signal_emit_mutable(&manager->events.new_object, hint); +} + +static const struct wp_tearing_control_manager_v1_interface tearing_impl = { + .destroy = resource_handle_destroy, + .get_tearing_control = tearing_control_manager_handle_get_tearing_control, +}; + +static void tearing_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_tearing_control_manager_v1 *manager = data; + + struct wl_resource *wl_resource = wl_resource_create(wl_client, + &wp_tearing_control_manager_v1_interface, version, id); + if (wl_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(wl_resource, &tearing_impl, manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_tearing_control_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + + struct wlr_tearing_control_v1 *hint, *tmp; + wl_list_for_each_safe(hint, tmp, &manager->surface_hints, link) { + destroy_tearing_hint(hint); + } + + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_tearing_control_manager_v1 *wlr_tearing_control_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= TEARING_CONTROL_MANAGER_VERSION); + + struct wlr_tearing_control_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + wl_signal_init(&manager->events.new_object); + wl_signal_init(&manager->events.destroy); + + wl_list_init(&manager->surface_hints); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + manager->global = wl_global_create(display, &wp_tearing_control_manager_v1_interface, + version, manager, tearing_bind); + + if (manager->global == NULL) { + wl_list_remove(&manager->display_destroy.link); + free(manager); + return NULL; + } + return manager; +} + +enum wp_tearing_control_v1_presentation_hint +wlr_tearing_control_manager_v1_surface_hint_from_surface(struct wlr_tearing_control_manager_v1 *manager, + struct wlr_surface *surface) { + struct wlr_addon *addon = + wlr_addon_find(&surface->addons, manager, &surface_addon_impl); + if (addon == NULL) { + return WP_TEARING_CONTROL_V1_PRESENTATION_HINT_VSYNC; + } + + struct wlr_tearing_control_v1 *hint = wl_container_of(addon, hint, addon); + + return hint->current; +} diff --git a/types/wlr_text_input_v3.c b/types/wlr_text_input_v3.c new file mode 100644 index 0000000..936eca1 --- /dev/null +++ b/types/wlr_text_input_v3.c @@ -0,0 +1,337 @@ +#include +#include +#include +#include +#include +#include +#include "text-input-unstable-v3-protocol.h" + +static void text_input_clear_focused_surface(struct wlr_text_input_v3 *text_input) { + wl_list_remove(&text_input->surface_destroy.link); + wl_list_init(&text_input->surface_destroy.link); + text_input->focused_surface = NULL; +} + +static const struct zwp_text_input_v3_interface text_input_impl; + +static struct wlr_text_input_v3 *text_input_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_text_input_v3_interface, + &text_input_impl)); + return wl_resource_get_user_data(resource); +} + +void wlr_text_input_v3_send_enter(struct wlr_text_input_v3 *text_input, + struct wlr_surface *surface) { + assert(wl_resource_get_client(text_input->resource) + == wl_resource_get_client(surface->resource)); + text_input->focused_surface = surface; + wl_signal_add(&text_input->focused_surface->events.destroy, + &text_input->surface_destroy); + zwp_text_input_v3_send_enter(text_input->resource, + text_input->focused_surface->resource); +} + +void wlr_text_input_v3_send_leave(struct wlr_text_input_v3 *text_input) { + zwp_text_input_v3_send_leave(text_input->resource, + text_input->focused_surface->resource); + text_input_clear_focused_surface(text_input); +} + +void wlr_text_input_v3_send_preedit_string(struct wlr_text_input_v3 *text_input, + const char *text, int32_t cursor_begin, int32_t cursor_end) { + zwp_text_input_v3_send_preedit_string(text_input->resource, text, + cursor_begin, cursor_end); +} + +void wlr_text_input_v3_send_commit_string(struct wlr_text_input_v3 *text_input, + const char *text) { + zwp_text_input_v3_send_commit_string(text_input->resource, text); +} + +void wlr_text_input_v3_send_delete_surrounding_text( + struct wlr_text_input_v3 *text_input, uint32_t before_length, + uint32_t after_length) { + zwp_text_input_v3_send_delete_surrounding_text(text_input->resource, + before_length, after_length); +} + +void wlr_text_input_v3_send_done(struct wlr_text_input_v3 *text_input) { + zwp_text_input_v3_send_done(text_input->resource, + text_input->current_serial); +} + +static void wlr_text_input_destroy(struct wlr_text_input_v3 *text_input) { + wl_signal_emit_mutable(&text_input->events.destroy, text_input); + text_input_clear_focused_surface(text_input); + wl_list_remove(&text_input->seat_destroy.link); + // remove from manager.text_inputs + wl_list_remove(&text_input->link); + free(text_input->current.surrounding.text); + free(text_input->pending.surrounding.text); + free(text_input); +} + +static void text_input_resource_destroy(struct wl_resource *resource) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + wlr_text_input_destroy(text_input); +} + +static void text_input_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void text_input_enable(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + struct wlr_text_input_v3_state defaults = {0}; + free(text_input->pending.surrounding.text); + text_input->pending = defaults; + text_input->pending_enabled = true; +} + +static void text_input_disable(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + text_input->pending_enabled = false; +} + +static void text_input_set_surrounding_text(struct wl_client *client, + struct wl_resource *resource, const char *text, int32_t cursor, + int32_t anchor) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + free(text_input->pending.surrounding.text); + text_input->pending.surrounding.text = strdup(text); + if (!text_input->pending.surrounding.text) { + wl_client_post_no_memory(client); + } + text_input->pending.features |= WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT; + text_input->pending.surrounding.cursor = cursor; + text_input->pending.surrounding.anchor = anchor; +} + +static void text_input_set_text_change_cause(struct wl_client *client, + struct wl_resource *resource, uint32_t cause) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + text_input->pending.text_change_cause = cause; +} + +static void text_input_set_content_type(struct wl_client *client, + struct wl_resource *resource, uint32_t hint, uint32_t purpose) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + text_input->pending.features |= WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE; + text_input->pending.content_type.hint = hint; + text_input->pending.content_type.purpose = purpose; +} + +static void text_input_set_cursor_rectangle(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y, int32_t width, + int32_t height) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + text_input->pending.features |= WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE; + text_input->pending.cursor_rectangle.x = x; + text_input->pending.cursor_rectangle.y = y; + text_input->pending.cursor_rectangle.width = width; + text_input->pending.cursor_rectangle.height = height; +} + +static void text_input_commit(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + free(text_input->current.surrounding.text); + text_input->current = text_input->pending; + if (text_input->pending.surrounding.text) { + text_input->current.surrounding.text = + strdup(text_input->pending.surrounding.text); + if (text_input->current.surrounding.text == NULL) { + wl_client_post_no_memory(client); + return; + } + } + + bool old_enabled = text_input->current_enabled; + text_input->current_enabled = text_input->pending_enabled; + text_input->current_serial++; + + if (text_input->focused_surface == NULL) { + wlr_log(WLR_DEBUG, "Text input commit received without focus"); + } + + if (!old_enabled && text_input->current_enabled) { + text_input->active_features = text_input->current.features; + wl_signal_emit_mutable(&text_input->events.enable, text_input); + } else if (old_enabled && !text_input->current_enabled) { + text_input->active_features = 0; + wl_signal_emit_mutable(&text_input->events.disable, text_input); + } else { // including never enabled + wl_signal_emit_mutable(&text_input->events.commit, text_input); + } +} + +static const struct zwp_text_input_v3_interface text_input_impl = { + .destroy = text_input_destroy, + .enable = text_input_enable, + .disable = text_input_disable, + .set_surrounding_text = text_input_set_surrounding_text, + .set_text_change_cause = text_input_set_text_change_cause, + .set_content_type = text_input_set_content_type, + .set_cursor_rectangle = text_input_set_cursor_rectangle, + .commit = text_input_commit, +}; + +static const struct zwp_text_input_manager_v3_interface text_input_manager_impl; + +static struct wlr_text_input_manager_v3 *text_input_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_text_input_manager_v3_interface, &text_input_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void text_input_manager_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void text_input_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_text_input_v3 *text_input = wl_container_of(listener, text_input, + seat_destroy); + struct wl_resource *resource = text_input->resource; + wlr_text_input_destroy(text_input); + wl_resource_set_user_data(resource, NULL); +} + +static void text_input_handle_focused_surface_destroy( + struct wl_listener *listener, void *data) { + struct wlr_text_input_v3 *text_input = wl_container_of(listener, text_input, + surface_destroy); + text_input_clear_focused_surface(text_input); +} + +static void text_input_manager_get_text_input(struct wl_client *client, + struct wl_resource *resource, uint32_t id, struct wl_resource *seat) { + int version = wl_resource_get_version(resource); + struct wl_resource *text_input_resource = wl_resource_create(client, + &zwp_text_input_v3_interface, version, id); + if (text_input_resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(text_input_resource, &text_input_impl, + NULL, text_input_resource_destroy); + + struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); + if (seat_client == NULL) { + return; + } + + struct wlr_text_input_v3 *text_input = calloc(1, sizeof(*text_input)); + if (text_input == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_signal_init(&text_input->events.enable); + wl_signal_init(&text_input->events.commit); + wl_signal_init(&text_input->events.disable); + wl_signal_init(&text_input->events.destroy); + + text_input->resource = text_input_resource; + wl_resource_set_user_data(text_input_resource, text_input); + + struct wlr_seat *wlr_seat = seat_client->seat; + text_input->seat = wlr_seat; + wl_signal_add(&seat_client->events.destroy, + &text_input->seat_destroy); + text_input->seat_destroy.notify = + text_input_handle_seat_destroy; + text_input->surface_destroy.notify = + text_input_handle_focused_surface_destroy; + wl_list_init(&text_input->surface_destroy.link); + + struct wlr_text_input_manager_v3 *manager = + text_input_manager_from_resource(resource); + wl_list_insert(&manager->text_inputs, &text_input->link); + + wl_signal_emit_mutable(&manager->events.text_input, text_input); +} + +static const struct zwp_text_input_manager_v3_interface + text_input_manager_impl = { + .destroy = text_input_manager_destroy, + .get_text_input = text_input_manager_get_text_input, +}; + +static void text_input_manager_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_text_input_manager_v3 *manager = data; + + struct wl_resource *resource = wl_resource_create(wl_client, + &zwp_text_input_manager_v3_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, &text_input_manager_impl, + manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_text_input_manager_v3 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_text_input_manager_v3 *wlr_text_input_manager_v3_create( + struct wl_display *display) { + struct wlr_text_input_manager_v3 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + return NULL; + } + + wl_list_init(&manager->text_inputs); + wl_signal_init(&manager->events.text_input); + wl_signal_init(&manager->events.destroy); + + manager->global = wl_global_create(display, + &zwp_text_input_manager_v3_interface, 1, manager, + text_input_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_touch.c b/types/wlr_touch.c new file mode 100644 index 0000000..66b35e5 --- /dev/null +++ b/types/wlr_touch.c @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include + +#include "interfaces/wlr_input_device.h" + +struct wlr_touch *wlr_touch_from_input_device( + struct wlr_input_device *input_device) { + assert(input_device->type == WLR_INPUT_DEVICE_TOUCH); + return wl_container_of(input_device, (struct wlr_touch *)NULL, base); +} + +void wlr_touch_init(struct wlr_touch *touch, + const struct wlr_touch_impl *impl, const char *name) { + *touch = (struct wlr_touch){ + .impl = impl, + }; + wlr_input_device_init(&touch->base, WLR_INPUT_DEVICE_TOUCH, name); + + wl_signal_init(&touch->events.down); + wl_signal_init(&touch->events.up); + wl_signal_init(&touch->events.motion); + wl_signal_init(&touch->events.cancel); + wl_signal_init(&touch->events.frame); +} + +void wlr_touch_finish(struct wlr_touch *touch) { + wlr_input_device_finish(&touch->base); + + free(touch->output_name); +} diff --git a/types/wlr_transient_seat_v1.c b/types/wlr_transient_seat_v1.c new file mode 100644 index 0000000..379ce0a --- /dev/null +++ b/types/wlr_transient_seat_v1.c @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include "ext-transient-seat-v1-protocol.h" + +static const struct ext_transient_seat_manager_v1_interface manager_impl; +static const struct ext_transient_seat_v1_interface transient_seat_impl; + +static struct wlr_transient_seat_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_transient_seat_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_transient_seat_v1 *transient_seat_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_transient_seat_v1_interface, &transient_seat_impl)); + return wl_resource_get_user_data(resource); +} + +static void transient_seat_destroy(struct wlr_transient_seat_v1 *seat) { + wl_list_remove(&seat->seat_destroy.link); + wlr_seat_destroy(seat->seat); + free(seat); +} + +static void transient_seat_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_transient_seat_v1 *seat = + transient_seat_from_resource(resource); + transient_seat_destroy(seat); +} + +static void transient_seat_handle_destroy(struct wl_client *client, + struct wl_resource *seat_resource) { + wl_resource_destroy(seat_resource); +} + +static const struct ext_transient_seat_v1_interface transient_seat_impl = { + .destroy = transient_seat_handle_destroy, +}; + +static void manager_create_transient_seat(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id) { + struct wlr_transient_seat_manager_v1 *manager = + manager_from_resource(manager_resource); + + struct wlr_transient_seat_v1 *seat = calloc(1, sizeof(*seat)); + if (!seat) { + goto failure; + } + + int version = wl_resource_get_version(manager_resource); + seat->resource = wl_resource_create(client, + &ext_transient_seat_v1_interface, version, id); + if (!seat->resource) { + goto failure; + } + + wl_resource_set_implementation(seat->resource, &transient_seat_impl, + seat, transient_seat_handle_resource_destroy); + + wl_signal_emit_mutable(&manager->events.create_seat, seat); + + return; + +failure: + free(seat); + wl_client_post_no_memory(client); +} + +static void transient_seat_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_transient_seat_v1 *seat = wl_container_of(listener, seat, + seat_destroy); + seat->seat = NULL; + wl_resource_set_user_data(seat->resource, NULL); + transient_seat_destroy(seat); +} + +void wlr_transient_seat_v1_ready(struct wlr_transient_seat_v1 *seat, + struct wlr_seat *wlr_seat) { + assert(wlr_seat); + + seat->seat = wlr_seat; + + seat->seat_destroy.notify = transient_seat_handle_seat_destroy; + wl_signal_add(&wlr_seat->events.destroy, &seat->seat_destroy); + + struct wl_client *client = wl_resource_get_client(seat->resource); + uint32_t global_name = wl_global_get_name(seat->seat->global, client); + assert(global_name != 0); + ext_transient_seat_v1_send_ready(seat->resource, global_name); +} + +void wlr_transient_seat_v1_deny(struct wlr_transient_seat_v1 *seat) { + ext_transient_seat_v1_send_denied(seat->resource); +} + +static void manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct ext_transient_seat_manager_v1_interface manager_impl = { + .create = manager_create_transient_seat, + .destroy = manager_handle_destroy, +}; + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_transient_seat_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +static void transient_seat_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_transient_seat_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &ext_transient_seat_manager_v1_interface, version, id); + + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +struct wlr_transient_seat_manager_v1 *wlr_transient_seat_manager_v1_create( + struct wl_display *display) { + struct wlr_transient_seat_manager_v1 *manager = + calloc(1, sizeof(*manager)); + if (!manager) { + return NULL; + } + + manager->global = wl_global_create(display, + &ext_transient_seat_manager_v1_interface, 1, manager, + transient_seat_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + wl_signal_init(&manager->events.create_seat); + + return manager; +} diff --git a/types/wlr_viewporter.c b/types/wlr_viewporter.c new file mode 100644 index 0000000..c6bda48 --- /dev/null +++ b/types/wlr_viewporter.c @@ -0,0 +1,256 @@ +#include +#include +#include +#include +#include +#include +#include "viewporter-protocol.h" + +#define VIEWPORTER_VERSION 1 + +struct wlr_viewport { + struct wl_resource *resource; + struct wlr_surface *surface; + + struct wlr_addon addon; + + struct wl_listener surface_client_commit; +}; + +static const struct wp_viewport_interface viewport_impl; + +// Returns NULL if the viewport is inert +static struct wlr_viewport *viewport_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_viewport_interface, + &viewport_impl)); + return wl_resource_get_user_data(resource); +} + +static void viewport_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void viewport_handle_set_source(struct wl_client *client, + struct wl_resource *resource, wl_fixed_t x_fixed, wl_fixed_t y_fixed, + wl_fixed_t width_fixed, wl_fixed_t height_fixed) { + struct wlr_viewport *viewport = viewport_from_resource(resource); + if (viewport == NULL) { + wl_resource_post_error(resource, WP_VIEWPORT_ERROR_NO_SURFACE, + "wp_viewport.set_source sent after wl_surface has been destroyed"); + return; + } + + struct wlr_surface_state *pending = &viewport->surface->pending; + + double x = wl_fixed_to_double(x_fixed); + double y = wl_fixed_to_double(y_fixed); + double width = wl_fixed_to_double(width_fixed); + double height = wl_fixed_to_double(height_fixed); + + if (x == -1.0 && y == -1.0 && width == -1.0 && height == -1.0) { + pending->viewport.has_src = false; + } else if (x < 0 || y < 0 || width <= 0 || height <= 0) { + wl_resource_post_error(resource, WP_VIEWPORT_ERROR_BAD_VALUE, + "wl_viewport.set_source sent with invalid values"); + return; + } else { + pending->viewport.has_src = true; + } + + pending->viewport.src.x = x; + pending->viewport.src.y = y; + pending->viewport.src.width = width; + pending->viewport.src.height = height; + + pending->committed |= WLR_SURFACE_STATE_VIEWPORT; +} + +static void viewport_handle_set_destination(struct wl_client *client, + struct wl_resource *resource, int32_t width, int32_t height) { + struct wlr_viewport *viewport = viewport_from_resource(resource); + if (viewport == NULL) { + wl_resource_post_error(resource, WP_VIEWPORT_ERROR_NO_SURFACE, + "wp_viewport.set_destination sent after wl_surface has been destroyed"); + return; + } + + struct wlr_surface_state *pending = &viewport->surface->pending; + + if (width == -1 && height == -1) { + pending->viewport.has_dst = false; + } else if (width <= 0 || height <= 0) { + wl_resource_post_error(resource, WP_VIEWPORT_ERROR_BAD_VALUE, + "wl_viewport.set_destination sent with invalid values"); + return; + } else { + pending->viewport.has_dst = true; + } + + pending->viewport.dst_width = width; + pending->viewport.dst_height = height; + + pending->committed |= WLR_SURFACE_STATE_VIEWPORT; +} + +static const struct wp_viewport_interface viewport_impl = { + .destroy = viewport_handle_destroy, + .set_source = viewport_handle_set_source, + .set_destination = viewport_handle_set_destination, +}; + +static void viewport_destroy(struct wlr_viewport *viewport) { + if (viewport == NULL) { + return; + } + + struct wlr_surface_state *pending = &viewport->surface->pending; + pending->viewport.has_src = false; + pending->viewport.has_dst = false; + pending->committed |= WLR_SURFACE_STATE_VIEWPORT; + + wlr_addon_finish(&viewport->addon); + + wl_resource_set_user_data(viewport->resource, NULL); + wl_list_remove(&viewport->surface_client_commit.link); + free(viewport); +} + +static void surface_addon_destroy(struct wlr_addon *addon) { + struct wlr_viewport *viewport = wl_container_of(addon, viewport, addon); + viewport_destroy(viewport); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "wlr_viewport", + .destroy = surface_addon_destroy, +}; + +static void viewport_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_viewport *viewport = viewport_from_resource(resource); + viewport_destroy(viewport); +} + +static bool check_src_buffer_bounds(const struct wlr_surface_state *state) { + int width = state->buffer_width / state->scale; + int height = state->buffer_height / state->scale; + wlr_output_transform_coords(state->transform, &width, &height); + + struct wlr_fbox box = state->viewport.src; + return box.x + box.width <= width && box.y + box.height <= height; +} + +static void viewport_handle_surface_client_commit(struct wl_listener *listener, + void *data) { + struct wlr_viewport *viewport = + wl_container_of(listener, viewport, surface_client_commit); + + struct wlr_surface_state *state = &viewport->surface->pending; + + if (!state->viewport.has_dst && + (floor(state->viewport.src.width) != state->viewport.src.width || + floor(state->viewport.src.height) != state->viewport.src.height)) { + wlr_surface_reject_pending(viewport->surface, + viewport->resource, WP_VIEWPORT_ERROR_BAD_SIZE, + "wl_viewport.set_source width and height must be integers " + "when the destination rectangle is unset"); + return; + } + + if (state->viewport.has_src && state->buffer != NULL && + !check_src_buffer_bounds(state)) { + wlr_surface_reject_pending(viewport->surface, + viewport->resource, WP_VIEWPORT_ERROR_OUT_OF_BUFFER, + "source rectangle out of buffer bounds"); + return; + } +} + +static void viewporter_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void viewporter_handle_get_viewport(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + if (wlr_addon_find(&surface->addons, NULL, &surface_addon_impl) != NULL) { + wl_resource_post_error(resource, WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS, + "wp_viewport for this surface already exists"); + return; + } + + struct wlr_viewport *viewport = calloc(1, sizeof(*viewport)); + if (viewport == NULL) { + wl_client_post_no_memory(client); + return; + } + + uint32_t version = wl_resource_get_version(resource); + viewport->resource = wl_resource_create(client, &wp_viewport_interface, + version, id); + if (viewport->resource == NULL) { + wl_client_post_no_memory(client); + free(viewport); + return; + } + wl_resource_set_implementation(viewport->resource, &viewport_impl, + viewport, viewport_handle_resource_destroy); + + viewport->surface = surface; + + wlr_addon_init(&viewport->addon, &surface->addons, NULL, &surface_addon_impl); + + viewport->surface_client_commit.notify = viewport_handle_surface_client_commit; + wl_signal_add(&surface->events.client_commit, &viewport->surface_client_commit); +} + +static const struct wp_viewporter_interface viewporter_impl = { + .destroy = viewporter_handle_destroy, + .get_viewport = viewporter_handle_get_viewport, +}; + +static void viewporter_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_viewporter *viewporter = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_viewporter_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &viewporter_impl, viewporter, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_viewporter *viewporter = + wl_container_of(listener, viewporter, display_destroy); + wl_signal_emit_mutable(&viewporter->events.destroy, NULL); + wl_global_destroy(viewporter->global); + free(viewporter); +} + +struct wlr_viewporter *wlr_viewporter_create(struct wl_display *display) { + struct wlr_viewporter *viewporter = calloc(1, sizeof(*viewporter)); + if (viewporter == NULL) { + return NULL; + } + + viewporter->global = wl_global_create(display, &wp_viewporter_interface, + VIEWPORTER_VERSION, viewporter, viewporter_bind); + if (viewporter->global == NULL) { + free(viewporter); + return NULL; + } + + wl_signal_init(&viewporter->events.destroy); + + viewporter->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &viewporter->display_destroy); + + return viewporter; +} diff --git a/types/wlr_virtual_keyboard_v1.c b/types/wlr_virtual_keyboard_v1.c new file mode 100644 index 0000000..8a6d107 --- /dev/null +++ b/types/wlr_virtual_keyboard_v1.c @@ -0,0 +1,242 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "virtual-keyboard-unstable-v1-protocol.h" + +static const struct wlr_keyboard_impl keyboard_impl = { + .name = "virtual-keyboard", +}; + +static const struct zwp_virtual_keyboard_v1_interface virtual_keyboard_impl; + +static struct wlr_virtual_keyboard_v1 *virtual_keyboard_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_virtual_keyboard_v1_interface, &virtual_keyboard_impl)); + return wl_resource_get_user_data(resource); +} + +struct wlr_virtual_keyboard_v1 *wlr_input_device_get_virtual_keyboard( + struct wlr_input_device *wlr_dev) { + if (wlr_dev->type != WLR_INPUT_DEVICE_KEYBOARD) { + return NULL; + } + struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(wlr_dev); + if (wlr_keyboard->impl != &keyboard_impl) { + return NULL; + } + return wl_container_of(wlr_keyboard, + (struct wlr_virtual_keyboard_v1 *)NULL, keyboard); +} + +static void virtual_keyboard_keymap(struct wl_client *client, + struct wl_resource *resource, uint32_t format, int32_t fd, + uint32_t size) { + struct wlr_virtual_keyboard_v1 *keyboard = + virtual_keyboard_from_resource(resource); + if (keyboard == NULL) { + return; + } + + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) { + goto context_fail; + } + void *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (data == MAP_FAILED) { + goto fd_fail; + } + struct xkb_keymap *keymap = xkb_keymap_new_from_string(context, data, + XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(data, size); + if (!keymap) { + goto keymap_fail; + } + wlr_keyboard_set_keymap(&keyboard->keyboard, keymap); + keyboard->has_keymap = true; + xkb_keymap_unref(keymap); + xkb_context_unref(context); + close(fd); + return; +keymap_fail: +fd_fail: + xkb_context_unref(context); +context_fail: + wl_client_post_no_memory(client); + close(fd); +} + +static void virtual_keyboard_key(struct wl_client *client, + struct wl_resource *resource, uint32_t time, uint32_t key, + uint32_t state) { + struct wlr_virtual_keyboard_v1 *keyboard = + virtual_keyboard_from_resource(resource); + if (keyboard == NULL) { + return; + } + if (!keyboard->has_keymap) { + wl_resource_post_error(resource, + ZWP_VIRTUAL_KEYBOARD_V1_ERROR_NO_KEYMAP, + "Cannot send a keypress before defining a keymap"); + return; + } + struct wlr_keyboard_key_event event = { + .time_msec = time, + .keycode = key, + .update_state = false, + .state = state, + }; + wlr_keyboard_notify_key(&keyboard->keyboard, &event); +} + +static void virtual_keyboard_modifiers(struct wl_client *client, + struct wl_resource *resource, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + struct wlr_virtual_keyboard_v1 *keyboard = + virtual_keyboard_from_resource(resource); + if (keyboard == NULL) { + return; + } + if (!keyboard->has_keymap) { + wl_resource_post_error(resource, + ZWP_VIRTUAL_KEYBOARD_V1_ERROR_NO_KEYMAP, + "Cannot send a modifier state before defining a keymap"); + return; + } + wlr_keyboard_notify_modifiers(&keyboard->keyboard, + mods_depressed, mods_latched, mods_locked, group); +} + +static void virtual_keyboard_destroy_resource(struct wl_resource *resource) { + struct wlr_virtual_keyboard_v1 *keyboard = + virtual_keyboard_from_resource(resource); + if (keyboard == NULL) { + return; + } + + wlr_keyboard_finish(&keyboard->keyboard); + + wl_resource_set_user_data(keyboard->resource, NULL); + wl_list_remove(&keyboard->link); + free(keyboard); +} + +static void virtual_keyboard_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_virtual_keyboard_v1_interface virtual_keyboard_impl = { + .keymap = virtual_keyboard_keymap, + .key = virtual_keyboard_key, + .modifiers = virtual_keyboard_modifiers, + .destroy = virtual_keyboard_destroy, +}; + +static const struct zwp_virtual_keyboard_manager_v1_interface manager_impl; + +static struct wlr_virtual_keyboard_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_virtual_keyboard_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void virtual_keyboard_manager_create_virtual_keyboard( + struct wl_client *client, struct wl_resource *resource, + struct wl_resource *seat, uint32_t id) { + struct wlr_virtual_keyboard_manager_v1 *manager = + manager_from_resource(resource); + struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); + + struct wl_resource *keyboard_resource = wl_resource_create(client, + &zwp_virtual_keyboard_v1_interface, wl_resource_get_version(resource), + id); + if (!keyboard_resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(keyboard_resource, &virtual_keyboard_impl, + NULL, virtual_keyboard_destroy_resource); + if (seat_client == NULL) { + return; + } + + struct wlr_virtual_keyboard_v1 *virtual_keyboard = calloc(1, sizeof(*virtual_keyboard)); + if (!virtual_keyboard) { + wl_client_post_no_memory(client); + return; + } + + wlr_keyboard_init(&virtual_keyboard->keyboard, &keyboard_impl, + "wlr_virtual_keyboard_v1"); + + virtual_keyboard->resource = keyboard_resource; + virtual_keyboard->seat = seat_client->seat; + wl_resource_set_user_data(keyboard_resource, virtual_keyboard); + + wl_list_insert(&manager->virtual_keyboards, &virtual_keyboard->link); + + wl_signal_emit_mutable(&manager->events.new_virtual_keyboard, + virtual_keyboard); +} + +static const struct zwp_virtual_keyboard_manager_v1_interface manager_impl = { + .create_virtual_keyboard = virtual_keyboard_manager_create_virtual_keyboard, +}; + +static void virtual_keyboard_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_virtual_keyboard_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwp_virtual_keyboard_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_virtual_keyboard_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_virtual_keyboard_manager_v1* + wlr_virtual_keyboard_manager_v1_create( + struct wl_display *display) { + struct wlr_virtual_keyboard_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + return NULL; + } + + manager->global = wl_global_create(display, + &zwp_virtual_keyboard_manager_v1_interface, 1, manager, + virtual_keyboard_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + wl_list_init(&manager->virtual_keyboards); + + wl_signal_init(&manager->events.new_virtual_keyboard); + wl_signal_init(&manager->events.destroy); + + return manager; +} diff --git a/types/wlr_virtual_pointer_v1.c b/types/wlr_virtual_pointer_v1.c new file mode 100644 index 0000000..2ae39a0 --- /dev/null +++ b/types/wlr_virtual_pointer_v1.c @@ -0,0 +1,343 @@ +#include +#include +#include +#include +#include +#include +#include +#include "wlr-virtual-pointer-unstable-v1-protocol.h" + +static const struct wlr_pointer_impl pointer_impl = { + .name = "virtual-pointer", +}; + +static const struct zwlr_virtual_pointer_v1_interface virtual_pointer_impl; + +static struct wlr_virtual_pointer_v1 *virtual_pointer_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_virtual_pointer_v1_interface, &virtual_pointer_impl)); + return wl_resource_get_user_data(resource); +} + +static void virtual_pointer_motion(struct wl_client *client, + struct wl_resource *resource, uint32_t time, + wl_fixed_t dx, wl_fixed_t dy) { + struct wlr_virtual_pointer_v1 *pointer = + virtual_pointer_from_resource(resource); + if (pointer == NULL) { + return; + } + struct wlr_pointer_motion_event event = { + .pointer = &pointer->pointer, + .time_msec = time, + .delta_x = wl_fixed_to_double(dx), + .delta_y = wl_fixed_to_double(dy), + .unaccel_dx = wl_fixed_to_double(dx), + .unaccel_dy = wl_fixed_to_double(dy), + }; + wl_signal_emit_mutable(&pointer->pointer.events.motion, &event); +} + +static void virtual_pointer_motion_absolute(struct wl_client *client, + struct wl_resource *resource, uint32_t time, uint32_t x, uint32_t y, + uint32_t x_extent, uint32_t y_extent) { + struct wlr_virtual_pointer_v1 *pointer = + virtual_pointer_from_resource(resource); + if (pointer == NULL) { + return; + } + if (x_extent == 0 || y_extent == 0) { + return; + } + struct wlr_pointer_motion_absolute_event event = { + .pointer = &pointer->pointer, + .time_msec = time, + .x = (double)x / x_extent, + .y = (double)y / y_extent, + }; + wl_signal_emit_mutable(&pointer->pointer.events.motion_absolute, &event); +} + +static void virtual_pointer_button(struct wl_client *client, + struct wl_resource *resource, uint32_t time, uint32_t button, + uint32_t state) { + struct wlr_virtual_pointer_v1 *pointer = + virtual_pointer_from_resource(resource); + if (pointer == NULL) { + return; + } + struct wlr_pointer_button_event event = { + .pointer = &pointer->pointer, + .time_msec = time, + .button = button, + .state = state ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED, + }; + wl_signal_emit_mutable(&pointer->pointer.events.button, &event); +} + +static void virtual_pointer_axis(struct wl_client *client, + struct wl_resource *resource, uint32_t time, uint32_t axis, + wl_fixed_t value) { + if (axis > WL_POINTER_AXIS_HORIZONTAL_SCROLL) { + wl_resource_post_error(resource, + ZWLR_VIRTUAL_POINTER_V1_ERROR_INVALID_AXIS, + "Invalid enumeration value %" PRIu32, axis); + return; + } + struct wlr_virtual_pointer_v1 *pointer = + virtual_pointer_from_resource(resource); + if (pointer == NULL) { + return; + } + pointer->axis = axis; + pointer->axis_valid[pointer->axis] = true; + pointer->axis_event[pointer->axis].pointer = &pointer->pointer; + pointer->axis_event[pointer->axis].time_msec = time; + pointer->axis_event[pointer->axis].orientation = axis; + pointer->axis_event[pointer->axis].delta = wl_fixed_to_double(value); +} + +static void virtual_pointer_frame(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_virtual_pointer_v1 *pointer = + virtual_pointer_from_resource(resource); + if (pointer == NULL) { + return; + } + + for (size_t i = 0; + i < sizeof(pointer->axis_valid) / sizeof(pointer->axis_valid[0]); + ++i) { + if (pointer->axis_valid[i]) { + /* Deliver pending axis event */ + wl_signal_emit_mutable(&pointer->pointer.events.axis, + &pointer->axis_event[i]); + pointer->axis_event[i] = (struct wlr_pointer_axis_event){0}; + pointer->axis_valid[i] = false; + } + } + + wl_signal_emit_mutable(&pointer->pointer.events.frame, &pointer->pointer); +} + +static void virtual_pointer_axis_source(struct wl_client *client, + struct wl_resource *resource, uint32_t source) { + if (source > WL_POINTER_AXIS_SOURCE_WHEEL_TILT) { + wl_resource_post_error(resource, + ZWLR_VIRTUAL_POINTER_V1_ERROR_INVALID_AXIS_SOURCE, + "Invalid enumeration value %" PRIu32, source); + return; + } + struct wlr_virtual_pointer_v1 *pointer = + virtual_pointer_from_resource(resource); + if (pointer == NULL) { + return; + } + pointer->axis_event[pointer->axis].pointer = &pointer->pointer; + pointer->axis_event[pointer->axis].source = source; +} + +static void virtual_pointer_axis_stop(struct wl_client *client, + struct wl_resource *resource, uint32_t time, uint32_t axis) { + if (axis > WL_POINTER_AXIS_HORIZONTAL_SCROLL) { + wl_resource_post_error(resource, + ZWLR_VIRTUAL_POINTER_V1_ERROR_INVALID_AXIS, + "Invalid enumeration value %" PRIu32, axis); + return; + } + struct wlr_virtual_pointer_v1 *pointer = + virtual_pointer_from_resource(resource); + if (pointer == NULL) { + return; + } + pointer->axis = axis; + pointer->axis_valid[pointer->axis] = true; + pointer->axis_event[pointer->axis].pointer = &pointer->pointer; + pointer->axis_event[pointer->axis].time_msec = time; + pointer->axis_event[pointer->axis].orientation = axis; + pointer->axis_event[pointer->axis].delta = 0; + pointer->axis_event[pointer->axis].delta_discrete = 0; +} + +static void virtual_pointer_axis_discrete(struct wl_client *client, + struct wl_resource *resource, uint32_t time, uint32_t axis, + wl_fixed_t value, int32_t discrete) { + if (axis > WL_POINTER_AXIS_HORIZONTAL_SCROLL) { + wl_resource_post_error(resource, + ZWLR_VIRTUAL_POINTER_V1_ERROR_INVALID_AXIS, + "Invalid enumeration value %" PRIu32, axis); + return; + } + struct wlr_virtual_pointer_v1 *pointer = + virtual_pointer_from_resource(resource); + if (pointer == NULL) { + return; + } + pointer->axis = axis; + pointer->axis_valid[pointer->axis] = true; + pointer->axis_event[pointer->axis].pointer = &pointer->pointer; + pointer->axis_event[pointer->axis].time_msec = time; + pointer->axis_event[pointer->axis].orientation = axis; + pointer->axis_event[pointer->axis].delta = wl_fixed_to_double(value); + pointer->axis_event[pointer->axis].delta_discrete = discrete * + WLR_POINTER_AXIS_DISCRETE_STEP; +} + +static void virtual_pointer_destroy_resource(struct wl_resource *resource) { + struct wlr_virtual_pointer_v1 *pointer = + virtual_pointer_from_resource(resource); + if (pointer == NULL) { + return; + } + + wlr_pointer_finish(&pointer->pointer); + + wl_resource_set_user_data(pointer->resource, NULL); + wl_list_remove(&pointer->link); + free(pointer); +} + +static void virtual_pointer_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_virtual_pointer_v1_interface virtual_pointer_impl = { + .motion = virtual_pointer_motion, + .motion_absolute = virtual_pointer_motion_absolute, + .button = virtual_pointer_button, + .axis = virtual_pointer_axis, + .frame = virtual_pointer_frame, + .axis_source = virtual_pointer_axis_source, + .axis_stop = virtual_pointer_axis_stop, + .axis_discrete = virtual_pointer_axis_discrete, + .destroy = virtual_pointer_destroy, +}; + +static const struct zwlr_virtual_pointer_manager_v1_interface manager_impl; + +static struct wlr_virtual_pointer_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_virtual_pointer_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void virtual_pointer_manager_create_virtual_pointer_with_output( + struct wl_client *client, struct wl_resource *resource, + struct wl_resource *seat, struct wl_resource *output, + uint32_t id) { + struct wlr_virtual_pointer_manager_v1 *manager = manager_from_resource(resource); + + struct wlr_virtual_pointer_v1 *virtual_pointer = calloc(1, sizeof(*virtual_pointer)); + if (!virtual_pointer) { + wl_client_post_no_memory(client); + return; + } + + wlr_pointer_init(&virtual_pointer->pointer, &pointer_impl, + "wlr_virtual_pointer_v1"); + + struct wl_resource *pointer_resource = wl_resource_create(client, + &zwlr_virtual_pointer_v1_interface, wl_resource_get_version(resource), + id); + if (!pointer_resource) { + free(virtual_pointer); + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(pointer_resource, &virtual_pointer_impl, + virtual_pointer, virtual_pointer_destroy_resource); + + struct wlr_virtual_pointer_v1_new_pointer_event event = { + .new_pointer = virtual_pointer, + }; + + if (seat) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat); + event.suggested_seat = seat_client != NULL ? seat_client->seat : NULL; + } + + if (output) { + struct wlr_output *wlr_output = wlr_output_from_resource(output); + event.suggested_output = wlr_output; + } + + virtual_pointer->resource = pointer_resource; + + wl_list_insert(&manager->virtual_pointers, &virtual_pointer->link); + wl_signal_emit_mutable(&manager->events.new_virtual_pointer, &event); +} + +static void virtual_pointer_manager_create_virtual_pointer( + struct wl_client *client, struct wl_resource *resource, + struct wl_resource *seat, uint32_t id) { + virtual_pointer_manager_create_virtual_pointer_with_output(client, + resource, seat, NULL, id); +} +static void virtual_pointer_manager_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_virtual_pointer_manager_v1_interface manager_impl = { + .create_virtual_pointer = virtual_pointer_manager_create_virtual_pointer, + .create_virtual_pointer_with_output = virtual_pointer_manager_create_virtual_pointer_with_output, + .destroy = virtual_pointer_manager_destroy, +}; + +static void virtual_pointer_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_virtual_pointer_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwlr_virtual_pointer_manager_v1_interface, version, id); + + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_virtual_pointer_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + struct wlr_virtual_pointer_v1 *pointer, *pointer_tmp; + wl_list_for_each_safe(pointer, pointer_tmp, + &manager->virtual_pointers, link) { + wl_resource_destroy(pointer->resource); + } + free(manager); +} + +struct wlr_virtual_pointer_manager_v1* wlr_virtual_pointer_manager_v1_create( + struct wl_display *display) { + struct wlr_virtual_pointer_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + return NULL; + } + + wl_list_init(&manager->virtual_pointers); + + wl_signal_init(&manager->events.new_virtual_pointer); + wl_signal_init(&manager->events.destroy); + manager->global = wl_global_create(display, + &zwlr_virtual_pointer_manager_v1_interface, 2, manager, + virtual_pointer_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + return manager; +} diff --git a/types/wlr_xcursor_manager.c b/types/wlr_xcursor_manager.c new file mode 100644 index 0000000..b45dcd7 --- /dev/null +++ b/types/wlr_xcursor_manager.c @@ -0,0 +1,65 @@ +#include +#include +#include + +struct wlr_xcursor_manager *wlr_xcursor_manager_create(const char *name, + uint32_t size) { + struct wlr_xcursor_manager *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + if (name != NULL) { + manager->name = strdup(name); + } + manager->size = size; + wl_list_init(&manager->scaled_themes); + return manager; +} + +void wlr_xcursor_manager_destroy(struct wlr_xcursor_manager *manager) { + if (manager == NULL) { + return; + } + struct wlr_xcursor_manager_theme *theme, *tmp; + wl_list_for_each_safe(theme, tmp, &manager->scaled_themes, link) { + wl_list_remove(&theme->link); + wlr_xcursor_theme_destroy(theme->theme); + free(theme); + } + free(manager->name); + free(manager); +} + +bool wlr_xcursor_manager_load(struct wlr_xcursor_manager *manager, + float scale) { + struct wlr_xcursor_manager_theme *theme; + wl_list_for_each(theme, &manager->scaled_themes, link) { + if (theme->scale == scale) { + return true; + } + } + + theme = calloc(1, sizeof(*theme)); + if (theme == NULL) { + return false; + } + theme->scale = scale; + theme->theme = wlr_xcursor_theme_load(manager->name, manager->size * scale); + if (theme->theme == NULL) { + free(theme); + return false; + } + wl_list_insert(&manager->scaled_themes, &theme->link); + return true; +} + +struct wlr_xcursor *wlr_xcursor_manager_get_xcursor( + struct wlr_xcursor_manager *manager, const char *name, float scale) { + struct wlr_xcursor_manager_theme *theme; + wl_list_for_each(theme, &manager->scaled_themes, link) { + if (theme->scale == scale) { + return wlr_xcursor_theme_get_cursor(theme->theme, name); + } + } + return NULL; +} diff --git a/types/wlr_xdg_activation_v1.c b/types/wlr_xdg_activation_v1.c new file mode 100644 index 0000000..8e0fb06 --- /dev/null +++ b/types/wlr_xdg_activation_v1.c @@ -0,0 +1,427 @@ +#include +#include +#include +#include +#include +#include +#include +#include "util/token.h" +#include "xdg-activation-v1-protocol.h" + +#define XDG_ACTIVATION_V1_VERSION 1 + +static const struct xdg_activation_token_v1_interface token_impl; + +static struct wlr_xdg_activation_token_v1 *token_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &xdg_activation_token_v1_interface, &token_impl)); + return wl_resource_get_user_data(resource); +} + +void wlr_xdg_activation_token_v1_destroy( + struct wlr_xdg_activation_token_v1 *token) { + if (token == NULL) { + return; + } + if (token->resource != NULL) { + wl_resource_set_user_data(token->resource, NULL); // make inert + } + if (token->timeout != NULL) { + wl_event_source_remove(token->timeout); + } + + wl_signal_emit_mutable(&token->events.destroy, NULL); + + wl_list_remove(&token->link); + wl_list_remove(&token->seat_destroy.link); + wl_list_remove(&token->surface_destroy.link); + free(token->app_id); + free(token->token); + free(token); +} + +static int token_handle_timeout(void *data) { + struct wlr_xdg_activation_token_v1 *token = data; + wlr_log(WLR_DEBUG, "Activation token '%s' has expired", token->token); + wlr_xdg_activation_token_v1_destroy(token); + return 0; +} + +static void token_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_activation_token_v1 *token = token_from_resource(resource); + wlr_xdg_activation_token_v1_destroy(token); +} + +static void token_handle_destroy(struct wl_client *client, + struct wl_resource *token_resource) { + wl_resource_destroy(token_resource); +} + +static bool token_init( struct wlr_xdg_activation_token_v1 *token) { + char token_str[TOKEN_SIZE] = {0}; + if (!generate_token(token_str)) { + return false; + } + + token->token = strdup(token_str); + if (token->token == NULL) { + return false; + } + + if (token->activation->token_timeout_msec > 0) { + // Needs wayland > 1.19 + // struct wl_display *display = wl_global_get_display(activation->global); + struct wl_display *display = token->activation->display; + struct wl_event_loop *loop = wl_display_get_event_loop(display); + token->timeout = + wl_event_loop_add_timer(loop, token_handle_timeout, token); + if (token->timeout == NULL) { + return false; + } + wl_event_source_timer_update(token->timeout, + token->activation->token_timeout_msec); + } + + assert(wl_list_empty(&token->link)); + wl_list_insert(&token->activation->tokens, &token->link); + return true; +} + +static void token_handle_commit(struct wl_client *client, + struct wl_resource *token_resource) { + struct wlr_xdg_activation_token_v1 *token = + token_from_resource(token_resource); + if (token == NULL) { + wl_resource_post_error(token_resource, + XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, + "The activation token has already been used"); + return; + } + + // Make the token resource inert + wl_resource_set_user_data(token->resource, NULL); + token->resource = NULL; + + if (token->seat != NULL) { + struct wlr_seat_client *seat_client = + wlr_seat_client_for_wl_client(token->seat, client); + if (seat_client == NULL || + !wlr_seat_client_validate_event_serial(seat_client, token->serial)) { + wlr_log(WLR_DEBUG, "Rejecting token commit request: " + "serial %"PRIu32" was never given to client", token->serial); + goto error; + } + + if (token->surface != NULL && + token->surface != token->seat->keyboard_state.focused_surface && + token->surface != token->seat->pointer_state.focused_surface) { + wlr_log(WLR_DEBUG, "Rejecting token commit request: " + "surface doesn't have focus"); + goto error; + } + } + + if (!token_init(token)) { + wl_client_post_no_memory(client); + return; + } + + wl_signal_emit_mutable(&token->activation->events.new_token, token); + + xdg_activation_token_v1_send_done(token_resource, token->token); + + return; + +error:; + // Here we send a generated token, but it's invalid and can't be used to + // request activation. + char token_str[TOKEN_SIZE] = {0}; + if (!generate_token(token_str)) { + wl_client_post_no_memory(client); + return; + } + + xdg_activation_token_v1_send_done(token_resource, token_str); + wlr_xdg_activation_token_v1_destroy(token); +} + +static void token_handle_set_app_id(struct wl_client *client, + struct wl_resource *token_resource, const char *app_id) { + struct wlr_xdg_activation_token_v1 *token = + token_from_resource(token_resource); + if (token == NULL) { + wl_resource_post_error(token_resource, + XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, + "The activation token has already been used"); + return; + } + + free(token->app_id); + token->app_id = strdup(app_id); +} + +static void token_handle_seat_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_activation_token_v1 *token = + wl_container_of(listener, token, seat_destroy); + wl_list_remove(&token->seat_destroy.link); + wl_list_init(&token->seat_destroy.link); + token->serial = 0; + token->seat = NULL; +} + +static void token_handle_set_serial(struct wl_client *client, + struct wl_resource *token_resource, uint32_t serial, + struct wl_resource *seat_resource) { + struct wlr_xdg_activation_token_v1 *token = + token_from_resource(token_resource); + if (token == NULL) { + wl_resource_post_error(token_resource, + XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, + "The activation token has already been used"); + return; + } + + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + if (seat_client == NULL) { + wlr_log(WLR_DEBUG, "Rejecting token set_serial request: seat is inert"); + return; + } + + token->seat = seat_client->seat; + token->serial = serial; + + token->seat_destroy.notify = token_handle_seat_destroy; + wl_list_remove(&token->seat_destroy.link); + wl_signal_add(&token->seat->events.destroy, &token->seat_destroy); +} + +static void token_handle_surface_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_activation_token_v1 *token = + wl_container_of(listener, token, surface_destroy); + wl_list_remove(&token->surface_destroy.link); + wl_list_init(&token->surface_destroy.link); + token->surface = NULL; +} + +static void token_handle_set_surface(struct wl_client *client, + struct wl_resource *token_resource, + struct wl_resource *surface_resource) { + struct wlr_xdg_activation_token_v1 *token = + token_from_resource(token_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + if (token == NULL) { + wl_resource_post_error(token_resource, + XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, + "The activation token has already been used"); + return; + } + + token->surface = surface; + + token->surface_destroy.notify = token_handle_surface_destroy; + wl_list_remove(&token->surface_destroy.link); + wl_signal_add(&surface->events.destroy, &token->surface_destroy); +} + +static const struct xdg_activation_token_v1_interface token_impl = { + .destroy = token_handle_destroy, + .commit = token_handle_commit, + .set_app_id = token_handle_set_app_id, + .set_serial = token_handle_set_serial, + .set_surface = token_handle_set_surface, +}; + +static const struct xdg_activation_v1_interface activation_impl; + +static struct wlr_xdg_activation_v1 *activation_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &xdg_activation_v1_interface, &activation_impl)); + return wl_resource_get_user_data(resource); +} + +static void activation_handle_destroy(struct wl_client *client, + struct wl_resource *activation_resource) { + wl_resource_destroy(activation_resource); +} + +static struct wlr_xdg_activation_token_v1 *activation_token_create( + struct wlr_xdg_activation_v1 *activation) { + struct wlr_xdg_activation_token_v1 *token = calloc(1, sizeof(*token)); + if (token == NULL) { + return NULL; + } + wl_list_init(&token->link); + wl_list_init(&token->seat_destroy.link); + wl_list_init(&token->surface_destroy.link); + wl_signal_init(&token->events.destroy); + + token->activation = activation; + + return token; +} + +static void activation_handle_get_activation_token(struct wl_client *client, + struct wl_resource *activation_resource, uint32_t id) { + struct wlr_xdg_activation_v1 *activation = + activation_from_resource(activation_resource); + + struct wlr_xdg_activation_token_v1 *token = activation_token_create(activation); + if (token == NULL) { + wl_client_post_no_memory(client); + return; + } + + uint32_t version = wl_resource_get_version(activation_resource); + token->resource = wl_resource_create(client, + &xdg_activation_token_v1_interface, version, id); + if (token->resource == NULL) { + free(token); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(token->resource, &token_impl, token, + token_handle_resource_destroy); +} + +static void activation_handle_activate(struct wl_client *client, + struct wl_resource *activation_resource, const char *token_str, + struct wl_resource *surface_resource) { + struct wlr_xdg_activation_v1 *activation = + activation_from_resource(activation_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_xdg_activation_token_v1 *token; + bool found = false; + wl_list_for_each(token, &activation->tokens, link) { + if (strcmp(token_str, token->token) == 0) { + found = true; + break; + } + } + if (!found) { + wlr_log(WLR_DEBUG, "Rejecting activate request: unknown token"); + return; + } + + struct wlr_xdg_activation_v1_request_activate_event event = { + .activation = activation, + .token = token, + .surface = surface, + }; + wl_signal_emit_mutable(&activation->events.request_activate, &event); + + wlr_xdg_activation_token_v1_destroy(token); +} + +static const struct xdg_activation_v1_interface activation_impl = { + .destroy = activation_handle_destroy, + .get_activation_token = activation_handle_get_activation_token, + .activate = activation_handle_activate, +}; + +static void activation_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_activation_v1 *activation = data; + + struct wl_resource *resource = wl_resource_create(client, + &xdg_activation_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &activation_impl, activation, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_activation_v1 *activation = + wl_container_of(listener, activation, display_destroy); + wl_signal_emit_mutable(&activation->events.destroy, NULL); + + struct wlr_xdg_activation_token_v1 *token, *token_tmp; + wl_list_for_each_safe(token, token_tmp, &activation->tokens, link) { + wlr_xdg_activation_token_v1_destroy(token); + } + + wl_list_remove(&activation->display_destroy.link); + wl_global_destroy(activation->global); + free(activation); +} + +struct wlr_xdg_activation_v1 *wlr_xdg_activation_v1_create( + struct wl_display *display) { + struct wlr_xdg_activation_v1 *activation = calloc(1, sizeof(*activation)); + if (activation == NULL) { + return NULL; + } + + activation->token_timeout_msec = 30000; // 30s + wl_list_init(&activation->tokens); + wl_signal_init(&activation->events.destroy); + wl_signal_init(&activation->events.request_activate); + wl_signal_init(&activation->events.new_token); + + activation->global = wl_global_create(display, + &xdg_activation_v1_interface, XDG_ACTIVATION_V1_VERSION, activation, + activation_bind); + if (activation->global == NULL) { + free(activation); + return NULL; + } + + activation->display = display; + + activation->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &activation->display_destroy); + + return activation; +} + +struct wlr_xdg_activation_token_v1 *wlr_xdg_activation_token_v1_create( + struct wlr_xdg_activation_v1 *activation) { + struct wlr_xdg_activation_token_v1 *token = activation_token_create(activation); + + if (token == NULL) { + return NULL; + } + + if (!token_init(token)) { + wlr_xdg_activation_token_v1_destroy(token); + return NULL; + } + + return token; +} + +struct wlr_xdg_activation_token_v1 *wlr_xdg_activation_v1_find_token( + struct wlr_xdg_activation_v1 *activation, const char *token_str) { + struct wlr_xdg_activation_token_v1 *token; + wl_list_for_each(token, &activation->tokens, link) { + if (strcmp(token_str, token->token) == 0) { + return token; + } + } + return NULL; +} + +const char *wlr_xdg_activation_token_v1_get_name( + struct wlr_xdg_activation_token_v1 *token) { + return token->token; +} + +struct wlr_xdg_activation_token_v1 *wlr_xdg_activation_v1_add_token( + struct wlr_xdg_activation_v1 *activation, const char *token_str) { + assert(token_str); + + struct wlr_xdg_activation_token_v1 *token = activation_token_create(activation); + if (token == NULL) { + return NULL; + } + token->token = strdup(token_str); + + wl_list_insert(&activation->tokens, &token->link); + + return token; +} diff --git a/types/wlr_xdg_decoration_v1.c b/types/wlr_xdg_decoration_v1.c new file mode 100644 index 0000000..d16ac7c --- /dev/null +++ b/types/wlr_xdg_decoration_v1.c @@ -0,0 +1,285 @@ +#include +#include +#include +#include +#include +#include "xdg-decoration-unstable-v1-protocol.h" + +#define DECORATION_MANAGER_VERSION 1 + +static const struct zxdg_toplevel_decoration_v1_interface + toplevel_decoration_impl; + +static struct wlr_xdg_toplevel_decoration_v1 *toplevel_decoration_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zxdg_toplevel_decoration_v1_interface, &toplevel_decoration_impl)); + return wl_resource_get_user_data(resource); +} + +static void toplevel_decoration_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void toplevel_decoration_handle_set_mode(struct wl_client *client, + struct wl_resource *resource, + enum zxdg_toplevel_decoration_v1_mode mode) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + toplevel_decoration_from_resource(resource); + + decoration->requested_mode = + (enum wlr_xdg_toplevel_decoration_v1_mode)mode; + wl_signal_emit_mutable(&decoration->events.request_mode, decoration); +} + +static void toplevel_decoration_handle_unset_mode(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + toplevel_decoration_from_resource(resource); + + decoration->requested_mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE; + wl_signal_emit_mutable(&decoration->events.request_mode, decoration); +} + +static const struct zxdg_toplevel_decoration_v1_interface + toplevel_decoration_impl = { + .destroy = toplevel_decoration_handle_destroy, + .set_mode = toplevel_decoration_handle_set_mode, + .unset_mode = toplevel_decoration_handle_unset_mode, +}; + +uint32_t wlr_xdg_toplevel_decoration_v1_set_mode( + struct wlr_xdg_toplevel_decoration_v1 *decoration, + enum wlr_xdg_toplevel_decoration_v1_mode mode) { + assert(mode != WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE); + decoration->scheduled_mode = mode; + return wlr_xdg_surface_schedule_configure(decoration->toplevel->base); +} + +static void toplevel_decoration_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + toplevel_decoration_from_resource(resource); + wl_signal_emit_mutable(&decoration->events.destroy, decoration); + wlr_surface_synced_finish(&decoration->synced); + wl_list_remove(&decoration->toplevel_destroy.link); + wl_list_remove(&decoration->surface_configure.link); + wl_list_remove(&decoration->surface_ack_configure.link); + struct wlr_xdg_toplevel_decoration_v1_configure *configure, *tmp; + wl_list_for_each_safe(configure, tmp, &decoration->configure_list, link) { + free(configure); + } + wl_list_remove(&decoration->link); + free(decoration); +} + +static void toplevel_decoration_handle_toplevel_destroy( + struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + wl_container_of(listener, decoration, toplevel_destroy); + + wl_resource_post_error(decoration->resource, + ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ORPHANED, + "xdg_toplevel destroyed before xdg_toplevel_decoration"); + + wl_resource_destroy(decoration->resource); +} + +static void toplevel_decoration_handle_surface_configure( + struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + wl_container_of(listener, decoration, surface_configure); + struct wlr_xdg_surface_configure *surface_configure = data; + + if (decoration->pending.mode == decoration->scheduled_mode) { + return; + } + + struct wlr_xdg_toplevel_decoration_v1_configure *configure = calloc(1, sizeof(*configure)); + if (configure == NULL) { + return; + } + configure->surface_configure = surface_configure; + configure->mode = decoration->scheduled_mode; + wl_list_insert(decoration->configure_list.prev, &configure->link); + + zxdg_toplevel_decoration_v1_send_configure(decoration->resource, + configure->mode); +} + +static void toplevel_decoration_handle_surface_ack_configure( + struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + wl_container_of(listener, decoration, surface_ack_configure); + struct wlr_xdg_surface_configure *surface_configure = data; + + // First find the ack'ed configure + bool found = false; + struct wlr_xdg_toplevel_decoration_v1_configure *configure, *tmp; + wl_list_for_each(configure, &decoration->configure_list, link) { + if (configure->surface_configure == surface_configure) { + found = true; + break; + } + } + if (!found) { + return; + } + // Then remove old configures from the list + wl_list_for_each_safe(configure, tmp, &decoration->configure_list, link) { + if (configure->surface_configure == surface_configure) { + break; + } + wl_list_remove(&configure->link); + free(configure); + } + + decoration->pending.mode = configure->mode; + + wl_list_remove(&configure->link); + free(configure); +} + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_xdg_toplevel_decoration_v1_state), +}; + +static const struct zxdg_decoration_manager_v1_interface decoration_manager_impl; + +static struct wlr_xdg_decoration_manager_v1 * + decoration_manager_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zxdg_decoration_manager_v1_interface, + &decoration_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void decoration_manager_handle_destroy( + struct wl_client *client, struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static void decoration_manager_handle_get_toplevel_decoration( + struct wl_client *client, struct wl_resource *manager_resource, + uint32_t id, struct wl_resource *toplevel_resource) { + struct wlr_xdg_decoration_manager_v1 *manager = + decoration_manager_from_resource(manager_resource); + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(toplevel_resource); + + if (wlr_surface_has_buffer(toplevel->base->surface)) { + wl_resource_post_error(manager_resource, + ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER, + "xdg_toplevel_decoration must not have a buffer at creation"); + return; + } + + struct wlr_xdg_toplevel_decoration_v1 *existing; + wl_list_for_each(existing, &manager->decorations, link) { + if (existing->toplevel == toplevel) { + wl_resource_post_error(manager_resource, + ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ALREADY_CONSTRUCTED, + "xdg_toplevel already has a decoration object"); + return; + } + } + + struct wlr_xdg_toplevel_decoration_v1 *decoration = calloc(1, sizeof(*decoration)); + if (decoration == NULL) { + wl_client_post_no_memory(client); + return; + } + decoration->manager = manager; + decoration->toplevel = toplevel; + + if (!wlr_surface_synced_init(&decoration->synced, toplevel->base->surface, + &surface_synced_impl, &decoration->pending, &decoration->current)) { + free(decoration); + wl_client_post_no_memory(client); + return; + } + + uint32_t version = wl_resource_get_version(manager_resource); + decoration->resource = wl_resource_create(client, + &zxdg_toplevel_decoration_v1_interface, version, id); + if (decoration->resource == NULL) { + wlr_surface_synced_finish(&decoration->synced); + free(decoration); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(decoration->resource, + &toplevel_decoration_impl, decoration, + toplevel_decoration_handle_resource_destroy); + + wlr_log(WLR_DEBUG, "new xdg_toplevel_decoration %p (res %p)", decoration, + decoration->resource); + + wl_list_init(&decoration->configure_list); + wl_signal_init(&decoration->events.destroy); + wl_signal_init(&decoration->events.request_mode); + + wl_signal_add(&toplevel->events.destroy, &decoration->toplevel_destroy); + decoration->toplevel_destroy.notify = toplevel_decoration_handle_toplevel_destroy; + wl_signal_add(&toplevel->base->events.configure, &decoration->surface_configure); + decoration->surface_configure.notify = toplevel_decoration_handle_surface_configure; + wl_signal_add(&toplevel->base->events.ack_configure, &decoration->surface_ack_configure); + decoration->surface_ack_configure.notify = toplevel_decoration_handle_surface_ack_configure; + + wl_list_insert(&manager->decorations, &decoration->link); + + wl_signal_emit_mutable(&manager->events.new_toplevel_decoration, decoration); +} + +static const struct zxdg_decoration_manager_v1_interface + decoration_manager_impl = { + .destroy = decoration_manager_handle_destroy, + .get_toplevel_decoration = decoration_manager_handle_get_toplevel_decoration, +}; + +static void decoration_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_decoration_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zxdg_decoration_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &decoration_manager_impl, + manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_decoration_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_xdg_decoration_manager_v1 * + wlr_xdg_decoration_manager_v1_create(struct wl_display *display) { + struct wlr_xdg_decoration_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + manager->global = wl_global_create(display, + &zxdg_decoration_manager_v1_interface, DECORATION_MANAGER_VERSION, + manager, decoration_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + wl_list_init(&manager->decorations); + wl_signal_init(&manager->events.new_toplevel_decoration); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_xdg_foreign_registry.c b/types/wlr_xdg_foreign_registry.c new file mode 100644 index 0000000..c27d840 --- /dev/null +++ b/types/wlr_xdg_foreign_registry.c @@ -0,0 +1,72 @@ +#include +#include "util/token.h" +#include +#include +#include + +bool wlr_xdg_foreign_exported_init( + struct wlr_xdg_foreign_exported *exported, + struct wlr_xdg_foreign_registry *registry) { + do { + if (!generate_token(exported->handle)) { + return false; + } + } while (wlr_xdg_foreign_registry_find_by_handle(registry, exported->handle) != NULL); + + exported->registry = registry; + wl_list_insert(®istry->exported_surfaces, &exported->link); + + wl_signal_init(&exported->events.destroy); + return true; +} + +struct wlr_xdg_foreign_exported *wlr_xdg_foreign_registry_find_by_handle( + struct wlr_xdg_foreign_registry *registry, const char *handle) { + if (handle == NULL || strlen(handle) >= WLR_XDG_FOREIGN_HANDLE_SIZE) { + return NULL; + } + + struct wlr_xdg_foreign_exported *exported; + wl_list_for_each(exported, ®istry->exported_surfaces, link) { + if (strcmp(handle, exported->handle) == 0) { + return exported; + } + } + + return NULL; +} + +void wlr_xdg_foreign_exported_finish(struct wlr_xdg_foreign_exported *surface) { + wl_signal_emit_mutable(&surface->events.destroy, NULL); + surface->registry = NULL; + wl_list_remove(&surface->link); + wl_list_init(&surface->link); +} + +static void foreign_registry_handle_display_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xdg_foreign_registry *registry = + wl_container_of(listener, registry, display_destroy); + + wl_signal_emit_mutable(®istry->events.destroy, NULL); + + // Implementations are supposed to remove all surfaces + assert(wl_list_empty(®istry->exported_surfaces)); + free(registry); +} + + +struct wlr_xdg_foreign_registry *wlr_xdg_foreign_registry_create( + struct wl_display *display) { + struct wlr_xdg_foreign_registry *registry = calloc(1, sizeof(*registry)); + if (!registry) { + return NULL; + } + + registry->display_destroy.notify = foreign_registry_handle_display_destroy; + wl_display_add_destroy_listener(display, ®istry->display_destroy); + + wl_list_init(®istry->exported_surfaces); + wl_signal_init(®istry->events.destroy); + return registry; +} diff --git a/types/wlr_xdg_foreign_v1.c b/types/wlr_xdg_foreign_v1.c new file mode 100644 index 0000000..12cd4fc --- /dev/null +++ b/types/wlr_xdg_foreign_v1.c @@ -0,0 +1,420 @@ + +#include +#include +#include +#include +#include +#include +#include "xdg-foreign-unstable-v1-protocol.h" + +#define FOREIGN_V1_VERSION 1 + +static const struct zxdg_exported_v1_interface xdg_exported_impl; +static const struct zxdg_imported_v1_interface xdg_imported_impl; +static const struct zxdg_exporter_v1_interface xdg_exporter_impl; +static const struct zxdg_importer_v1_interface xdg_importer_impl; + +static struct wlr_xdg_imported_v1 *xdg_imported_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_imported_v1_interface, + &xdg_imported_impl)); + return wl_resource_get_user_data(resource); +} + +static void xdg_imported_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct wlr_xdg_toplevel *verify_is_toplevel(struct wl_resource *resource, + struct wlr_surface *surface) { + struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface); + if (xdg_surface == NULL || xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + wl_resource_post_error(resource, -1, "surface must be an xdg_toplevel"); + return NULL; + } + + return xdg_surface->toplevel; +} + +static void destroy_imported_child(struct wlr_xdg_imported_child_v1 *child) { + wl_list_remove(&child->xdg_toplevel_set_parent.link); + wl_list_remove(&child->xdg_toplevel_destroy.link); + wl_list_remove(&child->link); + free(child); +} + +static void handle_child_xdg_toplevel_destroy( + struct wl_listener *listener, void *data) { + struct wlr_xdg_imported_child_v1 *child = + wl_container_of(listener, child, xdg_toplevel_destroy); + destroy_imported_child(child); +} + +static void handle_xdg_toplevel_set_parent( + struct wl_listener *listener, void *data) { + struct wlr_xdg_imported_child_v1 *child = + wl_container_of(listener, child, xdg_toplevel_set_parent); + destroy_imported_child(child); +} + +static void xdg_imported_handle_set_parent_of(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *child_resource) { + struct wlr_xdg_imported_v1 *imported = + xdg_imported_from_resource(resource); + if (imported == NULL) { + return; + } + struct wlr_surface *wlr_surface = imported->exported->surface; + struct wlr_surface *wlr_surface_child = + wlr_surface_from_resource(child_resource); + + struct wlr_xdg_toplevel *child_toplevel = + verify_is_toplevel(resource, wlr_surface_child); + if (!child_toplevel) { + return; + } + + struct wlr_xdg_surface *surface = + wlr_xdg_surface_try_from_wlr_surface(wlr_surface); + + if (!surface->surface->mapped) { + wlr_xdg_toplevel_set_parent(child_toplevel, NULL); + return; + } + + struct wlr_xdg_imported_child_v1 *child; + wl_list_for_each(child, &imported->children, link) { + if (child->surface == wlr_surface_child) { + return; + } + } + + child = calloc(1, sizeof(*child)); + if (child == NULL) { + wl_client_post_no_memory(client); + return; + } + child->surface = wlr_surface_child; + child->xdg_toplevel_destroy.notify = handle_child_xdg_toplevel_destroy; + child->xdg_toplevel_set_parent.notify = handle_xdg_toplevel_set_parent; + + if (!wlr_xdg_toplevel_set_parent(child_toplevel, surface->toplevel)) { + wl_resource_post_error(surface->toplevel->resource, + XDG_TOPLEVEL_ERROR_INVALID_PARENT, + "a toplevel cannot be a parent of itself or its ancestor"); + free(child); + return; + } + + wlr_xdg_toplevel_set_parent(child_toplevel, surface->toplevel); + wl_signal_add(&child_toplevel->events.destroy, &child->xdg_toplevel_destroy); + wl_signal_add(&child_toplevel->events.set_parent, &child->xdg_toplevel_set_parent); + + wl_list_insert(&imported->children, &child->link); +} + +static const struct zxdg_imported_v1_interface xdg_imported_impl = { + .destroy = xdg_imported_handle_destroy, + .set_parent_of = xdg_imported_handle_set_parent_of +}; + +static struct wlr_xdg_exported_v1 *xdg_exported_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_exported_v1_interface, + &xdg_exported_impl)); + return wl_resource_get_user_data(resource); +} + +static void xdg_exported_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zxdg_exported_v1_interface xdg_exported_impl = { + .destroy = xdg_exported_handle_destroy +}; + +static void xdg_exporter_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct wlr_xdg_foreign_v1 *xdg_foreign_from_exporter_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_exporter_v1_interface, + &xdg_exporter_impl)); + return wl_resource_get_user_data(resource); +} + +static void destroy_imported(struct wlr_xdg_imported_v1 *imported) { + imported->exported = NULL; + struct wlr_xdg_imported_child_v1 *child, *child_tmp; + wl_list_for_each_safe(child, child_tmp, &imported->children, link) { + struct wlr_xdg_surface *xdg_child = + wlr_xdg_surface_try_from_wlr_surface(child->surface); + assert(xdg_child != NULL); + wlr_xdg_toplevel_set_parent(xdg_child->toplevel, NULL); + } + + wl_list_remove(&imported->exported_destroyed.link); + wl_list_init(&imported->exported_destroyed.link); + + wl_list_remove(&imported->link); + wl_list_init(&imported->link); + wl_resource_set_user_data(imported->resource, NULL); + free(imported); +} + +static void destroy_exported(struct wlr_xdg_exported_v1 *exported) { + wlr_xdg_foreign_exported_finish(&exported->base); + + wl_list_remove(&exported->xdg_toplevel_destroy.link); + wl_list_remove(&exported->link); + wl_resource_set_user_data(exported->resource, NULL); + free(exported); +} + +static void xdg_exported_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_xdg_exported_v1 *exported = + xdg_exported_from_resource(resource); + + if (exported) { + destroy_exported(exported); + } +} + +static void handle_xdg_toplevel_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_exported_v1 *exported = + wl_container_of(listener, exported, xdg_toplevel_destroy); + + destroy_exported(exported); +} + +static void xdg_exporter_handle_export(struct wl_client *wl_client, + struct wl_resource *client_resource, + uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_xdg_foreign_v1 *foreign = + xdg_foreign_from_exporter_resource(client_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_xdg_toplevel *xdg_toplevel = + verify_is_toplevel(client_resource, surface); + if (!xdg_toplevel) { + return; + } + + struct wlr_xdg_exported_v1 *exported = calloc(1, sizeof(*exported)); + if (exported == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + if (!wlr_xdg_foreign_exported_init(&exported->base, foreign->registry)) { + wl_client_post_no_memory(wl_client); + free(exported); + return; + } + + exported->base.surface = surface; + exported->resource = wl_resource_create(wl_client, &zxdg_exported_v1_interface, + wl_resource_get_version(client_resource), id); + if (exported->resource == NULL) { + wlr_xdg_foreign_exported_finish(&exported->base); + wl_client_post_no_memory(wl_client); + free(exported); + return; + } + + wl_resource_set_implementation(exported->resource, &xdg_exported_impl, + exported, xdg_exported_handle_resource_destroy); + + wl_list_insert(&foreign->exporter.objects, &exported->link); + + zxdg_exported_v1_send_handle(exported->resource, exported->base.handle); + + exported->xdg_toplevel_destroy.notify = handle_xdg_toplevel_destroy; + wl_signal_add(&xdg_toplevel->base->events.destroy, &exported->xdg_toplevel_destroy); +} + +static const struct zxdg_exporter_v1_interface xdg_exporter_impl = { + .destroy = xdg_exporter_handle_destroy, + .export = xdg_exporter_handle_export +}; + +static void xdg_exporter_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_foreign_v1 *foreign = data; + + struct wl_resource *exporter_resource = + wl_resource_create(wl_client, &zxdg_exporter_v1_interface, version, id); + if (exporter_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_resource_set_implementation(exporter_resource, &xdg_exporter_impl, + foreign, NULL); +} + +static struct wlr_xdg_foreign_v1 *xdg_foreign_from_importer_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_importer_v1_interface, + &xdg_importer_impl)); + return wl_resource_get_user_data(resource); +} + +static void xdg_importer_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void xdg_imported_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_xdg_imported_v1 *imported = xdg_imported_from_resource(resource); + if (!imported) { + return; + } + + destroy_imported(imported); +} + +static void xdg_imported_handle_exported_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xdg_imported_v1 *imported = + wl_container_of(listener, imported, exported_destroyed); + zxdg_imported_v1_send_destroyed(imported->resource); + destroy_imported(imported); +} + +static void xdg_importer_handle_import(struct wl_client *wl_client, + struct wl_resource *client_resource, + uint32_t id, + const char *handle) { + struct wlr_xdg_foreign_v1 *foreign = + xdg_foreign_from_importer_resource(client_resource); + + struct wlr_xdg_imported_v1 *imported = calloc(1, sizeof(*imported)); + if (imported == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + imported->exported = wlr_xdg_foreign_registry_find_by_handle( + foreign->registry, handle); + imported->resource = wl_resource_create(wl_client, &zxdg_imported_v1_interface, + wl_resource_get_version(client_resource), id); + if (imported->resource == NULL) { + wl_client_post_no_memory(wl_client); + free(imported); + return; + } + + wl_resource_set_implementation(imported->resource, &xdg_imported_impl, + imported, xdg_imported_handle_resource_destroy); + + if (imported->exported == NULL) { + wl_resource_set_user_data(imported->resource, NULL); + zxdg_imported_v1_send_destroyed(imported->resource); + free(imported); + return; + } + + wl_list_init(&imported->children); + wl_list_insert(&foreign->importer.objects, &imported->link); + + imported->exported_destroyed.notify = xdg_imported_handle_exported_destroy; + wl_signal_add(&imported->exported->events.destroy, &imported->exported_destroyed); +} + +static const struct zxdg_importer_v1_interface xdg_importer_impl = { + .destroy = xdg_importer_handle_destroy, + .import = xdg_importer_handle_import +}; + +static void xdg_importer_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_foreign_v1 *foreign = data; + + struct wl_resource *importer_resource = + wl_resource_create(wl_client, &zxdg_importer_v1_interface, version, id); + + if (importer_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_resource_set_implementation(importer_resource, &xdg_importer_impl, + foreign, NULL); +} + +static void xdg_foreign_destroy(struct wlr_xdg_foreign_v1 *foreign) { + if (!foreign) { + return; + } + + wl_signal_emit_mutable(&foreign->events.destroy, NULL); + wl_list_remove(&foreign->foreign_registry_destroy.link); + wl_list_remove(&foreign->display_destroy.link); + + wl_global_destroy(foreign->exporter.global); + wl_global_destroy(foreign->importer.global); + free(foreign); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_foreign_v1 *foreign = + wl_container_of(listener, foreign, display_destroy); + xdg_foreign_destroy(foreign); +} + +static void handle_foreign_registry_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xdg_foreign_v1 *foreign = + wl_container_of(listener, foreign, foreign_registry_destroy); + xdg_foreign_destroy(foreign); +} + +struct wlr_xdg_foreign_v1 *wlr_xdg_foreign_v1_create( + struct wl_display *display, struct wlr_xdg_foreign_registry *registry) { + struct wlr_xdg_foreign_v1 *foreign = calloc(1, sizeof(*foreign)); + if (!foreign) { + return NULL; + } + + foreign->exporter.global = wl_global_create(display, + &zxdg_exporter_v1_interface, + FOREIGN_V1_VERSION, foreign, + xdg_exporter_bind); + if (!foreign->exporter.global) { + free(foreign); + return NULL; + } + + foreign->importer.global = wl_global_create(display, + &zxdg_importer_v1_interface, + FOREIGN_V1_VERSION, foreign, + xdg_importer_bind); + if (!foreign->importer.global) { + wl_global_destroy(foreign->exporter.global); + free(foreign); + return NULL; + } + + foreign->registry = registry; + + wl_signal_init(&foreign->events.destroy); + wl_list_init(&foreign->exporter.objects); + wl_list_init(&foreign->importer.objects); + + foreign->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &foreign->display_destroy); + + foreign->foreign_registry_destroy.notify = handle_foreign_registry_destroy; + wl_signal_add(®istry->events.destroy, &foreign->foreign_registry_destroy); + + return foreign; +} diff --git a/types/wlr_xdg_foreign_v2.c b/types/wlr_xdg_foreign_v2.c new file mode 100644 index 0000000..4cfbe77 --- /dev/null +++ b/types/wlr_xdg_foreign_v2.c @@ -0,0 +1,423 @@ + +#include +#include +#include +#include +#include +#include +#include "xdg-foreign-unstable-v2-protocol.h" + +#define FOREIGN_V2_VERSION 1 + +static const struct zxdg_exported_v2_interface xdg_exported_impl; +static const struct zxdg_imported_v2_interface xdg_imported_impl; +static const struct zxdg_exporter_v2_interface xdg_exporter_impl; +static const struct zxdg_importer_v2_interface xdg_importer_impl; + +static struct wlr_xdg_imported_v2 *xdg_imported_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_imported_v2_interface, + &xdg_imported_impl)); + return wl_resource_get_user_data(resource); +} + +static void xdg_imported_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct wlr_xdg_toplevel *verify_is_toplevel(struct wl_resource *resource, + struct wlr_surface *surface) { + // Note: the error codes are the same for zxdg_exporter_v2 and + // zxdg_importer_v2 + + struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface); + if (xdg_surface == NULL || xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + wl_resource_post_error(resource, + ZXDG_EXPORTER_V2_ERROR_INVALID_SURFACE, + "surface must be an xdg_toplevel"); + return NULL; + } + + return xdg_surface->toplevel; +} + +static void destroy_imported_child(struct wlr_xdg_imported_child_v2 *child) { + wl_list_remove(&child->xdg_toplevel_set_parent.link); + wl_list_remove(&child->xdg_toplevel_destroy.link); + wl_list_remove(&child->link); + free(child); +} + +static void handle_child_xdg_toplevel_destroy( + struct wl_listener *listener, void *data) { + struct wlr_xdg_imported_child_v2 *child = + wl_container_of(listener, child, xdg_toplevel_destroy); + destroy_imported_child(child); +} + +static void handle_xdg_toplevel_set_parent( + struct wl_listener *listener, void *data) { + struct wlr_xdg_imported_child_v2 *child = + wl_container_of(listener, child, xdg_toplevel_set_parent); + destroy_imported_child(child); +} + +static void xdg_imported_handle_set_parent_of(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *child_resource) { + struct wlr_xdg_imported_v2 *imported = + xdg_imported_from_resource(resource); + if (imported == NULL) { + return; + } + struct wlr_surface *wlr_surface = imported->exported->surface; + struct wlr_surface *wlr_surface_child = + wlr_surface_from_resource(child_resource); + + struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(wlr_surface); + struct wlr_xdg_toplevel *child_toplevel = + verify_is_toplevel(resource, wlr_surface_child); + if (!child_toplevel) { + return; + } + + if (!surface->surface->mapped) { + wlr_xdg_toplevel_set_parent(child_toplevel, NULL); + return; + } + + struct wlr_xdg_imported_child_v2 *child; + wl_list_for_each(child, &imported->children, link) { + if (child->surface == wlr_surface_child) { + return; + } + } + + child = calloc(1, sizeof(*child)); + if (child == NULL) { + wl_client_post_no_memory(client); + return; + } + child->surface = wlr_surface_child; + child->xdg_toplevel_destroy.notify = handle_child_xdg_toplevel_destroy; + child->xdg_toplevel_set_parent.notify = handle_xdg_toplevel_set_parent; + + if (!wlr_xdg_toplevel_set_parent(child_toplevel, surface->toplevel)) { + wl_resource_post_error(surface->toplevel->resource, + XDG_TOPLEVEL_ERROR_INVALID_PARENT, + "a toplevel cannot be a parent of itself or its ancestor"); + free(child); + return; + } + + wlr_xdg_toplevel_set_parent(child_toplevel, surface->toplevel); + wl_signal_add(&child_toplevel->events.destroy, &child->xdg_toplevel_destroy); + wl_signal_add(&child_toplevel->events.set_parent, &child->xdg_toplevel_set_parent); + + wl_list_insert(&imported->children, &child->link); +} + +static const struct zxdg_imported_v2_interface xdg_imported_impl = { + .destroy = xdg_imported_handle_destroy, + .set_parent_of = xdg_imported_handle_set_parent_of +}; + +static struct wlr_xdg_exported_v2 *xdg_exported_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_exported_v2_interface, + &xdg_exported_impl)); + return wl_resource_get_user_data(resource); +} + +static void xdg_exported_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zxdg_exported_v2_interface xdg_exported_impl = { + .destroy = xdg_exported_handle_destroy +}; + +static void xdg_exporter_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct wlr_xdg_foreign_v2 *xdg_foreign_from_exporter_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_exporter_v2_interface, + &xdg_exporter_impl)); + return wl_resource_get_user_data(resource); +} + +static void destroy_imported(struct wlr_xdg_imported_v2 *imported) { + imported->exported = NULL; + struct wlr_xdg_imported_child_v2 *child, *child_tmp; + wl_list_for_each_safe(child, child_tmp, &imported->children, link) { + struct wlr_xdg_surface *xdg_child = + wlr_xdg_surface_try_from_wlr_surface(child->surface); + assert(xdg_child != NULL); + wlr_xdg_toplevel_set_parent(xdg_child->toplevel, NULL); + } + + wl_list_remove(&imported->exported_destroyed.link); + wl_list_init(&imported->exported_destroyed.link); + + wl_list_remove(&imported->link); + wl_list_init(&imported->link); + wl_resource_set_user_data(imported->resource, NULL); + free(imported); +} + +static void destroy_exported(struct wlr_xdg_exported_v2 *exported) { + wlr_xdg_foreign_exported_finish(&exported->base); + + wl_list_remove(&exported->xdg_toplevel_destroy.link); + wl_list_remove(&exported->link); + wl_resource_set_user_data(exported->resource, NULL); + free(exported); +} + +static void xdg_exported_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_xdg_exported_v2 *exported = + xdg_exported_from_resource(resource); + + if (exported) { + destroy_exported(exported); + } +} + +static void handle_xdg_toplevel_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_exported_v2 *exported = + wl_container_of(listener, exported, xdg_toplevel_destroy); + + destroy_exported(exported); +} + +static void xdg_exporter_handle_export(struct wl_client *wl_client, + struct wl_resource *client_resource, + uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_xdg_foreign_v2 *foreign = + xdg_foreign_from_exporter_resource(client_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_xdg_toplevel *xdg_toplevel = + verify_is_toplevel(client_resource, surface); + if (!xdg_toplevel) { + return; + } + + struct wlr_xdg_exported_v2 *exported = calloc(1, sizeof(*exported)); + if (exported == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + if (!wlr_xdg_foreign_exported_init(&exported->base, foreign->registry)) { + wl_client_post_no_memory(wl_client); + free(exported); + return; + } + + exported->base.surface = surface; + exported->resource = wl_resource_create(wl_client, &zxdg_exported_v2_interface, + wl_resource_get_version(client_resource), id); + if (exported->resource == NULL) { + wlr_xdg_foreign_exported_finish(&exported->base); + wl_client_post_no_memory(wl_client); + free(exported); + return; + } + + wl_resource_set_implementation(exported->resource, &xdg_exported_impl, + exported, xdg_exported_handle_resource_destroy); + + wl_list_insert(&foreign->exporter.objects, &exported->link); + + zxdg_exported_v2_send_handle(exported->resource, exported->base.handle); + + exported->xdg_toplevel_destroy.notify = handle_xdg_toplevel_destroy; + wl_signal_add(&xdg_toplevel->base->events.destroy, &exported->xdg_toplevel_destroy); +} + +static const struct zxdg_exporter_v2_interface xdg_exporter_impl = { + .destroy = xdg_exporter_handle_destroy, + .export_toplevel = xdg_exporter_handle_export +}; + +static void xdg_exporter_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_foreign_v2 *foreign = data; + + struct wl_resource *exporter_resource = + wl_resource_create(wl_client, &zxdg_exporter_v2_interface, version, id); + if (exporter_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_resource_set_implementation(exporter_resource, &xdg_exporter_impl, + foreign, NULL); +} + +static struct wlr_xdg_foreign_v2 *xdg_foreign_from_importer_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_importer_v2_interface, + &xdg_importer_impl)); + return wl_resource_get_user_data(resource); +} + +static void xdg_importer_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void xdg_imported_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_xdg_imported_v2 *imported = xdg_imported_from_resource(resource); + if (!imported) { + return; + } + + destroy_imported(imported); +} + +static void xdg_imported_handle_exported_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xdg_imported_v2 *imported = + wl_container_of(listener, imported, exported_destroyed); + zxdg_imported_v2_send_destroyed(imported->resource); + destroy_imported(imported); +} + +static void xdg_importer_handle_import(struct wl_client *wl_client, + struct wl_resource *client_resource, + uint32_t id, + const char *handle) { + struct wlr_xdg_foreign_v2 *foreign = + xdg_foreign_from_importer_resource(client_resource); + + struct wlr_xdg_imported_v2 *imported = calloc(1, sizeof(*imported)); + if (imported == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + imported->exported = wlr_xdg_foreign_registry_find_by_handle( + foreign->registry, handle); + imported->resource = wl_resource_create(wl_client, &zxdg_imported_v2_interface, + wl_resource_get_version(client_resource), id); + if (imported->resource == NULL) { + wl_client_post_no_memory(wl_client); + free(imported); + return; + } + + wl_resource_set_implementation(imported->resource, &xdg_imported_impl, + imported, xdg_imported_handle_resource_destroy); + + if (imported->exported == NULL) { + wl_resource_set_user_data(imported->resource, NULL); + zxdg_imported_v2_send_destroyed(imported->resource); + free(imported); + return; + } + + wl_list_init(&imported->children); + wl_list_insert(&foreign->importer.objects, &imported->link); + + imported->exported_destroyed.notify = xdg_imported_handle_exported_destroy; + wl_signal_add(&imported->exported->events.destroy, &imported->exported_destroyed); +} + +static const struct zxdg_importer_v2_interface xdg_importer_impl = { + .destroy = xdg_importer_handle_destroy, + .import_toplevel = xdg_importer_handle_import +}; + +static void xdg_importer_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_foreign_v2 *foreign = data; + + struct wl_resource *importer_resource = + wl_resource_create(wl_client, &zxdg_importer_v2_interface, version, id); + + if (importer_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_resource_set_implementation(importer_resource, &xdg_importer_impl, + foreign, NULL); +} + +static void xdg_foreign_destroy(struct wlr_xdg_foreign_v2 *foreign) { + if (!foreign) { + return; + } + + wl_signal_emit_mutable(&foreign->events.destroy, NULL); + wl_list_remove(&foreign->foreign_registry_destroy.link); + wl_list_remove(&foreign->display_destroy.link); + + wl_global_destroy(foreign->exporter.global); + wl_global_destroy(foreign->importer.global); + free(foreign); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_foreign_v2 *foreign = + wl_container_of(listener, foreign, display_destroy); + xdg_foreign_destroy(foreign); +} + +static void handle_foreign_registry_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xdg_foreign_v2 *foreign = + wl_container_of(listener, foreign, foreign_registry_destroy); + xdg_foreign_destroy(foreign); +} + +struct wlr_xdg_foreign_v2 *wlr_xdg_foreign_v2_create( + struct wl_display *display, struct wlr_xdg_foreign_registry *registry) { + struct wlr_xdg_foreign_v2 *foreign = calloc(1, sizeof(*foreign)); + if (!foreign) { + return NULL; + } + + foreign->exporter.global = wl_global_create(display, + &zxdg_exporter_v2_interface, + FOREIGN_V2_VERSION, foreign, + xdg_exporter_bind); + if (!foreign->exporter.global) { + free(foreign); + return NULL; + } + + foreign->importer.global = wl_global_create(display, + &zxdg_importer_v2_interface, + FOREIGN_V2_VERSION, foreign, + xdg_importer_bind); + if (!foreign->importer.global) { + wl_global_destroy(foreign->exporter.global); + free(foreign); + return NULL; + } + + foreign->registry = registry; + + wl_signal_init(&foreign->events.destroy); + wl_list_init(&foreign->exporter.objects); + wl_list_init(&foreign->importer.objects); + + foreign->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &foreign->display_destroy); + + foreign->foreign_registry_destroy.notify = handle_foreign_registry_destroy; + wl_signal_add(®istry->events.destroy, &foreign->foreign_registry_destroy); + + return foreign; +} diff --git a/types/wlr_xdg_output_v1.c b/types/wlr_xdg_output_v1.c new file mode 100644 index 0000000..895a0f0 --- /dev/null +++ b/types/wlr_xdg_output_v1.c @@ -0,0 +1,291 @@ +#include +#include +#include +#include +#include +#include +#include +#include "xdg-output-unstable-v1-protocol.h" + +#define OUTPUT_MANAGER_VERSION 3 +#define OUTPUT_DONE_DEPRECATED_SINCE_VERSION 3 +#define OUTPUT_DESCRIPTION_MUTABLE_SINCE_VERSION 3 + +static void output_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zxdg_output_v1_interface output_implementation = { + .destroy = output_handle_destroy, +}; + +static void output_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void output_send_details(struct wlr_xdg_output_v1 *xdg_output, + struct wl_resource *resource) { + zxdg_output_v1_send_logical_position(resource, + xdg_output->x, xdg_output->y); + zxdg_output_v1_send_logical_size(resource, + xdg_output->width, xdg_output->height); + if (wl_resource_get_version(resource) < OUTPUT_DONE_DEPRECATED_SINCE_VERSION) { + zxdg_output_v1_send_done(resource); + } +} + +static void output_update(struct wlr_xdg_output_v1 *xdg_output) { + struct wlr_output_layout_output *layout_output = xdg_output->layout_output; + bool updated = false; + + if (layout_output->x != xdg_output->x || layout_output->y != xdg_output->y) { + xdg_output->x = layout_output->x; + xdg_output->y = layout_output->y; + updated = true; + } + + int width, height; + wlr_output_effective_resolution(layout_output->output, &width, &height); + if (xdg_output->width != width || xdg_output->height != height) { + xdg_output->width = width; + xdg_output->height = height; + updated = true; + } + + if (updated) { + struct wl_resource *resource; + wl_resource_for_each(resource, &xdg_output->resources) { + output_send_details(xdg_output, resource); + } + + wlr_output_schedule_done(xdg_output->layout_output->output); + } +} + +static void output_destroy(struct wlr_xdg_output_v1 *output) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &output->resources) { + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } + wl_list_remove(&output->destroy.link); + wl_list_remove(&output->description.link); + wl_list_remove(&output->link); + free(output); +} + + +static void output_manager_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zxdg_output_manager_v1_interface + output_manager_implementation; + +static void output_manager_handle_get_xdg_output(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *output_resource) { + assert(wl_resource_instance_of(resource, &zxdg_output_manager_v1_interface, + &output_manager_implementation)); + + struct wlr_xdg_output_manager_v1 *manager = + wl_resource_get_user_data(resource); + struct wlr_output_layout *layout = manager->layout; + struct wlr_output *output = wlr_output_from_resource(output_resource); + + struct wl_resource *xdg_output_resource = wl_resource_create(client, + &zxdg_output_v1_interface, wl_resource_get_version(resource), id); + if (!xdg_output_resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(xdg_output_resource, &output_implementation, + NULL, output_handle_resource_destroy); + + if (output == NULL) { + wl_list_init(wl_resource_get_link(xdg_output_resource)); + return; + } + + struct wlr_output_layout_output *layout_output = + wlr_output_layout_get(layout, output); + assert(layout_output); + + struct wlr_xdg_output_v1 *_xdg_output, *xdg_output = NULL; + wl_list_for_each(_xdg_output, &manager->outputs, link) { + if (_xdg_output->layout_output == layout_output) { + xdg_output = _xdg_output; + break; + } + } + assert(xdg_output); + + + wl_list_insert(&xdg_output->resources, + wl_resource_get_link(xdg_output_resource)); + + // Name and description should only be sent once per output + uint32_t xdg_version = wl_resource_get_version(xdg_output_resource); + if (xdg_version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { + zxdg_output_v1_send_name(xdg_output_resource, output->name); + } + if (xdg_version >= ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION && + output->description != NULL) { + zxdg_output_v1_send_description(xdg_output_resource, + output->description); + } + + output_send_details(xdg_output, xdg_output_resource); + + uint32_t wl_version = wl_resource_get_version(output_resource); + if (wl_version >= WL_OUTPUT_DONE_SINCE_VERSION && + xdg_version >= OUTPUT_DONE_DEPRECATED_SINCE_VERSION) { + wl_output_send_done(output_resource); + } +} + +static const struct zxdg_output_manager_v1_interface + output_manager_implementation = { + .destroy = output_manager_handle_destroy, + .get_xdg_output = output_manager_handle_get_xdg_output, +}; + +static void output_manager_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_output_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(wl_client, + &zxdg_output_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, &output_manager_implementation, + manager, NULL); +} + +static void handle_output_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_output_v1 *output = wl_container_of(listener, output, destroy); + output_destroy(output); +} + +static void handle_output_description(struct wl_listener *listener, + void *data) { + struct wlr_xdg_output_v1 *xdg_output = + wl_container_of(listener, xdg_output, description); + struct wlr_output *output = xdg_output->layout_output->output; + + if (output->description == NULL) { + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &xdg_output->resources) { + if (wl_resource_get_version(resource) >= + OUTPUT_DESCRIPTION_MUTABLE_SINCE_VERSION) { + zxdg_output_v1_send_description(resource, output->description); + } + } +} + +static void add_output(struct wlr_xdg_output_manager_v1 *manager, + struct wlr_output_layout_output *layout_output) { + struct wlr_xdg_output_v1 *output = calloc(1, sizeof(*output)); + if (output == NULL) { + return; + } + wl_list_init(&output->resources); + output->manager = manager; + output->layout_output = layout_output; + output->destroy.notify = handle_output_destroy; + wl_signal_add(&layout_output->events.destroy, &output->destroy); + output->description.notify = handle_output_description; + wl_signal_add(&layout_output->output->events.description, + &output->description); + wl_list_insert(&manager->outputs, &output->link); + output_update(output); +} + +static void output_manager_send_details( + struct wlr_xdg_output_manager_v1 *manager) { + struct wlr_xdg_output_v1 *output; + wl_list_for_each(output, &manager->outputs, link) { + output_update(output); + } +} + +static void handle_layout_add(struct wl_listener *listener, void *data) { + struct wlr_xdg_output_manager_v1 *manager = + wl_container_of(listener, manager, layout_add); + struct wlr_output_layout_output *layout_output = data; + add_output(manager, layout_output); +} + +static void handle_layout_change(struct wl_listener *listener, void *data) { + struct wlr_xdg_output_manager_v1 *manager = + wl_container_of(listener, manager, layout_change); + output_manager_send_details(manager); +} + +static void manager_destroy(struct wlr_xdg_output_manager_v1 *manager) { + struct wlr_xdg_output_v1 *output, *tmp; + wl_list_for_each_safe(output, tmp, &manager->outputs, link) { + output_destroy(output); + } + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_list_remove(&manager->layout_add.link); + wl_list_remove(&manager->layout_change.link); + wl_list_remove(&manager->layout_destroy.link); + free(manager); +} + +static void handle_layout_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_output_manager_v1 *manager = + wl_container_of(listener, manager, layout_destroy); + manager_destroy(manager); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_output_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + manager_destroy(manager); +} + +struct wlr_xdg_output_manager_v1 *wlr_xdg_output_manager_v1_create( + struct wl_display *display, struct wlr_output_layout *layout) { + struct wlr_xdg_output_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + manager->layout = layout; + manager->global = wl_global_create(display, + &zxdg_output_manager_v1_interface, OUTPUT_MANAGER_VERSION, manager, + output_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + wl_list_init(&manager->outputs); + struct wlr_output_layout_output *layout_output; + wl_list_for_each(layout_output, &layout->outputs, link) { + add_output(manager, layout_output); + } + + wl_signal_init(&manager->events.destroy); + + manager->layout_add.notify = handle_layout_add; + wl_signal_add(&layout->events.add, &manager->layout_add); + manager->layout_change.notify = handle_layout_change; + wl_signal_add(&layout->events.change, &manager->layout_change); + manager->layout_destroy.notify = handle_layout_destroy; + wl_signal_add(&layout->events.destroy, &manager->layout_destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/xdg_shell/wlr_xdg_popup.c b/types/xdg_shell/wlr_xdg_popup.c new file mode 100644 index 0000000..e2038c8 --- /dev/null +++ b/types/xdg_shell/wlr_xdg_popup.c @@ -0,0 +1,532 @@ +#include +#include +#include +#include "types/wlr_xdg_shell.h" + +void handle_xdg_popup_ack_configure( + struct wlr_xdg_popup *popup, + struct wlr_xdg_popup_configure *configure) { + popup->pending.geometry = configure->geometry; + popup->pending.reactive = configure->rules.reactive; +} + +struct wlr_xdg_popup_configure *send_xdg_popup_configure( + struct wlr_xdg_popup *popup) { + struct wlr_xdg_popup_configure *configure = calloc(1, sizeof(*configure)); + if (configure == NULL) { + wl_resource_post_no_memory(popup->resource); + return NULL; + } + *configure = popup->scheduled; + + uint32_t version = wl_resource_get_version(popup->resource); + + if ((configure->fields & WLR_XDG_POPUP_CONFIGURE_REPOSITION_TOKEN) && + version >= XDG_POPUP_REPOSITIONED_SINCE_VERSION) { + xdg_popup_send_repositioned(popup->resource, + configure->reposition_token); + } + + struct wlr_box *geometry = &configure->geometry; + assert(geometry->width > 0 && geometry->height > 0); + xdg_popup_send_configure(popup->resource, + geometry->x, geometry->y, + geometry->width, geometry->height); + + popup->scheduled.fields = 0; + + return configure; +} + +static void xdg_popup_grab_end(struct wlr_xdg_popup_grab *popup_grab) { + struct wlr_xdg_popup *popup, *tmp; + wl_list_for_each_safe(popup, tmp, &popup_grab->popups, grab_link) { + xdg_popup_send_popup_done(popup->resource); + } + + wlr_seat_pointer_end_grab(popup_grab->seat); + wlr_seat_keyboard_end_grab(popup_grab->seat); + wlr_seat_touch_end_grab(popup_grab->seat); +} + +static void xdg_pointer_grab_enter(struct wlr_seat_pointer_grab *grab, + struct wlr_surface *surface, double sx, double sy) { + struct wlr_xdg_popup_grab *popup_grab = grab->data; + if (wl_resource_get_client(surface->resource) == popup_grab->client) { + wlr_seat_pointer_enter(grab->seat, surface, sx, sy); + } else { + wlr_seat_pointer_clear_focus(grab->seat); + } +} + +static void xdg_pointer_grab_clear_focus(struct wlr_seat_pointer_grab *grab) { + wlr_seat_pointer_clear_focus(grab->seat); +} + +static void xdg_pointer_grab_motion(struct wlr_seat_pointer_grab *grab, + uint32_t time, double sx, double sy) { + wlr_seat_pointer_send_motion(grab->seat, time, sx, sy); +} + +static uint32_t xdg_pointer_grab_button(struct wlr_seat_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state) { + uint32_t serial = + wlr_seat_pointer_send_button(grab->seat, time, button, state); + if (serial) { + return serial; + } else { + xdg_popup_grab_end(grab->data); + return 0; + } +} + +static void xdg_pointer_grab_axis(struct wlr_seat_pointer_grab *grab, + uint32_t time, enum wl_pointer_axis orientation, double value, + int32_t value_discrete, enum wl_pointer_axis_source source, + enum wl_pointer_axis_relative_direction relative_direction) { + wlr_seat_pointer_send_axis(grab->seat, time, orientation, value, + value_discrete, source, relative_direction); +} + +static void xdg_pointer_grab_frame(struct wlr_seat_pointer_grab *grab) { + wlr_seat_pointer_send_frame(grab->seat); +} + +static void xdg_pointer_grab_cancel(struct wlr_seat_pointer_grab *grab) { + xdg_popup_grab_end(grab->data); +} + +static const struct wlr_pointer_grab_interface xdg_pointer_grab_impl = { + .enter = xdg_pointer_grab_enter, + .clear_focus = xdg_pointer_grab_clear_focus, + .motion = xdg_pointer_grab_motion, + .button = xdg_pointer_grab_button, + .cancel = xdg_pointer_grab_cancel, + .axis = xdg_pointer_grab_axis, + .frame = xdg_pointer_grab_frame, +}; + +static void xdg_keyboard_grab_enter(struct wlr_seat_keyboard_grab *grab, + struct wlr_surface *surface, const uint32_t keycodes[], size_t num_keycodes, + const struct wlr_keyboard_modifiers *modifiers) { + // keyboard focus should remain on the popup +} + +static void xdg_keyboard_grab_clear_focus(struct wlr_seat_keyboard_grab *grab) { + // keyboard focus should remain on the popup +} + +static void xdg_keyboard_grab_key(struct wlr_seat_keyboard_grab *grab, uint32_t time, + uint32_t key, uint32_t state) { + wlr_seat_keyboard_send_key(grab->seat, time, key, state); +} + +static void xdg_keyboard_grab_modifiers(struct wlr_seat_keyboard_grab *grab, + const struct wlr_keyboard_modifiers *modifiers) { + wlr_seat_keyboard_send_modifiers(grab->seat, modifiers); +} + +static void xdg_keyboard_grab_cancel(struct wlr_seat_keyboard_grab *grab) { + wlr_seat_pointer_end_grab(grab->seat); +} + +static const struct wlr_keyboard_grab_interface xdg_keyboard_grab_impl = { + .enter = xdg_keyboard_grab_enter, + .clear_focus = xdg_keyboard_grab_clear_focus, + .key = xdg_keyboard_grab_key, + .modifiers = xdg_keyboard_grab_modifiers, + .cancel = xdg_keyboard_grab_cancel, +}; + +static uint32_t xdg_touch_grab_down(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + struct wlr_xdg_popup_grab *popup_grab = grab->data; + + if (wl_resource_get_client(point->surface->resource) != popup_grab->client) { + xdg_popup_grab_end(grab->data); + return 0; + } + + return wlr_seat_touch_send_down(grab->seat, point->surface, time, + point->touch_id, point->sx, point->sy); +} + +static void xdg_touch_grab_up(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + wlr_seat_touch_send_up(grab->seat, time, point->touch_id); +} + +static void xdg_touch_grab_motion(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + wlr_seat_touch_send_motion(grab->seat, time, point->touch_id, point->sx, + point->sy); +} + +static void xdg_touch_grab_enter(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { +} + +static void xdg_touch_grab_frame(struct wlr_seat_touch_grab *grab) { + wlr_seat_touch_send_frame(grab->seat); +} + +static void xdg_touch_grab_cancel(struct wlr_seat_touch_grab *grab) { + wlr_seat_touch_end_grab(grab->seat); +} + +static const struct wlr_touch_grab_interface xdg_touch_grab_impl = { + .down = xdg_touch_grab_down, + .up = xdg_touch_grab_up, + .motion = xdg_touch_grab_motion, + .enter = xdg_touch_grab_enter, + .frame = xdg_touch_grab_frame, + .cancel = xdg_touch_grab_cancel +}; + +static void destroy_xdg_popup_grab(struct wlr_xdg_popup_grab *xdg_grab) { + if (xdg_grab == NULL) { + return; + } + + wl_list_remove(&xdg_grab->seat_destroy.link); + + struct wlr_xdg_popup *popup, *tmp; + wl_list_for_each_safe(popup, tmp, &xdg_grab->popups, grab_link) { + wlr_xdg_popup_destroy(popup); + } + + wl_list_remove(&xdg_grab->link); + free(xdg_grab); +} + +static void xdg_popup_grab_handle_seat_destroy( + struct wl_listener *listener, void *data) { + struct wlr_xdg_popup_grab *xdg_grab = + wl_container_of(listener, xdg_grab, seat_destroy); + destroy_xdg_popup_grab(xdg_grab); +} + +static struct wlr_xdg_popup_grab *get_xdg_shell_popup_grab_from_seat( + struct wlr_xdg_shell *shell, struct wlr_seat *seat) { + struct wlr_xdg_popup_grab *xdg_grab; + wl_list_for_each(xdg_grab, &shell->popup_grabs, link) { + if (xdg_grab->seat == seat) { + return xdg_grab; + } + } + + xdg_grab = calloc(1, sizeof(*xdg_grab)); + if (!xdg_grab) { + return NULL; + } + + xdg_grab->pointer_grab.data = xdg_grab; + xdg_grab->pointer_grab.interface = &xdg_pointer_grab_impl; + xdg_grab->keyboard_grab.data = xdg_grab; + xdg_grab->keyboard_grab.interface = &xdg_keyboard_grab_impl; + xdg_grab->touch_grab.data = xdg_grab; + xdg_grab->touch_grab.interface = &xdg_touch_grab_impl; + + wl_list_init(&xdg_grab->popups); + + wl_list_insert(&shell->popup_grabs, &xdg_grab->link); + xdg_grab->seat = seat; + + xdg_grab->seat_destroy.notify = xdg_popup_grab_handle_seat_destroy; + wl_signal_add(&seat->events.destroy, &xdg_grab->seat_destroy); + + return xdg_grab; +} + +void handle_xdg_popup_client_commit(struct wlr_xdg_popup *popup) { + if (!popup->parent) { + wlr_surface_reject_pending(popup->base->surface, popup->base->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, "xdg_popup has no parent"); + return; + } +} + +static const struct xdg_popup_interface xdg_popup_implementation; + +struct wlr_xdg_popup *wlr_xdg_popup_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_popup_interface, + &xdg_popup_implementation)); + return wl_resource_get_user_data(resource); +} + +struct wlr_xdg_popup *wlr_xdg_popup_try_from_wlr_surface(struct wlr_surface *surface) { + struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface); + if (xdg_surface == NULL || xdg_surface->role != WLR_XDG_SURFACE_ROLE_POPUP) { + return NULL; + } + return xdg_surface->popup; +} + +static void xdg_popup_handle_grab(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial) { + struct wlr_xdg_popup *popup = + wlr_xdg_popup_from_resource(resource); + if (!popup) { + return; + } + + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + if (seat_client == NULL) { + wlr_xdg_popup_destroy(popup); + return; + } + if (popup->base->surface->mapped) { + wl_resource_post_error(popup->resource, + XDG_POPUP_ERROR_INVALID_GRAB, + "xdg_popup is already mapped"); + return; + } + + struct wlr_xdg_popup_grab *popup_grab = get_xdg_shell_popup_grab_from_seat( + popup->base->client->shell, seat_client->seat); + + if (!wl_list_empty(&popup->base->popups)) { + wl_resource_post_error(popup->base->client->resource, + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was not created on the topmost popup"); + return; + } + + popup_grab->client = popup->base->client->client; + popup->seat = seat_client->seat; + + wl_list_insert(&popup_grab->popups, &popup->grab_link); + + wlr_seat_pointer_start_grab(seat_client->seat, + &popup_grab->pointer_grab); + wlr_seat_keyboard_start_grab(seat_client->seat, + &popup_grab->keyboard_grab); + wlr_seat_touch_start_grab(seat_client->seat, + &popup_grab->touch_grab); +} + +static void xdg_popup_handle_reposition( + struct wl_client *client, struct wl_resource *resource, + struct wl_resource *positioner_resource, uint32_t token) { + struct wlr_xdg_popup *popup = + wlr_xdg_popup_from_resource(resource); + if (!popup) { + return; + } + + struct wlr_xdg_positioner *positioner = + wlr_xdg_positioner_from_resource(positioner_resource); + wlr_xdg_positioner_rules_get_geometry( + &positioner->rules, &popup->scheduled.geometry); + popup->scheduled.rules = positioner->rules; + + popup->scheduled.fields |= WLR_XDG_POPUP_CONFIGURE_REPOSITION_TOKEN; + popup->scheduled.reposition_token = token; + + wlr_xdg_surface_schedule_configure(popup->base); + + wl_signal_emit_mutable(&popup->events.reposition, NULL); +} + +static void xdg_popup_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_popup *popup = + wlr_xdg_popup_from_resource(resource); + + if (popup && !wl_list_empty(&popup->base->popups)) { + wl_resource_post_error(popup->base->client->resource, + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was destroyed while it was not the topmost popup"); + return; + } + + wl_resource_destroy(resource); +} + +static const struct xdg_popup_interface xdg_popup_implementation = { + .destroy = xdg_popup_handle_destroy, + .grab = xdg_popup_handle_grab, + .reposition = xdg_popup_handle_reposition, +}; + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_xdg_popup_state), +}; + +static void xdg_popup_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_popup *popup = + wlr_xdg_popup_from_resource(resource); + if (popup == NULL) { + return; + } + wlr_xdg_popup_destroy(popup); +} + +void create_xdg_popup(struct wlr_xdg_surface *surface, + struct wlr_xdg_surface *parent, + struct wlr_xdg_positioner *positioner, uint32_t id) { + if (positioner->rules.size.width == 0 || + positioner->rules.anchor_rect.width == 0) { + wl_resource_post_error(surface->client->resource, + XDG_WM_BASE_ERROR_INVALID_POSITIONER, + "positioner object is not complete"); + return; + } + + if (!set_xdg_surface_role(surface, WLR_XDG_SURFACE_ROLE_POPUP)) { + return; + } + + if (parent != NULL && parent->role == WLR_XDG_SURFACE_ROLE_NONE) { + wl_resource_post_error(surface->client->resource, XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT, + "a popup parent must have a role"); + return; + } + + assert(surface->popup == NULL); + surface->popup = calloc(1, sizeof(*surface->popup)); + if (!surface->popup) { + wl_resource_post_no_memory(surface->resource); + return; + } + surface->popup->base = surface; + + if (!wlr_surface_synced_init(&surface->popup->synced, surface->surface, + &surface_synced_impl, &surface->popup->pending, + &surface->popup->current)) { + goto error_popup; + } + + surface->popup->resource = wl_resource_create( + surface->client->client, &xdg_popup_interface, + wl_resource_get_version(surface->resource), id); + if (surface->popup->resource == NULL) { + goto error_synced; + } + wl_resource_set_implementation(surface->popup->resource, + &xdg_popup_implementation, surface->popup, + xdg_popup_handle_resource_destroy); + + surface->role = WLR_XDG_SURFACE_ROLE_POPUP; + + wlr_xdg_positioner_rules_get_geometry( + &positioner->rules, &surface->popup->scheduled.geometry); + surface->popup->scheduled.rules = positioner->rules; + + wl_signal_init(&surface->popup->events.destroy); + wl_signal_init(&surface->popup->events.reposition); + + if (parent) { + surface->popup->parent = parent->surface; + wl_list_insert(&parent->popups, &surface->popup->link); + wl_signal_emit_mutable(&parent->events.new_popup, surface->popup); + } else { + wl_list_init(&surface->popup->link); + } + + set_xdg_surface_role_object(surface, surface->popup->resource); + + wl_signal_emit_mutable(&surface->client->shell->events.new_popup, surface->popup); + + return; + +error_synced: + wlr_surface_synced_finish(&surface->popup->synced); +error_popup: + free(surface->popup); + surface->popup = NULL; + wl_resource_post_no_memory(surface->resource); +} + +void reset_xdg_popup(struct wlr_xdg_popup *popup) { + if (popup->seat != NULL) { + struct wlr_xdg_popup_grab *grab = + get_xdg_shell_popup_grab_from_seat( + popup->base->client->shell, popup->seat); + + wl_list_remove(&popup->grab_link); + + if (wl_list_empty(&grab->popups)) { + if (grab->seat->pointer_state.grab == &grab->pointer_grab) { + wlr_seat_pointer_end_grab(grab->seat); + } + if (grab->seat->keyboard_state.grab == &grab->keyboard_grab) { + wlr_seat_keyboard_end_grab(grab->seat); + } + if (grab->seat->touch_state.grab == &grab->touch_grab) { + wlr_seat_touch_end_grab(grab->seat); + } + + destroy_xdg_popup_grab(grab); + } + + popup->seat = NULL; + } +} + +void destroy_xdg_popup(struct wlr_xdg_popup *popup) { + wlr_surface_unmap(popup->base->surface); + reset_xdg_popup(popup); + + wl_signal_emit_mutable(&popup->events.destroy, NULL); + + wlr_surface_synced_finish(&popup->synced); + popup->base->popup = NULL; + wl_list_remove(&popup->link); + wl_resource_set_user_data(popup->resource, NULL); + free(popup); +} + +void wlr_xdg_popup_destroy(struct wlr_xdg_popup *popup) { + if (popup == NULL) { + return; + } + + struct wlr_xdg_popup *child, *child_tmp; + wl_list_for_each_safe(child, child_tmp, &popup->base->popups, link) { + wlr_xdg_popup_destroy(child); + } + + xdg_popup_send_popup_done(popup->resource); + destroy_xdg_popup(popup); +} + +void wlr_xdg_popup_get_toplevel_coords(struct wlr_xdg_popup *popup, + int popup_sx, int popup_sy, int *toplevel_sx, int *toplevel_sy) { + struct wlr_surface *parent = popup->parent; + struct wlr_xdg_surface *xdg_surface; + while ((xdg_surface = wlr_xdg_surface_try_from_wlr_surface(parent))) { + if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP && xdg_surface->popup != NULL) { + popup_sx += xdg_surface->popup->current.geometry.x; + popup_sy += xdg_surface->popup->current.geometry.y; + parent = xdg_surface->popup->parent; + } else { + popup_sx += xdg_surface->current.geometry.x; + popup_sy += xdg_surface->current.geometry.y; + break; + } + } + assert(parent); + + *toplevel_sx = popup_sx; + *toplevel_sy = popup_sy; +} + +void wlr_xdg_popup_unconstrain_from_box(struct wlr_xdg_popup *popup, + const struct wlr_box *toplevel_space_box) { + int toplevel_sx, toplevel_sy; + wlr_xdg_popup_get_toplevel_coords(popup, + 0, 0, &toplevel_sx, &toplevel_sy); + struct wlr_box popup_constraint = { + .x = toplevel_space_box->x - toplevel_sx, + .y = toplevel_space_box->y - toplevel_sy, + .width = toplevel_space_box->width, + .height = toplevel_space_box->height, + }; + wlr_xdg_positioner_rules_unconstrain_box(&popup->scheduled.rules, + &popup_constraint, &popup->scheduled.geometry); + wlr_xdg_surface_schedule_configure(popup->base); +} diff --git a/types/xdg_shell/wlr_xdg_positioner.c b/types/xdg_shell/wlr_xdg_positioner.c new file mode 100644 index 0000000..6a991bb --- /dev/null +++ b/types/xdg_shell/wlr_xdg_positioner.c @@ -0,0 +1,503 @@ +#include +#include +#include +#include "types/wlr_xdg_shell.h" + +static const struct xdg_positioner_interface xdg_positioner_implementation; + +struct wlr_xdg_positioner *wlr_xdg_positioner_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_positioner_interface, + &xdg_positioner_implementation)); + return wl_resource_get_user_data(resource); +} + +static void xdg_positioner_handle_set_size(struct wl_client *client, + struct wl_resource *resource, int32_t width, int32_t height) { + struct wlr_xdg_positioner *positioner = + wlr_xdg_positioner_from_resource(resource); + + if (width < 1 || height < 1) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "width and height must be positive and non-zero"); + return; + } + + positioner->rules.size.width = width; + positioner->rules.size.height = height; +} + +static void xdg_positioner_handle_set_anchor_rect(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y, int32_t width, + int32_t height) { + struct wlr_xdg_positioner *positioner = + wlr_xdg_positioner_from_resource(resource); + + if (width < 0 || height < 0) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "width and height must be positive"); + return; + } + + positioner->rules.anchor_rect.x = x; + positioner->rules.anchor_rect.y = y; + positioner->rules.anchor_rect.width = width; + positioner->rules.anchor_rect.height = height; +} + +static void xdg_positioner_handle_set_anchor(struct wl_client *client, + struct wl_resource *resource, uint32_t anchor) { + struct wlr_xdg_positioner *positioner = + wlr_xdg_positioner_from_resource(resource); + + if (anchor > XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "invalid anchor value"); + return; + } + + positioner->rules.anchor = anchor; +} + +static void xdg_positioner_handle_set_gravity(struct wl_client *client, + struct wl_resource *resource, uint32_t gravity) { + struct wlr_xdg_positioner *positioner = + wlr_xdg_positioner_from_resource(resource); + + if (gravity > XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "invalid gravity value"); + return; + } + + positioner->rules.gravity = gravity; +} + +static void xdg_positioner_handle_set_constraint_adjustment( + struct wl_client *client, struct wl_resource *resource, + uint32_t constraint_adjustment) { + struct wlr_xdg_positioner *positioner = + wlr_xdg_positioner_from_resource(resource); + + positioner->rules.constraint_adjustment = constraint_adjustment; +} + +static void xdg_positioner_handle_set_offset(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y) { + struct wlr_xdg_positioner *positioner = + wlr_xdg_positioner_from_resource(resource); + + positioner->rules.offset.x = x; + positioner->rules.offset.y = y; +} + +static void xdg_positioner_handle_set_reactive( + struct wl_client *client, struct wl_resource *resource) { + struct wlr_xdg_positioner *positioner = + wlr_xdg_positioner_from_resource(resource); + + positioner->rules.reactive = true; +} + +static void xdg_positioner_handle_set_parent_configure( + struct wl_client *client, struct wl_resource *resource, + uint32_t serial) { + struct wlr_xdg_positioner *positioner = + wlr_xdg_positioner_from_resource(resource); + + positioner->rules.has_parent_configure_serial = true; + positioner->rules.parent_configure_serial = serial; +} + +static void xdg_positioner_handle_set_parent_size(struct wl_client *client, + struct wl_resource *resource, + int32_t parent_width, int32_t parent_height) { + struct wlr_xdg_positioner *positioner = + wlr_xdg_positioner_from_resource(resource); + + positioner->rules.parent_size.width = parent_width; + positioner->rules.parent_size.height = parent_height; +} + +static void xdg_positioner_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct xdg_positioner_interface + xdg_positioner_implementation = { + .destroy = xdg_positioner_handle_destroy, + .set_size = xdg_positioner_handle_set_size, + .set_anchor_rect = xdg_positioner_handle_set_anchor_rect, + .set_anchor = xdg_positioner_handle_set_anchor, + .set_gravity = xdg_positioner_handle_set_gravity, + .set_constraint_adjustment = + xdg_positioner_handle_set_constraint_adjustment, + .set_offset = xdg_positioner_handle_set_offset, + .set_reactive = xdg_positioner_handle_set_reactive, + .set_parent_size = xdg_positioner_handle_set_parent_size, + .set_parent_configure = xdg_positioner_handle_set_parent_configure, +}; + +static void xdg_positioner_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_xdg_positioner *positioner = + wlr_xdg_positioner_from_resource(resource); + free(positioner); +} + +void create_xdg_positioner(struct wlr_xdg_client *client, uint32_t id) { + struct wlr_xdg_positioner *positioner = calloc(1, sizeof(*positioner)); + if (positioner == NULL) { + wl_client_post_no_memory(client->client); + return; + } + + positioner->resource = wl_resource_create(client->client, + &xdg_positioner_interface, + wl_resource_get_version(client->resource), + id); + if (positioner->resource == NULL) { + free(positioner); + wl_client_post_no_memory(client->client); + return; + } + wl_resource_set_implementation(positioner->resource, + &xdg_positioner_implementation, + positioner, xdg_positioner_handle_resource_destroy); +} + +static uint32_t xdg_positioner_anchor_to_wlr_edges( + enum xdg_positioner_anchor anchor) { + switch (anchor) { + case XDG_POSITIONER_ANCHOR_NONE: + return WLR_EDGE_NONE; + case XDG_POSITIONER_ANCHOR_TOP: + return WLR_EDGE_TOP; + case XDG_POSITIONER_ANCHOR_TOP_LEFT: + return WLR_EDGE_TOP | WLR_EDGE_LEFT; + case XDG_POSITIONER_ANCHOR_TOP_RIGHT: + return WLR_EDGE_TOP | WLR_EDGE_RIGHT; + case XDG_POSITIONER_ANCHOR_BOTTOM: + return WLR_EDGE_BOTTOM; + case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: + return WLR_EDGE_BOTTOM | WLR_EDGE_LEFT; + case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: + return WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT; + case XDG_POSITIONER_ANCHOR_LEFT: + return WLR_EDGE_LEFT; + case XDG_POSITIONER_ANCHOR_RIGHT: + return WLR_EDGE_RIGHT; + } + + abort(); // Unreachable +} + +static uint32_t xdg_positioner_gravity_to_wlr_edges( + enum xdg_positioner_gravity gravity) { + // Gravity and edge enums are the same + return xdg_positioner_anchor_to_wlr_edges((enum xdg_positioner_anchor)gravity); +} + +void wlr_xdg_positioner_rules_get_geometry( + const struct wlr_xdg_positioner_rules *rules, struct wlr_box *box) { + box->x = rules->offset.x; + box->y = rules->offset.y; + box->width = rules->size.width; + box->height = rules->size.height; + + uint32_t edges = xdg_positioner_anchor_to_wlr_edges(rules->anchor); + + if (edges & WLR_EDGE_TOP) { + box->y += rules->anchor_rect.y; + } else if (edges & WLR_EDGE_BOTTOM) { + box->y += rules->anchor_rect.y + rules->anchor_rect.height; + } else { + box->y += rules->anchor_rect.y + rules->anchor_rect.height / 2; + } + + if (edges & WLR_EDGE_LEFT) { + box->x += rules->anchor_rect.x; + } else if (edges & WLR_EDGE_RIGHT) { + box->x += rules->anchor_rect.x + rules->anchor_rect.width; + } else { + box->x += rules->anchor_rect.x + rules->anchor_rect.width / 2; + } + + edges = xdg_positioner_gravity_to_wlr_edges(rules->gravity); + + if (edges & WLR_EDGE_TOP) { + box->y -= box->height; + } else if (~edges & WLR_EDGE_BOTTOM) { + box->y -= box->height / 2; + } + + if (edges & WLR_EDGE_LEFT) { + box->x -= box->width; + } else if (~edges & WLR_EDGE_RIGHT) { + box->x -= box->width / 2; + } +} + +static enum xdg_positioner_anchor xdg_positioner_anchor_invert_x( + enum xdg_positioner_anchor anchor) { + switch (anchor) { + case XDG_POSITIONER_ANCHOR_LEFT: + return XDG_POSITIONER_ANCHOR_RIGHT; + case XDG_POSITIONER_ANCHOR_RIGHT: + return XDG_POSITIONER_ANCHOR_LEFT; + case XDG_POSITIONER_ANCHOR_TOP_LEFT: + return XDG_POSITIONER_ANCHOR_TOP_RIGHT; + case XDG_POSITIONER_ANCHOR_TOP_RIGHT: + return XDG_POSITIONER_ANCHOR_TOP_LEFT; + case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: + return XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT; + case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: + return XDG_POSITIONER_ANCHOR_BOTTOM_LEFT; + default: + return anchor; + } +} + +static enum xdg_positioner_gravity xdg_positioner_gravity_invert_x( + enum xdg_positioner_gravity gravity) { + // gravity and edge enums are the same + return (enum xdg_positioner_gravity)xdg_positioner_anchor_invert_x( + (enum xdg_positioner_anchor)gravity); +} + +static enum xdg_positioner_anchor xdg_positioner_anchor_invert_y( + enum xdg_positioner_anchor anchor) { + switch (anchor) { + case XDG_POSITIONER_ANCHOR_TOP: + return XDG_POSITIONER_ANCHOR_BOTTOM; + case XDG_POSITIONER_ANCHOR_BOTTOM: + return XDG_POSITIONER_ANCHOR_TOP; + case XDG_POSITIONER_ANCHOR_TOP_LEFT: + return XDG_POSITIONER_ANCHOR_BOTTOM_LEFT; + case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: + return XDG_POSITIONER_ANCHOR_TOP_LEFT; + case XDG_POSITIONER_ANCHOR_TOP_RIGHT: + return XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT; + case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: + return XDG_POSITIONER_ANCHOR_TOP_RIGHT; + default: + return anchor; + } +} + +static enum xdg_positioner_gravity xdg_positioner_gravity_invert_y( + enum xdg_positioner_gravity gravity) { + // gravity and edge enums are the same + return (enum xdg_positioner_gravity)xdg_positioner_anchor_invert_y( + (enum xdg_positioner_anchor)gravity); +} + +/** + * Distances from each edge of the box to the corresponding edge of + * the anchor rect. Each distance is positive if the edge is outside + * the anchor rect, and negative if the edge is inside it. + */ +struct constraint_offsets { + int top; + int bottom; + int left; + int right; +}; + +static bool is_unconstrained(const struct constraint_offsets *offsets) { + return offsets->top <= 0 && offsets->bottom <= 0 && + offsets->left <= 0 && offsets->right <= 0; +} + +static void get_constrained_box_offsets(const struct wlr_box *constraint, + const struct wlr_box *box, struct constraint_offsets *offsets) { + offsets->left = constraint->x - box->x; + offsets->right = box->x + box->width - constraint->x - constraint->width; + offsets->top = constraint->y - box->y; + offsets->bottom = box->y + box->height - constraint->y - constraint->height; +} + +static bool xdg_positioner_rules_unconstrain_by_flip( + const struct wlr_xdg_positioner_rules *rules, + const struct wlr_box *constraint, struct wlr_box *box, + struct constraint_offsets *offsets) { + // If none of the edges are constrained, no need to flip. + // If both edges are constrained, the box is bigger than + // the anchor rect and flipping won't help anyway. + bool flip_x = ((offsets->left > 0) ^ (offsets->right > 0)) && + (rules->constraint_adjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X); + bool flip_y = ((offsets->top > 0) ^ (offsets->bottom > 0)) && + (rules->constraint_adjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y); + + if (!flip_x && !flip_y) { + return false; + } + + struct wlr_xdg_positioner_rules flipped = *rules; + if (flip_x) { + flipped.anchor = xdg_positioner_anchor_invert_x(flipped.anchor); + flipped.gravity = xdg_positioner_gravity_invert_x(flipped.gravity); + } + if (flip_y) { + flipped.anchor = xdg_positioner_anchor_invert_y(flipped.anchor); + flipped.gravity = xdg_positioner_gravity_invert_y(flipped.gravity); + } + + struct wlr_box flipped_box; + wlr_xdg_positioner_rules_get_geometry(&flipped, &flipped_box); + struct constraint_offsets flipped_offsets; + get_constrained_box_offsets(constraint, &flipped_box, &flipped_offsets); + + // Only apply flipping if it helps + if (flipped_offsets.left <= 0 && flipped_offsets.right <= 0) { + box->x = flipped_box.x; + offsets->left = flipped_offsets.left; + offsets->right = flipped_offsets.right; + } + if (flipped_offsets.top <= 0 && flipped_offsets.bottom <= 0) { + box->y = flipped_box.y; + offsets->top = flipped_offsets.top; + offsets->bottom = flipped_offsets.bottom; + } + + return is_unconstrained(offsets); +} + +static bool xdg_positioner_rules_unconstrain_by_slide( + const struct wlr_xdg_positioner_rules *rules, + const struct wlr_box *constraint, struct wlr_box *box, + struct constraint_offsets *offsets) { + uint32_t gravity = xdg_positioner_gravity_to_wlr_edges(rules->gravity); + + bool slide_x = (offsets->left > 0 || offsets->right > 0) && + (rules->constraint_adjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X); + bool slide_y = (offsets->top > 0 || offsets->bottom > 0) && + (rules->constraint_adjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y); + + if (!slide_x && !slide_y) { + return false; + } + + if (slide_x) { + if (offsets->left > 0 && offsets->right > 0) { + // The protocol states: "First try to slide towards the direction of + // the gravity [...] Then try to slide towards the opposite direction + // of the gravity". The only situation where this order matters is when + // the box is bigger than the anchor rect and completely includes it. + // In this case, the second slide will fail immediately, so simply + // slide towards the direction of the gravity. + // Note that the protocol doesn't specify the behavior when there is no + // gravity on the axis (which is what e.g. GTK tooltips use). In this + // case, fall back to sliding the box to the right/bottom, which is what + // GTK X11 popup adjustment code does. + if (gravity & WLR_EDGE_LEFT) { + box->x -= offsets->right; + } else { + box->x += offsets->left; + } + } else { + // If at least one edge is already unconstrained, the order of slide + // attempts doesn't matter. Slide for the minimal distance needed to + // satisfy the requirement of constraining one edge or unconstraining + // another. + int abs_left = offsets->left > 0 ? offsets->left : -offsets->left; + int abs_right = offsets->right > 0 ? offsets->right : -offsets->right; + if (abs_left < abs_right) { + box->x += offsets->left; + } else { + box->x -= offsets->right; + } + } + } + if (slide_y) { + if (offsets->top > 0 && offsets->bottom > 0) { + if (gravity & WLR_EDGE_TOP) { + box->y -= offsets->bottom; + } else { + box->y += offsets->top; + } + } else { + int abs_top = offsets->top > 0 ? offsets->top : -offsets->top; + int abs_bottom = offsets->bottom > 0 ? offsets->bottom : -offsets->bottom; + if (abs_top < abs_bottom) { + box->y += offsets->top; + } else { + box->y -= offsets->bottom; + } + } + } + + get_constrained_box_offsets(constraint, box, offsets); + return is_unconstrained(offsets); +} + +static bool xdg_positioner_rules_unconstrain_by_resize( + const struct wlr_xdg_positioner_rules *rules, + const struct wlr_box *constraint, struct wlr_box *box, + struct constraint_offsets *offsets) { + bool resize_x = (offsets->left > 0 || offsets->right > 0) && + (rules->constraint_adjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X); + bool resize_y = (offsets->top > 0 || offsets->bottom > 0) && + (rules->constraint_adjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y); + + if (!resize_x && !resize_y) { + return false; + } + + if (offsets->left < 0) { + offsets->left = 0; + } + if (offsets->right < 0) { + offsets->right = 0; + } + if (offsets->top < 0) { + offsets->top = 0; + } + if (offsets->bottom < 0) { + offsets->bottom = 0; + } + + // Try to satisfy the constraints by clipping the box. + struct wlr_box resized_box = *box; + if (resize_x) { + resized_box.x += offsets->left; + resized_box.width -= offsets->left + offsets->right; + } + if (resize_y) { + resized_box.y += offsets->top; + resized_box.height -= offsets->top + offsets->bottom; + } + + if (wlr_box_empty(&resized_box)) { + return false; + } + + *box = resized_box; + get_constrained_box_offsets(constraint, box, offsets); + return is_unconstrained(offsets); +} + +void wlr_xdg_positioner_rules_unconstrain_box( + const struct wlr_xdg_positioner_rules *rules, + const struct wlr_box *constraint, struct wlr_box *box) { + struct constraint_offsets offsets; + get_constrained_box_offsets(constraint, box, &offsets); + if (is_unconstrained(&offsets)) { + // Already unconstrained + return; + } + if (xdg_positioner_rules_unconstrain_by_flip(rules, constraint, box, &offsets)) { + return; + } + if (xdg_positioner_rules_unconstrain_by_slide(rules, constraint, box, &offsets)) { + return; + } + if (xdg_positioner_rules_unconstrain_by_resize(rules, constraint, box, &offsets)) { + return; + } +} diff --git a/types/xdg_shell/wlr_xdg_shell.c b/types/xdg_shell/wlr_xdg_shell.c new file mode 100644 index 0000000..3baea04 --- /dev/null +++ b/types/xdg_shell/wlr_xdg_shell.c @@ -0,0 +1,169 @@ +#include +#include +#include "types/wlr_xdg_shell.h" + +#define WM_BASE_VERSION 6 + +static const struct xdg_wm_base_interface xdg_shell_impl; + +static struct wlr_xdg_client *xdg_client_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_wm_base_interface, + &xdg_shell_impl)); + return wl_resource_get_user_data(resource); +} + +static void xdg_shell_handle_create_positioner(struct wl_client *wl_client, + struct wl_resource *resource, uint32_t id) { + struct wlr_xdg_client *client = + xdg_client_from_resource(resource); + create_xdg_positioner(client, id); +} + +static void xdg_shell_handle_get_xdg_surface(struct wl_client *wl_client, + struct wl_resource *client_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_xdg_client *client = + xdg_client_from_resource(client_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + create_xdg_surface(client, surface, id); +} + +static void xdg_shell_handle_pong(struct wl_client *wl_client, + struct wl_resource *resource, uint32_t serial) { + struct wlr_xdg_client *client = xdg_client_from_resource(resource); + + if (client->ping_serial != serial) { + return; + } + + wl_event_source_timer_update(client->ping_timer, 0); + client->ping_serial = 0; +} + +static void xdg_shell_handle_destroy(struct wl_client *wl_client, + struct wl_resource *resource) { + struct wlr_xdg_client *client = xdg_client_from_resource(resource); + + if (!wl_list_empty(&client->surfaces)) { + wl_resource_post_error(client->resource, + XDG_WM_BASE_ERROR_DEFUNCT_SURFACES, + "xdg_wm_base was destroyed before children"); + return; + } + + wl_resource_destroy(resource); +} + +static const struct xdg_wm_base_interface xdg_shell_impl = { + .destroy = xdg_shell_handle_destroy, + .create_positioner = xdg_shell_handle_create_positioner, + .get_xdg_surface = xdg_shell_handle_get_xdg_surface, + .pong = xdg_shell_handle_pong, +}; + +static void xdg_client_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_client *client = xdg_client_from_resource(resource); + + struct wlr_xdg_surface *surface, *tmp = NULL; + wl_list_for_each_safe(surface, tmp, &client->surfaces, link) { + destroy_xdg_surface(surface); + } + + if (client->ping_timer != NULL) { + wl_event_source_remove(client->ping_timer); + } + + wl_list_remove(&client->link); + free(client); +} + +static int xdg_client_ping_timeout(void *user_data) { + struct wlr_xdg_client *client = user_data; + + struct wlr_xdg_surface *surface; + wl_list_for_each(surface, &client->surfaces, link) { + wl_signal_emit_mutable(&surface->events.ping_timeout, NULL); + } + + client->ping_serial = 0; + return 1; +} + +static void xdg_shell_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_shell *xdg_shell = data; + + struct wlr_xdg_client *client = calloc(1, sizeof(*client)); + if (client == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_list_init(&client->surfaces); + + client->resource = + wl_resource_create(wl_client, &xdg_wm_base_interface, version, id); + if (client->resource == NULL) { + free(client); + wl_client_post_no_memory(wl_client); + return; + } + client->client = wl_client; + client->shell = xdg_shell; + + wl_resource_set_implementation(client->resource, &xdg_shell_impl, client, + xdg_client_handle_resource_destroy); + wl_list_insert(&xdg_shell->clients, &client->link); + + struct wl_display *display = wl_client_get_display(client->client); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + client->ping_timer = wl_event_loop_add_timer(loop, + xdg_client_ping_timeout, client); + if (client->ping_timer == NULL) { + wl_client_post_no_memory(client->client); + } +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_shell *xdg_shell = + wl_container_of(listener, xdg_shell, display_destroy); + wl_signal_emit_mutable(&xdg_shell->events.destroy, xdg_shell); + wl_list_remove(&xdg_shell->display_destroy.link); + wl_global_destroy(xdg_shell->global); + free(xdg_shell); +} + +struct wlr_xdg_shell *wlr_xdg_shell_create(struct wl_display *display, + uint32_t version) { + assert(version <= WM_BASE_VERSION); + + struct wlr_xdg_shell *xdg_shell = calloc(1, sizeof(*xdg_shell)); + if (!xdg_shell) { + return NULL; + } + + xdg_shell->version = version; + xdg_shell->ping_timeout = 10000; + + wl_list_init(&xdg_shell->clients); + wl_list_init(&xdg_shell->popup_grabs); + + struct wl_global *global = wl_global_create(display, + &xdg_wm_base_interface, version, xdg_shell, xdg_shell_bind); + if (!global) { + free(xdg_shell); + return NULL; + } + xdg_shell->global = global; + + wl_signal_init(&xdg_shell->events.new_surface); + wl_signal_init(&xdg_shell->events.new_toplevel); + wl_signal_init(&xdg_shell->events.new_popup); + wl_signal_init(&xdg_shell->events.destroy); + + xdg_shell->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &xdg_shell->display_destroy); + + return xdg_shell; +} diff --git a/types/xdg_shell/wlr_xdg_surface.c b/types/xdg_shell/wlr_xdg_surface.c new file mode 100644 index 0000000..029ab81 --- /dev/null +++ b/types/xdg_shell/wlr_xdg_surface.c @@ -0,0 +1,625 @@ +#include +#include +#include +#include +#include +#include "types/wlr_xdg_shell.h" + +static void xdg_surface_configure_destroy( + struct wlr_xdg_surface_configure *configure) { + if (configure == NULL) { + return; + } + wl_list_remove(&configure->link); + free(configure->toplevel_configure); + free(configure); +} + +// An xdg_surface implementation is reset, when: +// 1) a surface is unmapped due to a commit with NULL buffer, or +// 2) the xdg_surface role object is destroyed, or +// 3) wlr_xdg_surface is destroyed +// An xdg_surface role object implementation is reset, when: +// 1) a surface is unmapped due to a commit with NULL buffer, or +// 2) the xdg_surface role object implementation is destroyed + +static void reset_xdg_surface(struct wlr_xdg_surface *surface) { + surface->configured = false; + surface->initialized = false; + + struct wlr_xdg_popup *popup, *popup_tmp; + wl_list_for_each_safe(popup, popup_tmp, &surface->popups, link) { + wlr_xdg_popup_destroy(popup); + } + + struct wlr_xdg_surface_configure *configure, *tmp; + wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) { + xdg_surface_configure_destroy(configure); + } + + if (surface->configure_idle) { + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + } +} + +static void reset_xdg_surface_role_object(struct wlr_xdg_surface *surface) { + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + if (surface->toplevel != NULL) { + reset_xdg_toplevel(surface->toplevel); + } + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + if (surface->popup != NULL) { + reset_xdg_popup(surface->popup); + } + break; + case WLR_XDG_SURFACE_ROLE_NONE: + break; + } +} + +static void xdg_surface_handle_ack_configure(struct wl_client *client, + struct wl_resource *resource, uint32_t serial) { + struct wlr_xdg_surface *surface = wlr_xdg_surface_from_resource(resource); + if (surface == NULL) { + return; + } + + if (surface->role == WLR_XDG_SURFACE_ROLE_NONE) { + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return; + } + + // First find the ack'ed configure + bool found = false; + struct wlr_xdg_surface_configure *configure, *tmp; + wl_list_for_each(configure, &surface->configure_list, link) { + if (configure->serial == serial) { + found = true; + break; + } + } + if (!found) { + wl_resource_post_error(surface->client->resource, + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, + "wrong configure serial: %u", serial); + return; + } + // Then remove old configures from the list + wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) { + if (configure->serial == serial) { + break; + } + wl_signal_emit_mutable(&surface->events.ack_configure, configure); + xdg_surface_configure_destroy(configure); + } + + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + if (surface->toplevel != NULL) { + handle_xdg_toplevel_ack_configure(surface->toplevel, + configure->toplevel_configure); + } + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + if (surface->popup != NULL) { + handle_xdg_popup_ack_configure(surface->popup, + configure->popup_configure); + } + break; + } + + surface->configured = true; + surface->pending.configure_serial = serial; + + wl_signal_emit_mutable(&surface->events.ack_configure, configure); + xdg_surface_configure_destroy(configure); +} + +static void surface_send_configure(void *user_data) { + struct wlr_xdg_surface *surface = user_data; + + surface->configure_idle = NULL; + + struct wlr_xdg_surface_configure *configure = calloc(1, sizeof(*configure)); + if (configure == NULL) { + wl_client_post_no_memory(surface->client->client); + return; + } + + wl_list_insert(surface->configure_list.prev, &configure->link); + configure->serial = surface->scheduled_serial; + configure->surface = surface; + + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + if (surface->toplevel != NULL) { + configure->toplevel_configure = + send_xdg_toplevel_configure(surface->toplevel); + } + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + if (surface->popup != NULL) { + configure->popup_configure = + send_xdg_popup_configure(surface->popup); + } + break; + } + + wl_signal_emit_mutable(&surface->events.configure, configure); + + xdg_surface_send_configure(surface->resource, configure->serial); +} + +uint32_t wlr_xdg_surface_schedule_configure(struct wlr_xdg_surface *surface) { + struct wl_display *display = wl_client_get_display(surface->client->client); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + + if (!surface->initialized) { + wlr_log(WLR_ERROR, "A configure is scheduled for an uninitialized xdg_surface %p", + surface); + } + + if (surface->configure_idle == NULL) { + surface->scheduled_serial = wl_display_next_serial(display); + surface->configure_idle = wl_event_loop_add_idle(loop, + surface_send_configure, surface); + if (surface->configure_idle == NULL) { + wl_client_post_no_memory(surface->client->client); + } + } + return surface->scheduled_serial; +} + +static void xdg_surface_handle_get_popup(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *parent_resource, + struct wl_resource *positioner_resource) { + struct wlr_xdg_surface *xdg_surface = + wlr_xdg_surface_from_resource(resource); + assert(xdg_surface != NULL); + struct wlr_xdg_surface *parent = NULL; + if (parent_resource != NULL) { + parent = wlr_xdg_surface_from_resource(parent_resource); + } + struct wlr_xdg_positioner *positioner = + wlr_xdg_positioner_from_resource(positioner_resource); + create_xdg_popup(xdg_surface, parent, positioner, id); +} + +static void xdg_surface_handle_get_toplevel(struct wl_client *client, + struct wl_resource *resource, uint32_t id) { + struct wlr_xdg_surface *xdg_surface = + wlr_xdg_surface_from_resource(resource); + assert(xdg_surface != NULL); + create_xdg_toplevel(xdg_surface, id); +} + +static void xdg_surface_handle_set_window_geometry(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y, int32_t width, + int32_t height) { + struct wlr_xdg_surface *surface = wlr_xdg_surface_from_resource(resource); + assert(surface != NULL); + + if (surface->role == WLR_XDG_SURFACE_ROLE_NONE) { + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return; + } + + if (width <= 0 || height <= 0) { + wl_resource_post_error(resource, + XDG_SURFACE_ERROR_INVALID_SIZE, + "Tried to set invalid xdg-surface geometry"); + return; + } + + surface->pending.geometry.x = x; + surface->pending.geometry.y = y; + surface->pending.geometry.width = width; + surface->pending.geometry.height = height; +} + +static void xdg_surface_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface *surface = wlr_xdg_surface_from_resource(resource); + if (surface == NULL) { + return; + } + + if (surface->role_resource != NULL) { + wl_resource_post_error(resource, + XDG_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT, + "surface was destroyed before its role object"); + return; + } + + wl_resource_destroy(resource); +} + +static const struct xdg_surface_interface xdg_surface_implementation = { + .destroy = xdg_surface_handle_destroy, + .get_toplevel = xdg_surface_handle_get_toplevel, + .get_popup = xdg_surface_handle_get_popup, + .ack_configure = xdg_surface_handle_ack_configure, + .set_window_geometry = xdg_surface_handle_set_window_geometry, +}; + +static void xdg_surface_role_client_commit(struct wlr_surface *wlr_surface) { + struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(wlr_surface); + assert(surface != NULL); + + if (wlr_surface_state_has_buffer(&wlr_surface->pending) && !surface->configured) { + wlr_surface_reject_pending(wlr_surface, surface->resource, + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER, "xdg_surface has never been configured"); + return; + } + + if (surface->role_resource == NULL) { + wlr_surface_reject_pending(wlr_surface, surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, "xdg_surface must have a role object"); + return; + } + + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + return; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + if (surface->toplevel != NULL) { + handle_xdg_toplevel_client_commit(surface->toplevel); + } + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + if (surface->popup != NULL) { + handle_xdg_popup_client_commit(surface->popup); + } + break; + } +} + +static void xdg_surface_role_commit(struct wlr_surface *wlr_surface) { + struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(wlr_surface); + assert(surface != NULL); + + if (surface->surface->unmap_commit) { + reset_xdg_surface_role_object(surface); + reset_xdg_surface(surface); + + assert(!surface->initial_commit); + surface->initial_commit = false; + } else { + surface->initial_commit = !surface->initialized; + surface->initialized = true; + } + + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + return; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + if (surface->toplevel == NULL) { + return; + } + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + if (surface->popup == NULL) { + return; + } + break; + } + + if (wlr_surface_has_buffer(wlr_surface)) { + wlr_surface_map(wlr_surface); + } +} + +static void xdg_surface_role_destroy(struct wlr_surface *wlr_surface) { + struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(wlr_surface); + if (surface == NULL) { + // This is the only time xdg_surface can be inert + return; + } + + destroy_xdg_surface(surface); +} + +static const struct wlr_surface_role xdg_surface_role = { + .name = "xdg_surface", + .client_commit = xdg_surface_role_client_commit, + .commit = xdg_surface_role_commit, + .destroy = xdg_surface_role_destroy, +}; + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_xdg_surface_state), +}; + +struct wlr_xdg_surface *wlr_xdg_surface_try_from_wlr_surface( + struct wlr_surface *surface) { + if (surface->role != &xdg_surface_role || surface->role_resource == NULL) { + return NULL; + } + return wlr_xdg_surface_from_resource(surface->role_resource); +} + +void create_xdg_surface(struct wlr_xdg_client *client, struct wlr_surface *wlr_surface, + uint32_t id) { + if (!wlr_surface_set_role(wlr_surface, &xdg_surface_role, client->resource, + XDG_WM_BASE_ERROR_ROLE)) { + return; + } + + if (wlr_surface_has_buffer(wlr_surface)) { + wl_resource_post_error(client->resource, + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER, + "xdg_surface must not have a buffer at creation"); + return; + } + + struct wlr_xdg_surface *surface = calloc(1, sizeof(*surface)); + if (surface == NULL) { + wl_client_post_no_memory(client->client); + return; + } + + if (!wlr_surface_synced_init(&surface->synced, wlr_surface, + &surface_synced_impl, &surface->pending, &surface->current)) { + goto error_surface; + } + + surface->client = client; + surface->role = WLR_XDG_SURFACE_ROLE_NONE; + surface->surface = wlr_surface; + surface->resource = wl_resource_create(client->client, + &xdg_surface_interface, wl_resource_get_version(client->resource), + id); + if (surface->resource == NULL) { + goto error_synced; + } + + wl_list_init(&surface->configure_list); + wl_list_init(&surface->popups); + + wl_signal_init(&surface->events.destroy); + wl_signal_init(&surface->events.ping_timeout); + wl_signal_init(&surface->events.new_popup); + wl_signal_init(&surface->events.configure); + wl_signal_init(&surface->events.ack_configure); + + wlr_log(WLR_DEBUG, "new xdg_surface %p (res %p)", surface, + surface->resource); + wl_resource_set_implementation(surface->resource, + &xdg_surface_implementation, surface, NULL); + wl_list_insert(&client->surfaces, &surface->link); + + wlr_surface_set_role_object(wlr_surface, surface->resource); + + wl_signal_emit_mutable(&surface->client->shell->events.new_surface, surface); + + return; + +error_synced: + wlr_surface_synced_finish(&surface->synced); +error_surface: + free(surface); + wl_client_post_no_memory(client->client); +} + +bool set_xdg_surface_role(struct wlr_xdg_surface *surface, enum wlr_xdg_surface_role role) { + assert(role != WLR_XDG_SURFACE_ROLE_NONE); + + static const char *role_names[] = { + [WLR_XDG_SURFACE_ROLE_TOPLEVEL] = "xdg_toplevel", + [WLR_XDG_SURFACE_ROLE_POPUP] = "xdg_popup", + }; + + if (surface->role != WLR_XDG_SURFACE_ROLE_NONE && surface->role != role) { + wl_resource_post_error(surface->client->resource, XDG_WM_BASE_ERROR_ROLE, + "Cannot assign role %s to xdg_surface@%" PRIu32 ", already has role %s", + role_names[role], wl_resource_get_id(surface->resource), + role_names[surface->role]); + return false; + } + if (surface->role_resource != NULL) { + wl_resource_post_error(surface->client->resource, XDG_WM_BASE_ERROR_ROLE, + "Cannot reassign role %s to xdg_surface@%" PRIu32 ", role object still exists", + role_names[role], wl_resource_get_id(surface->resource)); + return false; + } + + surface->role = role; + return true; +} + +static void destroy_xdg_surface_role_object(struct wlr_xdg_surface *surface) { + if (surface->role_resource == NULL) { + return; + } + + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + if (surface->toplevel != NULL) { + destroy_xdg_toplevel(surface->toplevel); + } + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + if (surface->popup != NULL) { + destroy_xdg_popup(surface->popup); + } + break; + } + + surface->role_resource = NULL; + wl_list_remove(&surface->role_resource_destroy.link); + wl_list_init(&surface->role_resource_destroy.link); +} + +static void xdg_surface_handle_role_resource_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_surface *surface = wl_container_of(listener, surface, role_resource_destroy); + destroy_xdg_surface_role_object(surface); + reset_xdg_surface(surface); +} + +void set_xdg_surface_role_object(struct wlr_xdg_surface *surface, + struct wl_resource *role_resource) { + assert(surface->role != WLR_XDG_SURFACE_ROLE_NONE); + assert(surface->role_resource == NULL); + assert(role_resource != NULL); + surface->role_resource = role_resource; + surface->role_resource_destroy.notify = xdg_surface_handle_role_resource_destroy; + wl_resource_add_destroy_listener(role_resource, &surface->role_resource_destroy); +} + +void destroy_xdg_surface(struct wlr_xdg_surface *surface) { + destroy_xdg_surface_role_object(surface); + reset_xdg_surface(surface); + + wl_signal_emit_mutable(&surface->events.destroy, NULL); + + wl_list_remove(&surface->link); + wlr_surface_synced_finish(&surface->synced); + wl_resource_set_user_data(surface->resource, NULL); + free(surface); +} + +struct wlr_xdg_surface *wlr_xdg_surface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_surface_interface, + &xdg_surface_implementation)); + return wl_resource_get_user_data(resource); +} + +void wlr_xdg_surface_ping(struct wlr_xdg_surface *surface) { + if (surface->client->ping_serial != 0) { + // already pinged + return; + } + + surface->client->ping_serial = + wl_display_next_serial(wl_client_get_display(surface->client->client)); + wl_event_source_timer_update(surface->client->ping_timer, + surface->client->shell->ping_timeout); + xdg_wm_base_send_ping(surface->client->resource, + surface->client->ping_serial); +} + +void wlr_xdg_popup_get_position(struct wlr_xdg_popup *popup, + double *popup_sx, double *popup_sy) { + struct wlr_xdg_surface *parent = wlr_xdg_surface_try_from_wlr_surface(popup->parent); + assert(parent != NULL); + struct wlr_box parent_geo; + wlr_xdg_surface_get_geometry(parent, &parent_geo); + *popup_sx = parent_geo.x + popup->current.geometry.x - + popup->base->current.geometry.x; + *popup_sy = parent_geo.y + popup->current.geometry.y - + popup->base->current.geometry.y; +} + +struct wlr_surface *wlr_xdg_surface_surface_at( + struct wlr_xdg_surface *surface, double sx, double sy, + double *sub_x, double *sub_y) { + struct wlr_surface *sub = wlr_xdg_surface_popup_surface_at(surface, sx, sy, + sub_x, sub_y); + if (sub != NULL) { + return sub; + } + return wlr_surface_surface_at(surface->surface, sx, sy, sub_x, sub_y); +} + +struct wlr_surface *wlr_xdg_surface_popup_surface_at( + struct wlr_xdg_surface *surface, double sx, double sy, + double *sub_x, double *sub_y) { + struct wlr_xdg_popup *popup; + wl_list_for_each(popup, &surface->popups, link) { + if (!popup->base->surface->mapped) { + continue; + } + + double popup_sx, popup_sy; + wlr_xdg_popup_get_position(popup, &popup_sx, &popup_sy); + + struct wlr_surface *sub = wlr_xdg_surface_surface_at( + popup->base, sx - popup_sx, sy - popup_sy, + sub_x, sub_y); + if (sub != NULL) { + return sub; + } + } + + return NULL; +} + +struct xdg_surface_iterator_data { + wlr_surface_iterator_func_t user_iterator; + void *user_data; + int x, y; +}; + +static void xdg_surface_iterator(struct wlr_surface *surface, + int sx, int sy, void *data) { + struct xdg_surface_iterator_data *iter_data = data; + iter_data->user_iterator(surface, iter_data->x + sx, iter_data->y + sy, + iter_data->user_data); +} + +static void xdg_surface_for_each_popup_surface(struct wlr_xdg_surface *surface, + int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) { + struct wlr_xdg_popup *popup; + wl_list_for_each(popup, &surface->popups, link) { + if (!popup->base->surface->mapped) { + continue; + } + + double popup_sx, popup_sy; + wlr_xdg_popup_get_position(popup, &popup_sx, &popup_sy); + + struct xdg_surface_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .x = x + popup_sx, .y = y + popup_sy, + }; + wlr_surface_for_each_surface(popup->base->surface, + xdg_surface_iterator, &data); + + xdg_surface_for_each_popup_surface(popup->base, + x + popup_sx, y + popup_sy, iterator, user_data); + } +} + +void wlr_xdg_surface_for_each_surface(struct wlr_xdg_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data) { + wlr_surface_for_each_surface(surface->surface, iterator, user_data); + xdg_surface_for_each_popup_surface(surface, 0, 0, iterator, user_data); +} + +void wlr_xdg_surface_for_each_popup_surface(struct wlr_xdg_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data) { + xdg_surface_for_each_popup_surface(surface, 0, 0, iterator, user_data); +} + +void wlr_xdg_surface_get_geometry(struct wlr_xdg_surface *surface, + struct wlr_box *box) { + wlr_surface_get_extends(surface->surface, box); + + /* The client never set the geometry */ + if (wlr_box_empty(&surface->current.geometry)) { + return; + } + + wlr_box_intersection(box, &surface->current.geometry, box); +} diff --git a/types/xdg_shell/wlr_xdg_toplevel.c b/types/xdg_shell/wlr_xdg_toplevel.c new file mode 100644 index 0000000..488e454 --- /dev/null +++ b/types/xdg_shell/wlr_xdg_toplevel.c @@ -0,0 +1,613 @@ +#include +#include +#include +#include +#include +#include "types/wlr_xdg_shell.h" +#include "util/utf8.h" + +void handle_xdg_toplevel_ack_configure( + struct wlr_xdg_toplevel *toplevel, + struct wlr_xdg_toplevel_configure *configure) { + toplevel->pending.maximized = configure->maximized; + toplevel->pending.fullscreen = configure->fullscreen; + toplevel->pending.resizing = configure->resizing; + toplevel->pending.activated = configure->activated; + toplevel->pending.tiled = configure->tiled; + toplevel->pending.suspended = configure->suspended; + + toplevel->pending.width = configure->width; + toplevel->pending.height = configure->height; +} + +struct wlr_xdg_toplevel_configure *send_xdg_toplevel_configure( + struct wlr_xdg_toplevel *toplevel) { + struct wlr_xdg_toplevel_configure *configure = + calloc(1, sizeof(*configure)); + if (configure == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + wl_resource_post_no_memory(toplevel->resource); + return NULL; + } + *configure = toplevel->scheduled; + + uint32_t version = wl_resource_get_version(toplevel->resource); + + if ((configure->fields & WLR_XDG_TOPLEVEL_CONFIGURE_BOUNDS) && + version >= XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION) { + xdg_toplevel_send_configure_bounds(toplevel->resource, + configure->bounds.width, configure->bounds.height); + } + + if ((configure->fields & WLR_XDG_TOPLEVEL_CONFIGURE_WM_CAPABILITIES) && + version >= XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) { + size_t caps_len = 0; + uint32_t caps[32]; + if (configure->wm_capabilities & WLR_XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU) { + caps[caps_len++] = XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU; + } + if (configure->wm_capabilities & WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE) { + caps[caps_len++] = XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE; + } + if (configure->wm_capabilities & WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN) { + caps[caps_len++] = XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN; + } + if (configure->wm_capabilities & WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE) { + caps[caps_len++] = XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE; + } + assert(caps_len <= sizeof(caps) / sizeof(caps[0])); + + struct wl_array caps_array = { + .size = caps_len * sizeof(caps[0]), + .data = caps, + }; + xdg_toplevel_send_wm_capabilities(toplevel->resource, &caps_array); + } + + size_t nstates = 0; + uint32_t states[32]; + if (configure->maximized) { + states[nstates++] = XDG_TOPLEVEL_STATE_MAXIMIZED; + } + if (configure->fullscreen) { + states[nstates++] = XDG_TOPLEVEL_STATE_FULLSCREEN; + } + if (configure->resizing) { + states[nstates++] = XDG_TOPLEVEL_STATE_RESIZING; + } + if (configure->activated) { + states[nstates++] = XDG_TOPLEVEL_STATE_ACTIVATED; + } + if (configure->tiled && version >= XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION) { + const struct { + enum wlr_edges edge; + enum xdg_toplevel_state state; + } tiled[] = { + { WLR_EDGE_LEFT, XDG_TOPLEVEL_STATE_TILED_LEFT }, + { WLR_EDGE_RIGHT, XDG_TOPLEVEL_STATE_TILED_RIGHT }, + { WLR_EDGE_TOP, XDG_TOPLEVEL_STATE_TILED_TOP }, + { WLR_EDGE_BOTTOM, XDG_TOPLEVEL_STATE_TILED_BOTTOM }, + }; + + for (size_t i = 0; i < sizeof(tiled)/sizeof(tiled[0]); ++i) { + if ((configure->tiled & tiled[i].edge) == 0) { + continue; + } + states[nstates++] = tiled[i].state; + } + } + if (configure->suspended && version >= XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION) { + states[nstates++] = XDG_TOPLEVEL_STATE_SUSPENDED; + } + assert(nstates <= sizeof(states) / sizeof(states[0])); + + int32_t width = configure->width; + int32_t height = configure->height; + struct wl_array wl_states = { + .size = nstates * sizeof(states[0]), + .data = states, + }; + xdg_toplevel_send_configure(toplevel->resource, + width, height, &wl_states); + + toplevel->scheduled.fields = 0; + + return configure; +} + +void handle_xdg_toplevel_client_commit(struct wlr_xdg_toplevel *toplevel) { + struct wlr_xdg_toplevel_state *pending = &toplevel->pending; + + // 1) Negative values are prohibited + // 2) If both min and max are set (aren't 0), min ≤ max + if (pending->min_width < 0 || pending->min_height < 0 || + pending->max_width < 0 || pending->max_height < 0 || + (pending->max_width != 0 && pending->max_width < pending->min_width) || + (pending->max_height != 0 && pending->max_height < pending->min_height)) { + wlr_surface_reject_pending(toplevel->base->surface, toplevel->resource, + XDG_TOPLEVEL_ERROR_INVALID_SIZE, "client provided an invalid min or max size"); + return; + } +} + +static const struct xdg_toplevel_interface xdg_toplevel_implementation; + +struct wlr_xdg_toplevel *wlr_xdg_toplevel_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_toplevel_interface, + &xdg_toplevel_implementation)); + return wl_resource_get_user_data(resource); +} + +struct wlr_xdg_toplevel *wlr_xdg_toplevel_try_from_wlr_surface(struct wlr_surface *surface) { + struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface); + if (xdg_surface == NULL || xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + return NULL; + } + return xdg_surface->toplevel; +} + +static void handle_parent_unmap(struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel *toplevel = + wl_container_of(listener, toplevel, parent_unmap); + if (!wlr_xdg_toplevel_set_parent(toplevel, toplevel->parent->parent)) { + assert(0 && "Unreachable"); + } +} + +bool wlr_xdg_toplevel_set_parent(struct wlr_xdg_toplevel *toplevel, + struct wlr_xdg_toplevel *parent) { + // Check for a loop + struct wlr_xdg_toplevel *iter = parent; + while (iter != NULL) { + if (iter == toplevel) { + return false; + } + iter = iter->parent; + } + + if (toplevel->parent != NULL) { + wl_list_remove(&toplevel->parent_unmap.link); + } + + if (parent != NULL && parent->base->surface->mapped) { + toplevel->parent = parent; + toplevel->parent_unmap.notify = handle_parent_unmap; + wl_signal_add(&toplevel->parent->base->surface->events.unmap, + &toplevel->parent_unmap); + } else { + toplevel->parent = NULL; + } + + wl_signal_emit_mutable(&toplevel->events.set_parent, NULL); + return true; +} + +static void xdg_toplevel_handle_set_parent(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *parent_resource) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + struct wlr_xdg_toplevel *parent = NULL; + + if (parent_resource != NULL) { + parent = wlr_xdg_toplevel_from_resource(parent_resource); + } + + if (!wlr_xdg_toplevel_set_parent(toplevel, parent)) { + wl_resource_post_error(resource, XDG_TOPLEVEL_ERROR_INVALID_PARENT, + "a toplevel cannot be a parent of itself or its ancestor"); + } +} + +static void xdg_toplevel_handle_set_title(struct wl_client *client, + struct wl_resource *resource, const char *title) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + char *tmp; + + if (!is_utf8(title)) { + // TODO: update when xdg_toplevel has a dedicated error code for this + wl_resource_post_error(resource, (uint32_t)-1, "xdg_toplevel title is not valid UTF-8"); + return; + } + + tmp = strdup(title); + if (tmp == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + free(toplevel->title); + toplevel->title = tmp; + wl_signal_emit_mutable(&toplevel->events.set_title, NULL); +} + +static void xdg_toplevel_handle_set_app_id(struct wl_client *client, + struct wl_resource *resource, const char *app_id) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + char *tmp; + + tmp = strdup(app_id); + if (tmp == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + free(toplevel->app_id); + toplevel->app_id = tmp; + wl_signal_emit_mutable(&toplevel->events.set_app_id, NULL); +} + +static void xdg_toplevel_handle_show_window_menu(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial, int32_t x, int32_t y) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + struct wlr_seat_client *seat = + wlr_seat_client_from_resource(seat_resource); + + if (!toplevel->base->configured) { + wl_resource_post_error(toplevel->base->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "surface has not been configured yet"); + return; + } + + struct wlr_xdg_toplevel_show_window_menu_event event = { + .toplevel = toplevel, + .seat = seat, + .serial = serial, + .x = x, + .y = y, + }; + + wl_signal_emit_mutable(&toplevel->events.request_show_window_menu, &event); +} + +static void xdg_toplevel_handle_move(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + struct wlr_seat_client *seat = + wlr_seat_client_from_resource(seat_resource); + + if (!toplevel->base->configured) { + wl_resource_post_error(toplevel->base->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "surface has not been configured yet"); + return; + } + + struct wlr_xdg_toplevel_move_event event = { + .toplevel = toplevel, + .seat = seat, + .serial = serial, + }; + + wl_signal_emit_mutable(&toplevel->events.request_move, &event); +} + +static void xdg_toplevel_handle_resize(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial, uint32_t edges) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + struct wlr_seat_client *seat = + wlr_seat_client_from_resource(seat_resource); + + switch (edges) { + case XDG_TOPLEVEL_RESIZE_EDGE_TOP: + case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM: + case XDG_TOPLEVEL_RESIZE_EDGE_LEFT: + case XDG_TOPLEVEL_RESIZE_EDGE_RIGHT: + case XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT: + case XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT: + case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT: + case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT: + break; + default: + wl_resource_post_error(toplevel->base->resource, + XDG_TOPLEVEL_ERROR_INVALID_RESIZE_EDGE, + "provided value is not a valid variant of the resize_edge enum"); + return; + } + + if (!toplevel->base->configured) { + wl_resource_post_error(toplevel->base->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "surface has not been configured yet"); + return; + } + + struct wlr_xdg_toplevel_resize_event event = { + .toplevel = toplevel, + .seat = seat, + .serial = serial, + .edges = edges, + }; + + wl_signal_emit_mutable(&toplevel->events.request_resize, &event); +} + +static void xdg_toplevel_handle_set_max_size(struct wl_client *client, + struct wl_resource *resource, int32_t width, int32_t height) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + toplevel->pending.max_width = width; + toplevel->pending.max_height = height; +} + +static void xdg_toplevel_handle_set_min_size(struct wl_client *client, + struct wl_resource *resource, int32_t width, int32_t height) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + toplevel->pending.min_width = width; + toplevel->pending.min_height = height; +} + +static void xdg_toplevel_handle_set_maximized(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + toplevel->requested.maximized = true; + wl_signal_emit_mutable(&toplevel->events.request_maximize, NULL); +} + +static void xdg_toplevel_handle_unset_maximized(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + toplevel->requested.maximized = false; + wl_signal_emit_mutable(&toplevel->events.request_maximize, NULL); +} + +static void handle_fullscreen_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xdg_toplevel_requested *req = + wl_container_of(listener, req, fullscreen_output_destroy); + req->fullscreen_output = NULL; + wl_list_remove(&req->fullscreen_output_destroy.link); +} + +static void store_fullscreen_requested(struct wlr_xdg_toplevel *toplevel, + bool fullscreen, struct wlr_output *output) { + struct wlr_xdg_toplevel_requested *req = &toplevel->requested; + req->fullscreen = fullscreen; + if (req->fullscreen_output) { + wl_list_remove(&req->fullscreen_output_destroy.link); + } + req->fullscreen_output = output; + if (req->fullscreen_output) { + req->fullscreen_output_destroy.notify = + handle_fullscreen_output_destroy; + wl_signal_add(&req->fullscreen_output->events.destroy, + &req->fullscreen_output_destroy); + } +} + +static void xdg_toplevel_handle_set_fullscreen(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *output_resource) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + + struct wlr_output *output = NULL; + if (output_resource != NULL) { + output = wlr_output_from_resource(output_resource); + } + + store_fullscreen_requested(toplevel, true, output); + + wl_signal_emit_mutable(&toplevel->events.request_fullscreen, NULL); +} + +static void xdg_toplevel_handle_unset_fullscreen(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + + store_fullscreen_requested(toplevel, false, NULL); + + wl_signal_emit_mutable(&toplevel->events.request_fullscreen, NULL); +} + +static void xdg_toplevel_handle_set_minimized(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_toplevel *toplevel = + wlr_xdg_toplevel_from_resource(resource); + toplevel->requested.minimized = true; + wl_signal_emit_mutable(&toplevel->events.request_minimize, NULL); +} + +static void xdg_toplevel_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct xdg_toplevel_interface xdg_toplevel_implementation = { + .destroy = xdg_toplevel_handle_destroy, + .set_parent = xdg_toplevel_handle_set_parent, + .set_title = xdg_toplevel_handle_set_title, + .set_app_id = xdg_toplevel_handle_set_app_id, + .show_window_menu = xdg_toplevel_handle_show_window_menu, + .move = xdg_toplevel_handle_move, + .resize = xdg_toplevel_handle_resize, + .set_max_size = xdg_toplevel_handle_set_max_size, + .set_min_size = xdg_toplevel_handle_set_min_size, + .set_maximized = xdg_toplevel_handle_set_maximized, + .unset_maximized = xdg_toplevel_handle_unset_maximized, + .set_fullscreen = xdg_toplevel_handle_set_fullscreen, + .unset_fullscreen = xdg_toplevel_handle_unset_fullscreen, + .set_minimized = xdg_toplevel_handle_set_minimized, +}; + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_xdg_toplevel_state), +}; + +void create_xdg_toplevel(struct wlr_xdg_surface *surface, + uint32_t id) { + if (!set_xdg_surface_role(surface, WLR_XDG_SURFACE_ROLE_TOPLEVEL)) { + return; + } + + assert(surface->toplevel == NULL); + surface->toplevel = calloc(1, sizeof(*surface->toplevel)); + if (surface->toplevel == NULL) { + wl_resource_post_no_memory(surface->resource); + return; + } + surface->toplevel->base = surface; + + wl_signal_init(&surface->toplevel->events.destroy); + wl_signal_init(&surface->toplevel->events.request_maximize); + wl_signal_init(&surface->toplevel->events.request_fullscreen); + wl_signal_init(&surface->toplevel->events.request_minimize); + wl_signal_init(&surface->toplevel->events.request_move); + wl_signal_init(&surface->toplevel->events.request_resize); + wl_signal_init(&surface->toplevel->events.request_show_window_menu); + wl_signal_init(&surface->toplevel->events.set_parent); + wl_signal_init(&surface->toplevel->events.set_title); + wl_signal_init(&surface->toplevel->events.set_app_id); + + if (!wlr_surface_synced_init(&surface->toplevel->synced, surface->surface, + &surface_synced_impl, &surface->toplevel->pending, &surface->toplevel->current)) { + goto error_toplevel; + } + + surface->toplevel->resource = wl_resource_create( + surface->client->client, &xdg_toplevel_interface, + wl_resource_get_version(surface->resource), id); + if (surface->toplevel->resource == NULL) { + goto error_synced; + } + wl_resource_set_implementation(surface->toplevel->resource, + &xdg_toplevel_implementation, surface->toplevel, NULL); + + set_xdg_surface_role_object(surface, surface->toplevel->resource); + + if (surface->client->shell->version >= XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) { + // The first configure event must carry WM capabilities + surface->toplevel->scheduled.wm_capabilities = + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU | + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE | + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN | + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE; + surface->toplevel->scheduled.fields |= WLR_XDG_TOPLEVEL_CONFIGURE_WM_CAPABILITIES; + } + + wl_signal_emit_mutable(&surface->client->shell->events.new_toplevel, surface->toplevel); + + return; + +error_synced: + wlr_surface_synced_finish(&surface->toplevel->synced); +error_toplevel: + free(surface->toplevel); + surface->toplevel = NULL; + wl_resource_post_no_memory(surface->resource); +} + +void reset_xdg_toplevel(struct wlr_xdg_toplevel *toplevel) { + if (toplevel->parent) { + wl_list_remove(&toplevel->parent_unmap.link); + toplevel->parent = NULL; + } + free(toplevel->title); + toplevel->title = NULL; + free(toplevel->app_id); + toplevel->app_id = NULL; + + if (toplevel->requested.fullscreen_output) { + wl_list_remove(&toplevel->requested.fullscreen_output_destroy.link); + toplevel->requested.fullscreen_output = NULL; + } + toplevel->requested.fullscreen = false; + toplevel->requested.maximized = false; + toplevel->requested.minimized = false; +} + +void destroy_xdg_toplevel(struct wlr_xdg_toplevel *toplevel) { + wlr_surface_unmap(toplevel->base->surface); + reset_xdg_toplevel(toplevel); + + wl_signal_emit_mutable(&toplevel->events.destroy, NULL); + + wlr_surface_synced_finish(&toplevel->synced); + toplevel->base->toplevel = NULL; + wl_resource_set_user_data(toplevel->resource, NULL); + free(toplevel); +} + +void wlr_xdg_toplevel_send_close(struct wlr_xdg_toplevel *toplevel) { + xdg_toplevel_send_close(toplevel->resource); +} + +uint32_t wlr_xdg_toplevel_set_size(struct wlr_xdg_toplevel *toplevel, + int32_t width, int32_t height) { + assert(width >= 0 && height >= 0); + toplevel->scheduled.width = width; + toplevel->scheduled.height = height; + return wlr_xdg_surface_schedule_configure(toplevel->base); +} + +uint32_t wlr_xdg_toplevel_set_activated(struct wlr_xdg_toplevel *toplevel, + bool activated) { + toplevel->scheduled.activated = activated; + return wlr_xdg_surface_schedule_configure(toplevel->base); +} + +uint32_t wlr_xdg_toplevel_set_maximized(struct wlr_xdg_toplevel *toplevel, + bool maximized) { + toplevel->scheduled.maximized = maximized; + return wlr_xdg_surface_schedule_configure(toplevel->base); +} + +uint32_t wlr_xdg_toplevel_set_fullscreen(struct wlr_xdg_toplevel *toplevel, + bool fullscreen) { + toplevel->scheduled.fullscreen = fullscreen; + return wlr_xdg_surface_schedule_configure(toplevel->base); +} + +uint32_t wlr_xdg_toplevel_set_resizing(struct wlr_xdg_toplevel *toplevel, + bool resizing) { + toplevel->scheduled.resizing = resizing; + return wlr_xdg_surface_schedule_configure(toplevel->base); +} + +uint32_t wlr_xdg_toplevel_set_tiled(struct wlr_xdg_toplevel *toplevel, + uint32_t tiled) { + assert(toplevel->base->client->shell->version >= + XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION); + toplevel->scheduled.tiled = tiled; + return wlr_xdg_surface_schedule_configure(toplevel->base); +} + +uint32_t wlr_xdg_toplevel_set_bounds(struct wlr_xdg_toplevel *toplevel, + int32_t width, int32_t height) { + assert(toplevel->base->client->shell->version >= + XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION); + assert(width >= 0 && height >= 0); + toplevel->scheduled.fields |= WLR_XDG_TOPLEVEL_CONFIGURE_BOUNDS; + toplevel->scheduled.bounds.width = width; + toplevel->scheduled.bounds.height = height; + return wlr_xdg_surface_schedule_configure(toplevel->base); +} + +uint32_t wlr_xdg_toplevel_set_wm_capabilities(struct wlr_xdg_toplevel *toplevel, + uint32_t caps) { + assert(toplevel->base->client->shell->version >= + XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION); + toplevel->scheduled.fields |= WLR_XDG_TOPLEVEL_CONFIGURE_WM_CAPABILITIES; + toplevel->scheduled.wm_capabilities = caps; + return wlr_xdg_surface_schedule_configure(toplevel->base); +} + +uint32_t wlr_xdg_toplevel_set_suspended(struct wlr_xdg_toplevel *toplevel, + bool suspended) { + assert(toplevel->base->client->shell->version >= + XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION); + toplevel->scheduled.suspended = suspended; + return wlr_xdg_surface_schedule_configure(toplevel->base); +} diff --git a/util/addon.c b/util/addon.c new file mode 100644 index 0000000..63bcb5b --- /dev/null +++ b/util/addon.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include + +void wlr_addon_set_init(struct wlr_addon_set *set) { + *set = (struct wlr_addon_set){0}; + wl_list_init(&set->addons); +} + +void wlr_addon_set_finish(struct wlr_addon_set *set) { + while (!wl_list_empty(&set->addons)) { + struct wl_list *link = set->addons.next; + struct wlr_addon *addon = wl_container_of(link, addon, link); + const struct wlr_addon_interface *impl = addon->impl; + addon->impl->destroy(addon); + if (set->addons.next == link) { + wlr_log(WLR_ERROR, "Dangling addon: %s", impl->name); + abort(); + } + } +} + +void wlr_addon_init(struct wlr_addon *addon, struct wlr_addon_set *set, + const void *owner, const struct wlr_addon_interface *impl) { + assert(impl); + *addon = (struct wlr_addon){ + .impl = impl, + .owner = owner, + }; + struct wlr_addon *iter; + wl_list_for_each(iter, &set->addons, link) { + if (iter->owner == addon->owner && iter->impl == addon->impl) { + assert(0 && "Can't have two addons of the same type with the same owner"); + } + } + wl_list_insert(&set->addons, &addon->link); +} + +void wlr_addon_finish(struct wlr_addon *addon) { + wl_list_remove(&addon->link); +} + +struct wlr_addon *wlr_addon_find(struct wlr_addon_set *set, const void *owner, + const struct wlr_addon_interface *impl) { + struct wlr_addon *addon; + wl_list_for_each(addon, &set->addons, link) { + if (addon->owner == owner && addon->impl == impl) { + return addon; + } + } + return NULL; +} diff --git a/util/array.c b/util/array.c new file mode 100644 index 0000000..ec16a7b --- /dev/null +++ b/util/array.c @@ -0,0 +1,40 @@ +#include "util/array.h" +#include +#include + +void array_remove_at(struct wl_array *arr, size_t offset, size_t size) { + assert(arr->size >= offset + size); + + char *data = arr->data; + memmove(&data[offset], &data[offset + size], arr->size - offset - size); + arr->size -= size; +} + +bool array_realloc(struct wl_array *arr, size_t size) { + // If the size is less than 1/4th of the allocation size, we shrink it. + // 1/4th is picked to provide hysteresis, without which an array with size + // arr->alloc would constantly reallocate if an element is added and then + // removed continously. + size_t alloc; + if (arr->alloc > 0 && size > arr->alloc / 4) { + alloc = arr->alloc; + } else { + alloc = 16; + } + + while (alloc < size) { + alloc *= 2; + } + + if (alloc == arr->alloc) { + return true; + } + + void *data = realloc(arr->data, alloc); + if (data == NULL) { + return false; + } + arr->data = data; + arr->alloc = alloc; + return true; +} diff --git a/util/box.c b/util/box.c new file mode 100644 index 0000000..bc2c60d --- /dev/null +++ b/util/box.c @@ -0,0 +1,212 @@ +#include +#include +#include +#include +#include +#include + +void wlr_box_closest_point(const struct wlr_box *box, double x, double y, + double *dest_x, double *dest_y) { + // if box is empty, then it contains no points, so no closest point either + if (wlr_box_empty(box)) { + *dest_x = NAN; + *dest_y = NAN; + return; + } + + // find the closest x point + if (x < box->x) { + *dest_x = box->x; + } else if (x >= box->x + box->width) { + *dest_x = box->x + box->width - 1; + } else { + *dest_x = x; + } + + // find closest y point + if (y < box->y) { + *dest_y = box->y; + } else if (y >= box->y + box->height) { + *dest_y = box->y + box->height - 1; + } else { + *dest_y = y; + } +} + +bool wlr_box_empty(const struct wlr_box *box) { + return box == NULL || box->width <= 0 || box->height <= 0; +} + +bool wlr_box_intersection(struct wlr_box *dest, const struct wlr_box *box_a, + const struct wlr_box *box_b) { + bool a_empty = wlr_box_empty(box_a); + bool b_empty = wlr_box_empty(box_b); + + if (a_empty || b_empty) { + *dest = (struct wlr_box){0}; + return false; + } + + int x1 = fmax(box_a->x, box_b->x); + int y1 = fmax(box_a->y, box_b->y); + int x2 = fmin(box_a->x + box_a->width, box_b->x + box_b->width); + int y2 = fmin(box_a->y + box_a->height, box_b->y + box_b->height); + + dest->x = x1; + dest->y = y1; + dest->width = x2 - x1; + dest->height = y2 - y1; + + return !wlr_box_empty(dest); +} + +bool wlr_box_contains_point(const struct wlr_box *box, double x, double y) { + if (wlr_box_empty(box)) { + return false; + } else { + return x >= box->x && x < box->x + box->width && + y >= box->y && y < box->y + box->height; + } +} + +void wlr_box_transform(struct wlr_box *dest, const struct wlr_box *box, + enum wl_output_transform transform, int width, int height) { + struct wlr_box src = {0}; + if (box != NULL) { + src = *box; + } + + if (transform % 2 == 0) { + dest->width = src.width; + dest->height = src.height; + } else { + dest->width = src.height; + dest->height = src.width; + } + + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + dest->x = src.x; + dest->y = src.y; + break; + case WL_OUTPUT_TRANSFORM_90: + dest->x = height - src.y - src.height; + dest->y = src.x; + break; + case WL_OUTPUT_TRANSFORM_180: + dest->x = width - src.x - src.width; + dest->y = height - src.y - src.height; + break; + case WL_OUTPUT_TRANSFORM_270: + dest->x = src.y; + dest->y = width - src.x - src.width; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + dest->x = width - src.x - src.width; + dest->y = src.y; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + dest->x = src.y; + dest->y = src.x; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + dest->x = src.x; + dest->y = height - src.y - src.height; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + dest->x = height - src.y - src.height; + dest->y = width - src.x - src.width; + break; + } +} + +bool wlr_fbox_empty(const struct wlr_fbox *box) { + return box == NULL || box->width <= 0 || box->height <= 0; +} + +void wlr_fbox_transform(struct wlr_fbox *dest, const struct wlr_fbox *box, + enum wl_output_transform transform, double width, double height) { + struct wlr_fbox src = {0}; + if (box != NULL) { + src = *box; + } + + if (transform % 2 == 0) { + dest->width = src.width; + dest->height = src.height; + } else { + dest->width = src.height; + dest->height = src.width; + } + + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + dest->x = src.x; + dest->y = src.y; + break; + case WL_OUTPUT_TRANSFORM_90: + dest->x = height - src.y - src.height; + dest->y = src.x; + break; + case WL_OUTPUT_TRANSFORM_180: + dest->x = width - src.x - src.width; + dest->y = height - src.y - src.height; + break; + case WL_OUTPUT_TRANSFORM_270: + dest->x = src.y; + dest->y = width - src.x - src.width; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + dest->x = width - src.x - src.width; + dest->y = src.y; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + dest->x = src.y; + dest->y = src.x; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + dest->x = src.x; + dest->y = height - src.y - src.height; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + dest->x = height - src.y - src.height; + dest->y = width - src.x - src.width; + break; + } +} + +#ifdef WLR_USE_UNSTABLE + +bool wlr_box_equal(const struct wlr_box *a, const struct wlr_box *b) { + if (wlr_box_empty(a)) { + a = NULL; + } + if (wlr_box_empty(b)) { + b = NULL; + } + + if (a == NULL || b == NULL) { + return a == b; + } + + return a->x == b->x && a->y == b->y && + a->width == b->width && a->height == b->height; +} + +bool wlr_fbox_equal(const struct wlr_fbox *a, const struct wlr_fbox *b) { + if (wlr_fbox_empty(a)) { + a = NULL; + } + if (wlr_fbox_empty(b)) { + b = NULL; + } + + if (a == NULL || b == NULL) { + return a == b; + } + + return a->x == b->x && a->y == b->y && + a->width == b->width && a->height == b->height; +} + +#endif diff --git a/util/env.c b/util/env.c new file mode 100644 index 0000000..77911a2 --- /dev/null +++ b/util/env.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include "util/env.h" + +bool env_parse_bool(const char *option) { + const char *env = getenv(option); + if (env) { + wlr_log(WLR_INFO, "Loading %s option: %s", option, env); + } + + if (!env || strcmp(env, "0") == 0) { + return false; + } else if (strcmp(env, "1") == 0) { + return true; + } + + wlr_log(WLR_ERROR, "Unknown %s option: %s", option, env); + return false; +} + +size_t env_parse_switch(const char *option, const char **switches) { + const char *env = getenv(option); + if (env) { + wlr_log(WLR_INFO, "Loading %s option: %s", option, env); + } else { + return 0; + } + + for (ssize_t i = 0; switches[i]; i++) { + if (strcmp(env, switches[i]) == 0) { + return i; + } + } + + wlr_log(WLR_ERROR, "Unknown %s option: %s", option, env); + return 0; +} diff --git a/util/global.c b/util/global.c new file mode 100644 index 0000000..93636a8 --- /dev/null +++ b/util/global.c @@ -0,0 +1,57 @@ +#include +#include "util/global.h" + +struct destroy_global_data { + struct wl_global *global; + struct wl_event_source *event_source; + struct wl_listener display_destroy; +}; + +static void destroy_global(struct destroy_global_data *data) { + wl_list_remove(&data->display_destroy.link); + wl_global_destroy(data->global); + wl_event_source_remove(data->event_source); + free(data); +} + +static int handle_timer_event(void *data) { + destroy_global(data); + return 0; +} + +static void handle_display_destroy(struct wl_listener *listener, void *_data) { + struct destroy_global_data *data = + wl_container_of(listener, data, display_destroy); + destroy_global(data); +} + +void wlr_global_destroy_safe(struct wl_global *global) { + // Don't destroy the global immediately. If the global has been created + // recently, clients might try to bind to it after we've destroyed it. + // Instead, remove the global so that clients stop seeing it and wait an + // arbitrary amount of time before destroying the global as a workaround. + // See: https://gitlab.freedesktop.org/wayland/wayland/issues/10 + + wl_global_remove(global); + wl_global_set_user_data(global, NULL); // safety net + + struct wl_display *display = wl_global_get_display(global); + struct wl_event_loop *event_loop = wl_display_get_event_loop(display); + struct destroy_global_data *data = calloc(1, sizeof(*data)); + if (data == NULL) { + wl_global_destroy(global); + return; + } + data->global = global; + data->event_source = + wl_event_loop_add_timer(event_loop, handle_timer_event, data); + if (data->event_source == NULL) { + free(data); + wl_global_destroy(global); + return; + } + wl_event_source_timer_update(data->event_source, 5000); + + data->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &data->display_destroy); +} diff --git a/util/log.c b/util/log.c new file mode 100644 index 0000000..a329b43 --- /dev/null +++ b/util/log.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "util/time.h" + +static bool colored = true; +static enum wlr_log_importance log_importance = WLR_ERROR; +static struct timespec start_time = {-1}; + +static const char *verbosity_colors[] = { + [WLR_SILENT] = "", + [WLR_ERROR] = "\x1B[1;31m", + [WLR_INFO] = "\x1B[1;34m", + [WLR_DEBUG] = "\x1B[1;90m", +}; + +static const char *verbosity_headers[] = { + [WLR_SILENT] = "", + [WLR_ERROR] = "[ERROR]", + [WLR_INFO] = "[INFO]", + [WLR_DEBUG] = "[DEBUG]", +}; + +static void init_start_time(void) { + if (start_time.tv_sec >= 0) { + return; + } + clock_gettime(CLOCK_MONOTONIC, &start_time); +} + +static void log_stderr(enum wlr_log_importance verbosity, const char *fmt, + va_list args) { + init_start_time(); + + if (verbosity > log_importance) { + return; + } + + struct timespec ts = {0}; + clock_gettime(CLOCK_MONOTONIC, &ts); + timespec_sub(&ts, &ts, &start_time); + + fprintf(stderr, "%02d:%02d:%02d.%03ld ", (int)(ts.tv_sec / 60 / 60), + (int)(ts.tv_sec / 60 % 60), (int)(ts.tv_sec % 60), + ts.tv_nsec / 1000000); + + unsigned c = (verbosity < WLR_LOG_IMPORTANCE_LAST) ? verbosity : WLR_LOG_IMPORTANCE_LAST - 1; + + if (colored && isatty(STDERR_FILENO)) { + fprintf(stderr, "%s", verbosity_colors[c]); + } else { + fprintf(stderr, "%s ", verbosity_headers[c]); + } + + vfprintf(stderr, fmt, args); + + if (colored && isatty(STDERR_FILENO)) { + fprintf(stderr, "\x1B[0m"); + } + fprintf(stderr, "\n"); +} + +static wlr_log_func_t log_callback = log_stderr; + +static void log_wl(const char *fmt, va_list args) { + static char wlr_fmt[1024]; + int n = snprintf(wlr_fmt, sizeof(wlr_fmt), "[wayland] %s", fmt); + size_t len = strlen(wlr_fmt); + if (n > 0 && wlr_fmt[len - 1] == '\n') { + wlr_fmt[len - 1] = '\0'; + } + _wlr_vlog(WLR_INFO, wlr_fmt, args); +} + +void wlr_log_init(enum wlr_log_importance verbosity, wlr_log_func_t callback) { + init_start_time(); + + if (verbosity < WLR_LOG_IMPORTANCE_LAST) { + log_importance = verbosity; + } + if (callback) { + log_callback = callback; + } + + wl_log_set_handler_server(log_wl); +} + +void _wlr_vlog(enum wlr_log_importance verbosity, const char *fmt, va_list args) { + log_callback(verbosity, fmt, args); +} + +void _wlr_log(enum wlr_log_importance verbosity, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + log_callback(verbosity, fmt, args); + va_end(args); +} + +enum wlr_log_importance wlr_log_get_verbosity(void) { + return log_importance; +} diff --git a/util/meson.build b/util/meson.build new file mode 100644 index 0000000..053e2c5 --- /dev/null +++ b/util/meson.build @@ -0,0 +1,16 @@ +wlr_files += files( + 'addon.c', + 'array.c', + 'box.c', + 'env.c', + 'global.c', + 'log.c', + 'rect_union.c', + 'region.c', + 'set.c', + 'shm.c', + 'time.c', + 'token.c', + 'transform.c', + 'utf8.c', +) diff --git a/util/rect_union.c b/util/rect_union.c new file mode 100644 index 0000000..8cd26d7 --- /dev/null +++ b/util/rect_union.c @@ -0,0 +1,91 @@ +#include +#include "util/rect_union.h" + +static void box_union(pixman_box32_t *dst, pixman_box32_t box) { + dst->x1 = dst->x1 < box.x1 ? dst->x1 : box.x1; + dst->y1 = dst->y1 < box.y1 ? dst->y1 : box.y1; + dst->x2 = dst->x2 > box.x2 ? dst->x2 : box.x2; + dst->y2 = dst->y2 > box.y2 ? dst->y2 : box.y2; +} + +static bool box_empty_or_invalid(pixman_box32_t box) { + return box.x1 >= box.x2 || box.y1 >= box.y2; +} + +void rect_union_init(struct rect_union *ru) { + *ru = (struct rect_union) { + .alloc_failure = false, + .bounding_box = (pixman_box32_t) { + .x1 = INT_MAX, + .x2 = INT_MIN, + .y1 = INT_MAX, + .y2 = INT_MIN, + } + }; + pixman_region32_init(&ru->region); + wl_array_init(&ru->unsorted); +}; + +void rect_union_finish(struct rect_union *ru) { + pixman_region32_fini(&ru->region); + wl_array_release(&ru->unsorted); +} + +static void handle_alloc_failure(struct rect_union *ru) { + ru->alloc_failure = true; + wl_array_release(&ru->unsorted); + wl_array_init(&ru->unsorted); +} + +void rect_union_add(struct rect_union *ru, pixman_box32_t box) { + if (box_empty_or_invalid(box)) { + return; + } + + box_union(&ru->bounding_box, box); + + if (!ru->alloc_failure) { + pixman_box32_t *entry = wl_array_add(&ru->unsorted, sizeof(*entry)); + if (entry) { + *entry = box; + } else { + handle_alloc_failure(ru); + } + } +} + +const pixman_region32_t *rect_union_evaluate(struct rect_union *ru) { + if (ru->alloc_failure) { + goto bounding_box; + } + + int nrects = (int)(ru->unsorted.size / sizeof(pixman_box32_t)); + pixman_region32_t reg; + bool ok = pixman_region32_init_rects(®, ru->unsorted.data, nrects); + if (!ok) { + handle_alloc_failure(ru); + goto bounding_box; + } + ok = pixman_region32_union(®, ®, &ru->region); + if (!ok) { + pixman_region32_fini(®); + handle_alloc_failure(ru); + goto bounding_box; + } + pixman_region32_fini(&ru->region); + // pixman_region32_t is safe to move + ru->region = reg; + wl_array_release(&ru->unsorted); + wl_array_init(&ru->unsorted); + + return &ru->region; +bounding_box: + pixman_region32_fini(&ru->region); + if (box_empty_or_invalid(ru->bounding_box)) { + pixman_region32_init(&ru->region); + } else { + pixman_region32_init_with_extents(&ru->region, &ru->bounding_box); + } + return &ru->region; +} + diff --git a/util/region.c b/util/region.c new file mode 100644 index 0000000..b74c55e --- /dev/null +++ b/util/region.c @@ -0,0 +1,256 @@ +#include +#include +#include +#include +#include + +void wlr_region_scale(pixman_region32_t *dst, const pixman_region32_t *src, + float scale) { + wlr_region_scale_xy(dst, src, scale, scale); +} + +void wlr_region_scale_xy(pixman_region32_t *dst, const pixman_region32_t *src, + float scale_x, float scale_y) { + if (scale_x == 1.0 && scale_y == 1.0) { + pixman_region32_copy(dst, src); + return; + } + + int nrects; + const pixman_box32_t *src_rects = pixman_region32_rectangles(src, &nrects); + + pixman_box32_t *dst_rects = malloc(nrects * sizeof(pixman_box32_t)); + if (dst_rects == NULL) { + return; + } + + for (int i = 0; i < nrects; ++i) { + dst_rects[i].x1 = floor(src_rects[i].x1 * scale_x); + dst_rects[i].x2 = ceil(src_rects[i].x2 * scale_x); + dst_rects[i].y1 = floor(src_rects[i].y1 * scale_y); + dst_rects[i].y2 = ceil(src_rects[i].y2 * scale_y); + } + + pixman_region32_fini(dst); + pixman_region32_init_rects(dst, dst_rects, nrects); + free(dst_rects); +} + +void wlr_region_transform(pixman_region32_t *dst, const pixman_region32_t *src, + enum wl_output_transform transform, int width, int height) { + if (transform == WL_OUTPUT_TRANSFORM_NORMAL) { + pixman_region32_copy(dst, src); + return; + } + + int nrects; + const pixman_box32_t *src_rects = pixman_region32_rectangles(src, &nrects); + + pixman_box32_t *dst_rects = malloc(nrects * sizeof(pixman_box32_t)); + if (dst_rects == NULL) { + return; + } + + for (int i = 0; i < nrects; ++i) { + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + dst_rects[i].x1 = src_rects[i].x1; + dst_rects[i].y1 = src_rects[i].y1; + dst_rects[i].x2 = src_rects[i].x2; + dst_rects[i].y2 = src_rects[i].y2; + break; + case WL_OUTPUT_TRANSFORM_90: + dst_rects[i].x1 = height - src_rects[i].y2; + dst_rects[i].y1 = src_rects[i].x1; + dst_rects[i].x2 = height - src_rects[i].y1; + dst_rects[i].y2 = src_rects[i].x2; + break; + case WL_OUTPUT_TRANSFORM_180: + dst_rects[i].x1 = width - src_rects[i].x2; + dst_rects[i].y1 = height - src_rects[i].y2; + dst_rects[i].x2 = width - src_rects[i].x1; + dst_rects[i].y2 = height - src_rects[i].y1; + break; + case WL_OUTPUT_TRANSFORM_270: + dst_rects[i].x1 = src_rects[i].y1; + dst_rects[i].y1 = width - src_rects[i].x2; + dst_rects[i].x2 = src_rects[i].y2; + dst_rects[i].y2 = width - src_rects[i].x1; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + dst_rects[i].x1 = width - src_rects[i].x2; + dst_rects[i].y1 = src_rects[i].y1; + dst_rects[i].x2 = width - src_rects[i].x1; + dst_rects[i].y2 = src_rects[i].y2; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + dst_rects[i].x1 = src_rects[i].y1; + dst_rects[i].y1 = src_rects[i].x1; + dst_rects[i].x2 = src_rects[i].y2; + dst_rects[i].y2 = src_rects[i].x2; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + dst_rects[i].x1 = src_rects[i].x1; + dst_rects[i].y1 = height - src_rects[i].y2; + dst_rects[i].x2 = src_rects[i].x2; + dst_rects[i].y2 = height - src_rects[i].y1; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + dst_rects[i].x1 = height - src_rects[i].y2; + dst_rects[i].y1 = width - src_rects[i].x2; + dst_rects[i].x2 = height - src_rects[i].y1; + dst_rects[i].y2 = width - src_rects[i].x1; + break; + } + } + + pixman_region32_fini(dst); + pixman_region32_init_rects(dst, dst_rects, nrects); + free(dst_rects); +} + +void wlr_region_expand(pixman_region32_t *dst, const pixman_region32_t *src, + int distance) { + assert(distance >= 0); + + if (distance == 0) { + pixman_region32_copy(dst, src); + return; + } + + int nrects; + const pixman_box32_t *src_rects = pixman_region32_rectangles(src, &nrects); + + pixman_box32_t *dst_rects = malloc(nrects * sizeof(pixman_box32_t)); + if (dst_rects == NULL) { + return; + } + + for (int i = 0; i < nrects; ++i) { + dst_rects[i].x1 = src_rects[i].x1 - distance; + dst_rects[i].x2 = src_rects[i].x2 + distance; + dst_rects[i].y1 = src_rects[i].y1 - distance; + dst_rects[i].y2 = src_rects[i].y2 + distance; + } + + pixman_region32_fini(dst); + pixman_region32_init_rects(dst, dst_rects, nrects); + free(dst_rects); +} + +void wlr_region_rotated_bounds(pixman_region32_t *dst, const pixman_region32_t *src, + float rotation, int ox, int oy) { + if (rotation == 0) { + pixman_region32_copy(dst, src); + return; + } + + int nrects; + const pixman_box32_t *src_rects = pixman_region32_rectangles(src, &nrects); + + pixman_box32_t *dst_rects = malloc(nrects * sizeof(pixman_box32_t)); + if (dst_rects == NULL) { + return; + } + + for (int i = 0; i < nrects; ++i) { + double x1 = src_rects[i].x1 - ox; + double y1 = src_rects[i].y1 - oy; + double x2 = src_rects[i].x2 - ox; + double y2 = src_rects[i].y2 - oy; + + double rx1 = x1 * cos(rotation) - y1 * sin(rotation); + double ry1 = x1 * sin(rotation) + y1 * cos(rotation); + + double rx2 = x2 * cos(rotation) - y1 * sin(rotation); + double ry2 = x2 * sin(rotation) + y1 * cos(rotation); + + double rx3 = x2 * cos(rotation) - y2 * sin(rotation); + double ry3 = x2 * sin(rotation) + y2 * cos(rotation); + + double rx4 = x1 * cos(rotation) - y2 * sin(rotation); + double ry4 = x1 * sin(rotation) + y2 * cos(rotation); + + x1 = fmin(fmin(rx1, rx2), fmin(rx3, rx4)); + y1 = fmin(fmin(ry1, ry2), fmin(ry3, ry4)); + x2 = fmax(fmax(rx1, rx2), fmax(rx3, rx4)); + y2 = fmax(fmax(ry1, ry2), fmax(ry3, ry4)); + + dst_rects[i].x1 = floor(ox + x1); + dst_rects[i].x2 = ceil(ox + x2); + dst_rects[i].y1 = floor(oy + y1); + dst_rects[i].y2 = ceil(oy + y2); + } + + pixman_region32_fini(dst); + pixman_region32_init_rects(dst, dst_rects, nrects); + free(dst_rects); +} + +static void region_confine(const pixman_region32_t *region, double x1, double y1, double x2, + double y2, double *x2_out, double *y2_out, pixman_box32_t box) { + double x_clamped = fmax(fmin(x2, box.x2 - 1), box.x1); + double y_clamped = fmax(fmin(y2, box.y2 - 1), box.y1); + + // If the target coordinates are above box.{x,y}2 - 1, but less than + // box.{x,y}2, then they are still within the box. + if (floor(x_clamped) == floor(x2) && floor(y_clamped) == floor(y2)) { + *x2_out = x2; + *y2_out = y2; + return; + } + + double dx = x2 - x1; + double dy = y2 - y1; + + // We use fabs to avoid negative zeroes and thus avoid a bug + // with negative infinity. + double delta = fmin(fabs(x_clamped - x1) / fabs(dx), fabs(y_clamped - y1) / fabs(dy)); + + // We clamp it again due to precision errors. + double x = fmax(fmin(delta * dx + x1, box.x2 - 1), box.x1); + double y = fmax(fmin(delta * dy + y1, box.y2 - 1), box.y1); + + // Go one unit past the boundary to find an adjacent box. + int x_ext = floor(x) + (dx == 0 ? 0 : dx > 0 ? 1 : -1); + int y_ext = floor(y) + (dy == 0 ? 0 : dy > 0 ? 1 : -1); + + if (pixman_region32_contains_point(region, x_ext, y_ext, &box)) { + return region_confine(region, x, y, x2, y2, x2_out, y2_out, box); + } else if (dx == 0 || dy == 0) { + *x2_out = x; + *y2_out = y; + } else { + bool bordering_x = x == box.x1 || x == box.x2 - 1; + bool bordering_y = y == box.y1 || y == box.y2 - 1; + + if (bordering_x == bordering_y) { + double x2_potential, y2_potential; + double tmp1, tmp2; + region_confine(region, x, y, x, y2, &tmp1, &y2_potential, box); + region_confine(region, x, y, x2, y, &x2_potential, &tmp2, box); + if (fabs(x2_potential - x) > fabs(y2_potential - y)) { + *x2_out = x2_potential; + *y2_out = y; + } else { + *x2_out = x; + *y2_out = y2_potential; + } + } else if (bordering_x) { + return region_confine(region, x, y, x, y2, x2_out, y2_out, box); + } else if (bordering_y) { + return region_confine(region, x, y, x2, y, x2_out, y2_out, box); + } + } +} + +bool wlr_region_confine(const pixman_region32_t *region, double x1, double y1, double x2, + double y2, double *x2_out, double *y2_out) { + pixman_box32_t box; + if (pixman_region32_contains_point(region, floor(x1), floor(y1), &box)) { + region_confine(region, x1, y1, x2, y2, x2_out, y2_out, box); + return true; + } else { + return false; + } +} diff --git a/util/set.c b/util/set.c new file mode 100644 index 0000000..1366d04 --- /dev/null +++ b/util/set.c @@ -0,0 +1,25 @@ +#include "util/set.h" + +ssize_t set_add(uint32_t values[], size_t *len, size_t cap, uint32_t target) { + for (uint32_t i = 0; i < *len; ++i) { + if (values[i] == target) { + return i; + } + } + if (*len == cap) { + return -1; + } + values[*len] = target; + return (*len)++; +} + +ssize_t set_remove(uint32_t values[], size_t *len, size_t cap, uint32_t target) { + for (uint32_t i = 0; i < *len; ++i) { + if (values[i] == target) { + --(*len); + values[i] = values[*len]; + return i; + } + } + return -1; +} diff --git a/util/shm.c b/util/shm.c new file mode 100644 index 0000000..5eb87bb --- /dev/null +++ b/util/shm.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "util/shm.h" + +#define RANDNAME_PATTERN "/wlroots-XXXXXX" + +static void randname(char *buf) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + for (int i = 0; i < 6; ++i) { + buf[i] = 'A'+(r&15)+(r&16)*2; + r >>= 5; + } +} + +static int excl_shm_open(char *name) { + int retries = 100; + do { + randname(name + strlen(RANDNAME_PATTERN) - 6); + + --retries; + // CLOEXEC is guaranteed to be set by shm_open + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +int allocate_shm_file(size_t size) { + char name[] = RANDNAME_PATTERN; + int fd = excl_shm_open(name); + if (fd < 0) { + return -1; + } + shm_unlink(name); + + int ret; + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(fd); + return -1; + } + + return fd; +} + +bool allocate_shm_file_pair(size_t size, int *rw_fd_ptr, int *ro_fd_ptr) { + char name[] = RANDNAME_PATTERN; + int rw_fd = excl_shm_open(name); + if (rw_fd < 0) { + return false; + } + + // CLOEXEC is guaranteed to be set by shm_open + int ro_fd = shm_open(name, O_RDONLY, 0); + if (ro_fd < 0) { + shm_unlink(name); + close(rw_fd); + return false; + } + + shm_unlink(name); + + // Make sure the file cannot be re-opened in read-write mode (e.g. via + // "/proc/self/fd/" on Linux) + if (fchmod(rw_fd, 0) != 0) { + close(rw_fd); + close(ro_fd); + return false; + } + + int ret; + do { + ret = ftruncate(rw_fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(rw_fd); + close(ro_fd); + return false; + } + + *rw_fd_ptr = rw_fd; + *ro_fd_ptr = ro_fd; + return true; +} diff --git a/util/time.c b/util/time.c new file mode 100644 index 0000000..bc4a106 --- /dev/null +++ b/util/time.c @@ -0,0 +1,35 @@ +#include +#include + +#include "util/time.h" + +static const long NSEC_PER_SEC = 1000000000; + +int64_t timespec_to_msec(const struct timespec *a) { + return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; +} + +int64_t timespec_to_nsec(const struct timespec *a) { + return (int64_t)a->tv_sec * NSEC_PER_SEC + a->tv_nsec; +} + +void timespec_from_nsec(struct timespec *r, int64_t nsec) { + r->tv_sec = nsec / NSEC_PER_SEC; + r->tv_nsec = nsec % NSEC_PER_SEC; +} + +int64_t get_current_time_msec(void) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return timespec_to_msec(&now); +} + +void timespec_sub(struct timespec *r, const struct timespec *a, + const struct timespec *b) { + r->tv_sec = a->tv_sec - b->tv_sec; + r->tv_nsec = a->tv_nsec - b->tv_nsec; + if (r->tv_nsec < 0) { + r->tv_sec--; + r->tv_nsec += NSEC_PER_SEC; + } +} diff --git a/util/token.c b/util/token.c new file mode 100644 index 0000000..2d58d16 --- /dev/null +++ b/util/token.c @@ -0,0 +1,39 @@ +#include "util/token.h" +#include "wlr/util/log.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +bool generate_token(char out[static TOKEN_SIZE]) { + static FILE *urandom = NULL; + uint64_t data[2]; + + if (!urandom) { + int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); + if (fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open random device"); + return false; + } + if (!(urandom = fdopen(fd, "r"))) { + wlr_log_errno(WLR_ERROR, "fdopen failed"); + close(fd); + return false; + } + } + if (fread(data, sizeof(data), 1, urandom) != 1) { + wlr_log_errno(WLR_ERROR, "Failed to read from random device"); + return false; + } + if (snprintf(out, TOKEN_SIZE, "%016" PRIx64 "%016" PRIx64, data[0], data[1]) != TOKEN_SIZE - 1) { + wlr_log_errno(WLR_ERROR, "Failed to format hex string token"); + return false; + } + return true; +} + diff --git a/util/transform.c b/util/transform.c new file mode 100644 index 0000000..de3f8ef --- /dev/null +++ b/util/transform.c @@ -0,0 +1,33 @@ +#include + +enum wl_output_transform wlr_output_transform_invert( + enum wl_output_transform tr) { + if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) { + tr ^= WL_OUTPUT_TRANSFORM_180; + } + return tr; +} + +enum wl_output_transform wlr_output_transform_compose( + enum wl_output_transform tr_a, enum wl_output_transform tr_b) { + uint32_t flipped = (tr_a ^ tr_b) & WL_OUTPUT_TRANSFORM_FLIPPED; + uint32_t rotation_mask = WL_OUTPUT_TRANSFORM_90 | WL_OUTPUT_TRANSFORM_180; + uint32_t rotated; + if (tr_b & WL_OUTPUT_TRANSFORM_FLIPPED) { + // When a rotation of k degrees is followed by a flip, the + // equivalent transform is a flip followed by a rotation of + // -k degrees. + rotated = (tr_b - tr_a) & rotation_mask; + } else { + rotated = (tr_a + tr_b) & rotation_mask; + } + return flipped | rotated; +} + +void wlr_output_transform_coords(enum wl_output_transform tr, int *x, int *y) { + if (tr & WL_OUTPUT_TRANSFORM_90) { + int tmp = *x; + *x = *y; + *y = tmp; + } +} diff --git a/util/utf8.c b/util/utf8.c new file mode 100644 index 0000000..802fd01 --- /dev/null +++ b/util/utf8.c @@ -0,0 +1,66 @@ +#include +#include "util/utf8.h" + +static bool in_range(char x, uint8_t low, uint8_t high) { + uint8_t v = (uint8_t)x; + return low <= v && v <= high; +} + +bool is_utf8(const char *string) { + /* Returns true iff the string is 'well-formed', as defined by + * Unicode Standard 15.0.0. See Chapter 3, D92 and Table 3.7. + * + * UTF-8 strings are sequences of code points encoded in one of the + * following ways. The first byte determines the pattern. + * + * 00..7F + * C2..DF 80..BF + * E0 A0..BF 80..BF + * E1..EC 80..BF 80..BF + * ED 80..9F 80..BF + * EE..EF 80..BF 80..BF + * F0 90..BF 80..BF 80..BF + * F1..F3 80..BF 80..BF 80..BF + * F4 80..8F 80..BF 80..BF + */ + uint8_t range_table[9][8] = { + {0x00, 0x7F}, + {0xC2, 0xDF, 0x80, 0xBF}, + {0xE0, 0xE0, 0xA0, 0xBF, 0x80, 0xBF}, + {0xE1, 0xEC, 0x80, 0xBF, 0x80, 0xBF}, + {0xED, 0xED, 0x80, 0x9F, 0x80, 0xBF}, + {0xEE, 0xEF, 0x80, 0xBF, 0x80, 0xBF}, + {0xF0, 0xF0, 0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}, + {0xF1, 0xF3, 0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}, + {0xF4, 0xF4, 0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}, + }; + int lengths[9] = { + 1, 2, 3, 3, 3, 3, 4, 4, 4 + }; + + while (string[0]) { + bool accept = false; + for (int i = 0; i < 9; i++) { + if (!in_range(string[0], range_table[i][0], + range_table[i][1])) { + continue; + } + for (int j = 1; j < lengths[i]; j++) { + if (!in_range(string[j], range_table[i][2 * j], + range_table[i][2 * j + 1])) { + // Early exit is necessary to avoid + // reading past the null terminator + return false; + } + } + string += lengths[i]; + accept = true; + break; + } + if (!accept) { + return false; + } + } + + return true; +} diff --git a/wlroots.syms b/wlroots.syms new file mode 100644 index 0000000..7127121 --- /dev/null +++ b/wlroots.syms @@ -0,0 +1,7 @@ +{ + global: + wlr_*; + _wlr_*; + local: + *; +}; diff --git a/xcursor/meson.build b/xcursor/meson.build new file mode 100644 index 0000000..cf3718f --- /dev/null +++ b/xcursor/meson.build @@ -0,0 +1,10 @@ +icondir = get_option('icon_directory') +if icondir == '' + icondir = get_option('prefix') / get_option('datadir') / 'icons' +endif +internal_config.set_quoted('ICONDIR', icondir) + +wlr_files += files( + 'wlr_xcursor.c', + 'xcursor.c', +) diff --git a/xcursor/wlr_xcursor.c b/xcursor/wlr_xcursor.c new file mode 100644 index 0000000..c57d817 --- /dev/null +++ b/xcursor/wlr_xcursor.c @@ -0,0 +1,360 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include "xcursor/xcursor.h" + +static void xcursor_destroy(struct wlr_xcursor *cursor) { + for (size_t i = 0; i < cursor->image_count; i++) { + free(cursor->images[i]->buffer); + free(cursor->images[i]); + } + + free(cursor->images); + free(cursor->name); + free(cursor); +} + +#include "xcursor/cursor_data.h" + +static struct wlr_xcursor *xcursor_create_from_data( + const struct cursor_metadata *metadata, struct wlr_xcursor_theme *theme) { + struct wlr_xcursor *cursor = calloc(1, sizeof(*cursor)); + if (!cursor) { + return NULL; + } + + cursor->image_count = 1; + cursor->images = calloc(1, sizeof(*cursor->images)); + if (!cursor->images) { + goto err_free_cursor; + } + + cursor->name = strdup(metadata->name); + cursor->total_delay = 0; + + struct wlr_xcursor_image *image = calloc(1, sizeof(*image)); + if (!image) { + goto err_free_images; + } + + cursor->images[0] = image; + image->buffer = NULL; + image->width = metadata->width; + image->height = metadata->height; + image->hotspot_x = metadata->hotspot_x; + image->hotspot_y = metadata->hotspot_y; + image->delay = 0; + + int size = metadata->width * metadata->height * sizeof(uint32_t); + image->buffer = malloc(size); + if (!image->buffer) { + goto err_free_image; + } + + memcpy(image->buffer, cursor_data + metadata->offset, size); + + return cursor; + +err_free_image: + free(image); + +err_free_images: + free(cursor->name); + free(cursor->images); + +err_free_cursor: + free(cursor); + return NULL; +} + +static void load_default_theme(struct wlr_xcursor_theme *theme) { + free(theme->name); + theme->name = strdup("default"); + + size_t cursor_count = sizeof(cursor_metadata) / sizeof(cursor_metadata[0]); + theme->cursor_count = 0; + theme->cursors = malloc(cursor_count * sizeof(*theme->cursors)); + if (theme->cursors == NULL) { + return; + } + + for (uint32_t i = 0; i < cursor_count; ++i) { + theme->cursors[i] = xcursor_create_from_data(&cursor_metadata[i], theme); + if (theme->cursors[i] == NULL) { + break; + } + ++theme->cursor_count; + } +} + +static struct wlr_xcursor *xcursor_create_from_xcursor_images( + struct xcursor_images *images, struct wlr_xcursor_theme *theme) { + struct wlr_xcursor *cursor = calloc(1, sizeof(*cursor)); + if (!cursor) { + return NULL; + } + + cursor->images = calloc(images->nimage, sizeof(cursor->images[0])); + if (!cursor->images) { + free(cursor); + return NULL; + } + + cursor->name = strdup(images->name); + cursor->total_delay = 0; + + for (int i = 0; i < images->nimage; i++) { + struct wlr_xcursor_image *image = calloc(1, sizeof(*image)); + if (image == NULL) { + break; + } + + image->buffer = NULL; + + image->width = images->images[i]->width; + image->height = images->images[i]->height; + image->hotspot_x = images->images[i]->xhot; + image->hotspot_y = images->images[i]->yhot; + image->delay = images->images[i]->delay; + + size_t size = image->width * image->height * 4; + image->buffer = malloc(size); + if (!image->buffer) { + free(image); + break; + } + + /* copy pixels to shm pool */ + memcpy(image->buffer, images->images[i]->pixels, size); + cursor->total_delay += image->delay; + cursor->images[i] = image; + cursor->image_count++; + } + + if (cursor->image_count == 0) { + free(cursor->name); + free(cursor->images); + free(cursor); + return NULL; + } + + return cursor; +} + +static struct wlr_xcursor *xcursor_theme_get_cursor(struct wlr_xcursor_theme *theme, + const char *name); + +static void load_callback(struct xcursor_images *images, void *data) { + struct wlr_xcursor_theme *theme = data; + + if (xcursor_theme_get_cursor(theme, images->name)) { + xcursor_images_destroy(images); + return; + } + + struct wlr_xcursor *cursor = xcursor_create_from_xcursor_images(images, theme); + if (cursor) { + theme->cursor_count++; + struct wlr_xcursor **cursors = realloc(theme->cursors, + theme->cursor_count * sizeof(theme->cursors[0])); + if (cursors == NULL) { + theme->cursor_count--; + free(cursor); + } else { + theme->cursors = cursors; + theme->cursors[theme->cursor_count - 1] = cursor; + } + } + + xcursor_images_destroy(images); +} + +struct wlr_xcursor_theme *wlr_xcursor_theme_load(const char *name, int size) { + struct wlr_xcursor_theme *theme = calloc(1, sizeof(*theme)); + if (!theme) { + return NULL; + } + + if (!name) { + name = "default"; + } + + theme->name = strdup(name); + if (!theme->name) { + goto out_error_name; + } + theme->size = size; + theme->cursor_count = 0; + theme->cursors = NULL; + + xcursor_load_theme(name, size, load_callback, theme); + + if (theme->cursor_count == 0) { + load_default_theme(theme); + } + + wlr_log(WLR_DEBUG, "Loaded cursor theme '%s' at size %d (%d available cursors)", + theme->name, size, theme->cursor_count); + + return theme; + +out_error_name: + free(theme); + return NULL; +} + +void wlr_xcursor_theme_destroy(struct wlr_xcursor_theme *theme) { + for (unsigned int i = 0; i < theme->cursor_count; i++) { + xcursor_destroy(theme->cursors[i]); + } + + free(theme->name); + free(theme->cursors); + free(theme); +} + +static struct wlr_xcursor *xcursor_theme_get_cursor(struct wlr_xcursor_theme *theme, + const char *name) { + for (unsigned int i = 0; i < theme->cursor_count; i++) { + if (strcmp(name, theme->cursors[i]->name) == 0) { + return theme->cursors[i]; + } + } + + return NULL; +} + +struct wlr_xcursor *wlr_xcursor_theme_get_cursor(struct wlr_xcursor_theme *theme, + const char *name) { + struct wlr_xcursor *xcursor = xcursor_theme_get_cursor(theme, name); + if (xcursor) { + return xcursor; + } + + // Try the legacy name as a fallback + const char *fallback; + if (strcmp(name, "default") == 0) { + fallback = "left_ptr"; + } else if (strcmp(name, "text") == 0) { + fallback = "xterm"; + } else if (strcmp(name, "pointer") == 0) { + fallback = "hand1"; + } else if (strcmp(name, "wait") == 0) { + fallback = "watch"; + } else if (strcmp(name, "all-scroll") == 0) { + fallback = "grabbing"; + } else if (strcmp(name, "sw-resize") == 0) { + fallback = "bottom_left_corner"; + } else if (strcmp(name, "se-resize") == 0) { + fallback = "bottom_right_corner"; + } else if (strcmp(name, "s-resize") == 0) { + fallback = "bottom_side"; + } else if (strcmp(name, "w-resize") == 0) { + fallback = "left_side"; + } else if (strcmp(name, "e-resize") == 0) { + fallback = "right_side"; + } else if (strcmp(name, "nw-resize") == 0) { + fallback = "top_left_corner"; + } else if (strcmp(name, "ne-resize") == 0) { + fallback = "top_right_corner"; + } else if (strcmp(name, "n-resize") == 0) { + fallback = "top_side"; + } else { + return NULL; + } + return xcursor_theme_get_cursor(theme, fallback); +} + +static int xcursor_frame_and_duration(struct wlr_xcursor *cursor, + uint32_t time, uint32_t *duration) { + if (cursor->image_count == 1) { + if (duration) { + *duration = 0; + } + return 0; + } + + int i = 0; + uint32_t t = time % cursor->total_delay; + + /* If there is a 0 delay in the image set then this + * loop breaks on it and we display that cursor until + * time % cursor->total_delay wraps again. + * Since a 0 delay is silly, and we've never actually + * seen one in a cursor file, we haven't bothered to + * "fix" this. + */ + while (t - cursor->images[i]->delay < t) { + t -= cursor->images[i++]->delay; + } + + if (!duration) { + return i; + } + + /* Make sure we don't accidentally tell the caller this is + * a static cursor image. + */ + if (t >= cursor->images[i]->delay) { + *duration = 1; + } else { + *duration = cursor->images[i]->delay - t; + } + + return i; +} + +int wlr_xcursor_frame(struct wlr_xcursor *_cursor, uint32_t time) { + return xcursor_frame_and_duration(_cursor, time, NULL); +} + +const char *wlr_xcursor_get_resize_name(enum wlr_edges edges) { + if (edges & WLR_EDGE_TOP) { + if (edges & WLR_EDGE_RIGHT) { + return "ne-resize"; + } else if (edges & WLR_EDGE_LEFT) { + return "nw-resize"; + } + return "n-resize"; + } else if (edges & WLR_EDGE_BOTTOM) { + if (edges & WLR_EDGE_RIGHT) { + return "se-resize"; + } else if (edges & WLR_EDGE_LEFT) { + return "sw-resize"; + } + return "s-resize"; + } else if (edges & WLR_EDGE_RIGHT) { + return "e-resize"; + } else if (edges & WLR_EDGE_LEFT) { + return "w-resize"; + } + return "se-resize"; // fallback +} diff --git a/xcursor/xcursor.c b/xcursor/xcursor.c new file mode 100644 index 0000000..6634670 --- /dev/null +++ b/xcursor/xcursor.c @@ -0,0 +1,787 @@ +/* + * Copyright © 2002 Keith Packard + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#undef _POSIX_C_SOURCE +#define _DEFAULT_SOURCE // for d_type in struct dirent +#include +#include +#include +#include +#include +#include "config.h" +#include "xcursor/xcursor.h" + +/* + * Cursor files start with a header. The header + * contains a magic number, a version number and a + * table of contents which has type and offset information + * for the remaining tables in the file. + * + * File minor versions increment for compatible changes + * File major versions increment for incompatible changes (never, we hope) + * + * Chunks of the same type are always upward compatible. Incompatible + * changes are made with new chunk types; the old data can remain under + * the old type. Upward compatible changes can add header data as the + * header lengths are specified in the file. + * + * File: + * FileHeader + * LISTofChunk + * + * FileHeader: + * CARD32 magic magic number + * CARD32 header bytes in file header + * CARD32 version file version + * CARD32 ntoc number of toc entries + * LISTofFileToc toc table of contents + * + * FileToc: + * CARD32 type entry type + * CARD32 subtype entry subtype (size for images) + * CARD32 position absolute file position + */ + +#define XCURSOR_MAGIC 0x72756358 /* "Xcur" LSBFirst */ + +/* + * This version number is stored in cursor files; changes to the + * file format require updating this version number + */ +#define XCURSOR_FILE_MAJOR 1 +#define XCURSOR_FILE_MINOR 0 +#define XCURSOR_FILE_VERSION ((XCURSOR_FILE_MAJOR << 16) | (XCURSOR_FILE_MINOR)) +#define XCURSOR_FILE_HEADER_LEN (4 * 4) +#define XCURSOR_FILE_TOC_LEN (3 * 4) + +struct xcursor_file_toc { + uint32_t type; /* chunk type */ + uint32_t subtype; /* subtype (size for images) */ + uint32_t position; /* absolute position in file */ +}; + +struct xcursor_file_header { + uint32_t magic; /* magic number */ + uint32_t header; /* byte length of header */ + uint32_t version; /* file version number */ + uint32_t ntoc; /* number of toc entries */ + struct xcursor_file_toc *tocs; /* table of contents */ +}; + +/* + * The rest of the file is a list of chunks, each tagged by type + * and version. + * + * Chunk: + * ChunkHeader + * + * + * + * ChunkHeader: + * CARD32 header bytes in chunk header + type header + * CARD32 type chunk type + * CARD32 subtype chunk subtype + * CARD32 version chunk type version + */ + +#define XCURSOR_CHUNK_HEADER_LEN (4 * 4) + +struct xcursor_chunk_header { + uint32_t header; /* bytes in chunk header */ + uint32_t type; /* chunk type */ + uint32_t subtype; /* chunk subtype (size for images) */ + uint32_t version; /* version of this type */ +}; + +/* + * Each cursor image occupies a separate image chunk. + * The length of the image header follows the chunk header + * so that future versions can extend the header without + * breaking older applications + * + * Image: + * ChunkHeader header chunk header + * CARD32 width actual width + * CARD32 height actual height + * CARD32 xhot hot spot x + * CARD32 yhot hot spot y + * CARD32 delay animation delay + * LISTofCARD32 pixels ARGB pixels + */ + +#define XCURSOR_IMAGE_TYPE 0xfffd0002 +#define XCURSOR_IMAGE_VERSION 1 +#define XCURSOR_IMAGE_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (5*4)) +#define XCURSOR_IMAGE_MAX_SIZE 0x7fff /* 32767x32767 max cursor size */ + +/* + * From libXcursor/src/file.c + */ + +static struct xcursor_image * +xcursor_image_create(int width, int height) +{ + struct xcursor_image *image; + + if (width < 0 || height < 0) + return NULL; + if (width > XCURSOR_IMAGE_MAX_SIZE || height > XCURSOR_IMAGE_MAX_SIZE) + return NULL; + + image = malloc(sizeof(*image) + + width * height * sizeof(uint32_t)); + if (!image) + return NULL; + image->version = XCURSOR_IMAGE_VERSION; + image->pixels = (uint32_t *) (image + 1); + image->size = width > height ? width : height; + image->width = width; + image->height = height; + image->delay = 0; + return image; +} + +static void +xcursor_image_destroy(struct xcursor_image *image) +{ + free(image); +} + +static struct xcursor_images * +xcursor_images_create(int size) +{ + struct xcursor_images *images; + + images = malloc(sizeof(*images) + + size * sizeof(struct xcursor_image *)); + if (!images) + return NULL; + images->nimage = 0; + images->images = (struct xcursor_image **) (images + 1); + images->name = NULL; + return images; +} + +void +xcursor_images_destroy(struct xcursor_images *images) +{ + int n; + + if (!images) + return; + + for (n = 0; n < images->nimage; n++) + xcursor_image_destroy(images->images[n]); + free(images->name); + free(images); +} + +static bool +xcursor_read_uint(FILE *file, uint32_t *u) +{ + unsigned char bytes[4]; + + if (!file || !u) + return false; + + if (fread(bytes, 1, 4, file) != 4) + return false; + + *u = ((uint32_t)(bytes[0]) << 0) | + ((uint32_t)(bytes[1]) << 8) | + ((uint32_t)(bytes[2]) << 16) | + ((uint32_t)(bytes[3]) << 24); + return true; +} + +static void +xcursor_file_header_destroy(struct xcursor_file_header *file_header) +{ + free(file_header); +} + +static struct xcursor_file_header * +xcursor_file_header_create(uint32_t ntoc) +{ + struct xcursor_file_header *file_header; + + if (ntoc > 0x10000) + return NULL; + file_header = malloc(sizeof(*file_header) + + ntoc * sizeof(struct xcursor_file_toc)); + if (!file_header) + return NULL; + file_header->magic = XCURSOR_MAGIC; + file_header->header = XCURSOR_FILE_HEADER_LEN; + file_header->version = XCURSOR_FILE_VERSION; + file_header->ntoc = ntoc; + file_header->tocs = (struct xcursor_file_toc *) (file_header + 1); + return file_header; +} + +static struct xcursor_file_header * +xcursor_read_file_header(FILE *file) +{ + struct xcursor_file_header head, *file_header; + uint32_t skip; + unsigned int n; + + if (!file) + return NULL; + + if (!xcursor_read_uint(file, &head.magic)) + return NULL; + if (head.magic != XCURSOR_MAGIC) + return NULL; + if (!xcursor_read_uint(file, &head.header)) + return NULL; + if (!xcursor_read_uint(file, &head.version)) + return NULL; + if (!xcursor_read_uint(file, &head.ntoc)) + return NULL; + skip = head.header - XCURSOR_FILE_HEADER_LEN; + if (skip) + if (fseek(file, skip, SEEK_CUR) == EOF) + return NULL; + file_header = xcursor_file_header_create(head.ntoc); + if (!file_header) + return NULL; + file_header->magic = head.magic; + file_header->header = head.header; + file_header->version = head.version; + file_header->ntoc = head.ntoc; + for (n = 0; n < file_header->ntoc; n++) { + if (!xcursor_read_uint(file, &file_header->tocs[n].type)) + break; + if (!xcursor_read_uint(file, &file_header->tocs[n].subtype)) + break; + if (!xcursor_read_uint(file, &file_header->tocs[n].position)) + break; + } + if (n != file_header->ntoc) { + xcursor_file_header_destroy(file_header); + return NULL; + } + return file_header; +} + +static bool +xcursor_seek_to_toc(FILE *file, + struct xcursor_file_header *file_header, + int toc) +{ + if (!file || !file_header || + fseek(file, file_header->tocs[toc].position, SEEK_SET) == EOF) + return false; + return true; +} + +static bool +xcursor_file_read_chunk_header(FILE *file, + struct xcursor_file_header *file_header, + int toc, + struct xcursor_chunk_header *chunk_header) +{ + if (!file || !file_header || !chunk_header) + return false; + if (!xcursor_seek_to_toc(file, file_header, toc)) + return false; + if (!xcursor_read_uint(file, &chunk_header->header)) + return false; + if (!xcursor_read_uint(file, &chunk_header->type)) + return false; + if (!xcursor_read_uint(file, &chunk_header->subtype)) + return false; + if (!xcursor_read_uint(file, &chunk_header->version)) + return false; + /* sanity check */ + if (chunk_header->type != file_header->tocs[toc].type || + chunk_header->subtype != file_header->tocs[toc].subtype) + return false; + return true; +} + +static uint32_t +dist(uint32_t a, uint32_t b) +{ + return a > b ? a - b : b - a; +} + +static uint32_t +xcursor_file_best_size(struct xcursor_file_header *file_header, + uint32_t size, int *nsizesp) +{ + unsigned int n; + int nsizes = 0; + uint32_t best_size = 0; + uint32_t this_size; + + if (!file_header || !nsizesp) + return 0; + + for (n = 0; n < file_header->ntoc; n++) { + if (file_header->tocs[n].type != XCURSOR_IMAGE_TYPE) + continue; + this_size = file_header->tocs[n].subtype; + if (!best_size || dist(this_size, size) < dist(best_size, size)) { + best_size = this_size; + nsizes = 1; + } else if (this_size == best_size) { + nsizes++; + } + } + *nsizesp = nsizes; + return best_size; +} + +static int +xcursor_find_image_toc(struct xcursor_file_header *file_header, + uint32_t size, int count) +{ + unsigned int toc; + uint32_t this_size; + + if (!file_header) + return 0; + + for (toc = 0; toc < file_header->ntoc; toc++) { + if (file_header->tocs[toc].type != XCURSOR_IMAGE_TYPE) + continue; + this_size = file_header->tocs[toc].subtype; + if (this_size != size) + continue; + if (!count) + break; + count--; + } + if (toc == file_header->ntoc) + return -1; + return toc; +} + +static struct xcursor_image * +xcursor_read_image(FILE *file, + struct xcursor_file_header *file_header, + int toc) +{ + struct xcursor_chunk_header chunk_header; + struct xcursor_image head; + struct xcursor_image *image; + int n; + uint32_t *p; + + if (!file || !file_header) + return NULL; + + if (!xcursor_file_read_chunk_header(file, file_header, toc, &chunk_header)) + return NULL; + if (!xcursor_read_uint(file, &head.width)) + return NULL; + if (!xcursor_read_uint(file, &head.height)) + return NULL; + if (!xcursor_read_uint(file, &head.xhot)) + return NULL; + if (!xcursor_read_uint(file, &head.yhot)) + return NULL; + if (!xcursor_read_uint(file, &head.delay)) + return NULL; + /* sanity check data */ + if (head.width > XCURSOR_IMAGE_MAX_SIZE || + head.height > XCURSOR_IMAGE_MAX_SIZE) + return NULL; + if (head.width == 0 || head.height == 0) + return NULL; + if (head.xhot > head.width || head.yhot > head.height) + return NULL; + + /* Create the image and initialize it */ + image = xcursor_image_create(head.width, head.height); + if (image == NULL) + return NULL; + if (chunk_header.version < image->version) + image->version = chunk_header.version; + image->size = chunk_header.subtype; + image->xhot = head.xhot; + image->yhot = head.yhot; + image->delay = head.delay; + n = image->width * image->height; + p = image->pixels; + while (n--) { + if (!xcursor_read_uint(file, p)) { + xcursor_image_destroy(image); + return NULL; + } + p++; + } + return image; +} + +static struct xcursor_images * +xcursor_xc_file_load_images(FILE *file, int size) +{ + struct xcursor_file_header *file_header; + uint32_t best_size; + int nsize; + struct xcursor_images *images; + int n; + int toc; + + if (!file || size < 0) + return NULL; + file_header = xcursor_read_file_header(file); + if (!file_header) + return NULL; + best_size = xcursor_file_best_size(file_header, (uint32_t) size, &nsize); + if (!best_size) { + xcursor_file_header_destroy(file_header); + return NULL; + } + images = xcursor_images_create(nsize); + if (!images) { + xcursor_file_header_destroy(file_header); + return NULL; + } + for (n = 0; n < nsize; n++) { + toc = xcursor_find_image_toc(file_header, best_size, n); + if (toc < 0) + break; + images->images[images->nimage] = xcursor_read_image(file, file_header, + toc); + if (!images->images[images->nimage]) + break; + images->nimage++; + } + xcursor_file_header_destroy(file_header); + if (images->nimage != nsize) { + xcursor_images_destroy(images); + images = NULL; + } + return images; +} + +/* + * From libXcursor/src/library.c + */ + +#ifndef ICONDIR +#define ICONDIR "/usr/X11R6/lib/X11/icons" +#endif + +#ifndef XCURSORPATH +#define XCURSORPATH "~/.icons:/usr/share/icons:/usr/share/pixmaps:~/.cursors:/usr/share/cursors/xorg-x11:"ICONDIR +#endif + +#define XDG_DATA_HOME_FALLBACK "~/.local/share" +#define CURSORDIR "/icons" + +/** Get search path for cursor themes + * + * This function builds the list of directories to look for cursor + * themes in. The format is PATH-like: directories are separated by + * colons. + * + * The memory block returned by this function is allocated on the heap + * and must be freed by the caller. + */ +static char * +xcursor_library_path(void) +{ + const char *env_var, *suffix; + char *path; + size_t path_size; + + env_var = getenv("XCURSOR_PATH"); + if (env_var) + return strdup(env_var); + + env_var = getenv("XDG_DATA_HOME"); + if (!env_var || env_var[0] != '/') + env_var = XDG_DATA_HOME_FALLBACK; + + suffix = CURSORDIR ":" XCURSORPATH; + path_size = strlen(env_var) + strlen(suffix) + 1; + path = malloc(path_size); + if (!path) + return NULL; + snprintf(path, path_size, "%s%s", env_var, suffix); + return path; +} + +static char * +xcursor_build_theme_dir(const char *dir, const char *theme) +{ + const char *colon; + const char *tcolon; + char *full; + const char *home, *homesep; + size_t dirlen; + size_t homelen; + size_t themelen; + size_t full_size; + + if (!dir || !theme) + return NULL; + + colon = strchr(dir, ':'); + if (!colon) + colon = dir + strlen(dir); + + dirlen = colon - dir; + + tcolon = strchr(theme, ':'); + if (!tcolon) + tcolon = theme + strlen(theme); + + themelen = tcolon - theme; + + home = ""; + homelen = 0; + homesep = ""; + if (*dir == '~') { + home = getenv("HOME"); + if (!home) + return NULL; + homelen = strlen(home); + homesep = "/"; + dir++; + dirlen--; + } + + /* + * add space for any needed directory separators, one per component, + * and one for the trailing null + */ + full_size = 1 + homelen + 1 + dirlen + 1 + themelen + 1; + full = malloc(full_size); + if (!full) + return NULL; + snprintf(full, full_size, "%s%s%.*s/%.*s", home, homesep, + (int)dirlen, dir, (int)themelen, theme); + return full; +} + +static char * +xcursor_build_fullname(const char *dir, const char *subdir, const char *file) +{ + char *full; + size_t full_size; + + if (!dir || !subdir || !file) + return NULL; + + full_size = strlen(dir) + 1 + strlen(subdir) + 1 + strlen(file) + 1; + full = malloc(full_size); + if (!full) + return NULL; + snprintf(full, full_size, "%s/%s/%s", dir, subdir, file); + return full; +} + +static const char * +xcursor_next_path(const char *path) +{ + char *colon = strchr(path, ':'); + + if (!colon) + return NULL; + return colon + 1; +} + +static bool +xcursor_white(char c) +{ + return c == ' ' || c == '\t' || c == '\n'; +} + +static bool +xcursor_sep(char c) +{ + return c == ';' || c == ','; +} + +static char * +xcursor_theme_inherits(const char *full) +{ + char *line = NULL; + size_t line_size = 0; + char *result = NULL; + FILE *f; + + if (!full) + return NULL; + + f = fopen(full, "r"); + if (!f) + return NULL; + + while (getline(&line, &line_size, f) >= 0) { + const char *l; + char *r; + + if (strncmp(line, "Inherits", 8)) + continue; + + l = line + 8; + while (*l == ' ') + l++; + if (*l != '=') + continue; + l++; + while (*l == ' ') + l++; + result = malloc(strlen(l) + 1); + if (!result) + break; + + r = result; + while (*l) { + while (xcursor_sep(*l) || xcursor_white(*l)) + l++; + if (!*l) + break; + if (r != result) + *r++ = ':'; + while (*l && !xcursor_white(*l) && !xcursor_sep(*l)) + *r++ = *l++; + } + *r++ = '\0'; + + break; + } + + fclose(f); + free(line); + + return result; +} + +static void +load_all_cursors_from_dir(const char *path, int size, + void (*load_callback)(struct xcursor_images *, void *), + void *user_data) +{ + FILE *f; + DIR *dir = opendir(path); + struct dirent *ent; + char *full; + struct xcursor_images *images; + + if (!dir) + return; + + for (ent = readdir(dir); ent; ent = readdir(dir)) { +#ifdef _DIRENT_HAVE_D_TYPE + if (ent->d_type != DT_UNKNOWN && + ent->d_type != DT_REG && + ent->d_type != DT_LNK) + continue; +#endif + + full = xcursor_build_fullname(path, "", ent->d_name); + if (!full) + continue; + + f = fopen(full, "r"); + if (!f) { + free(full); + continue; + } + + images = xcursor_xc_file_load_images(f, size); + + if (images) { + images->name = strdup(ent->d_name); + load_callback(images, user_data); + } + + fclose(f); + free(full); + } + + closedir(dir); +} + +/** Load all the cursor of a theme + * + * This function loads all the cursor images of a given theme and its + * inherited themes. Each cursor is loaded into an struct xcursor_images object + * which is passed to the caller's load callback. If a cursor appears + * more than once across all the inherited themes, the load callback + * will be called multiple times, with possibly different struct xcursor_images + * object which have the same name. The user is expected to destroy the + * struct xcursor_images objects passed to the callback with + * xcursor_images_destroy(). + * + * \param theme The name of theme that should be loaded + * \param size The desired size of the cursor images + * \param load_callback A callback function that will be called + * for each cursor loaded. The first parameter is the struct xcursor_images + * object representing the loaded cursor and the second is a pointer + * to data provided by the user. + * \param user_data The data that should be passed to the load callback + */ +void +xcursor_load_theme(const char *theme, int size, + void (*load_callback)(struct xcursor_images *, void *), + void *user_data) +{ + char *full, *dir; + char *inherits = NULL; + const char *path, *i; + char *xcursor_path; + + if (!theme) + theme = "default"; + + xcursor_path = xcursor_library_path(); + for (path = xcursor_path; + path; + path = xcursor_next_path(path)) { + dir = xcursor_build_theme_dir(path, theme); + if (!dir) + continue; + + full = xcursor_build_fullname(dir, "cursors", ""); + if (full) { + load_all_cursors_from_dir(full, size, load_callback, + user_data); + free(full); + } + + if (!inherits) { + full = xcursor_build_fullname(dir, "", "index.theme"); + inherits = xcursor_theme_inherits(full); + free(full); + } + + free(dir); + } + + for (i = inherits; i; i = xcursor_next_path(i)) + xcursor_load_theme(i, size, load_callback, user_data); + + free(inherits); + free(xcursor_path); +} diff --git a/xwayland/meson.build b/xwayland/meson.build new file mode 100644 index 0000000..81cb018 --- /dev/null +++ b/xwayland/meson.build @@ -0,0 +1,98 @@ +xwayland_libs = [] +xwayland_required = [ + 'xcb', + 'xcb-composite', + 'xcb-ewmh', + 'xcb-icccm', + 'xcb-render', + 'xcb-res', + 'xcb-xfixes', +] +xwayland_optional = { + 'xcb-errors': 'Required for printing X11 errors.', +} + +msg = [] +if get_option('xwayland').enabled() + msg += 'Install "@0@" or pass "-Dxwayland=disabled".' +endif +if not get_option('xwayland').disabled() + msg += 'Required for Xwayland support.' +endif + +xwayland = dependency( + 'xwayland', + required: get_option('xwayland'), + fallback: 'xserver', + default_options: [ + 'werror=false', + 'xorg=false', + 'xephyr=false', + 'xwayland=true', + 'xnest=false', + 'xvfb=false', + ], +) +if not xwayland.found() + subdir_done() +endif + +foreach lib : xwayland_required + dep = dependency(lib, + required: get_option('xwayland'), + not_found_message: '\n'.join(msg).format(lib), + ) + if not dep.found() + subdir_done() + endif + + xwayland_libs += dep +endforeach + +foreach lib, desc : xwayland_optional + msg = [] + if get_option(lib).enabled() + msg += 'Install "@0@" or pass "-D@0@=disabled".' + endif + if not get_option(lib).disabled() + msg += desc + endif + + dep = dependency(lib, + required: get_option(lib), + not_found_message: '\n'.join(msg).format(lib), + ) + + internal_features += { lib: dep.found() } + xwayland_libs += dep +endforeach + +xwayland_feature_names = [ + 'listenfd', + 'no_touch_pointer_emulation', + 'force_xrandr_emulation', + 'terminate_delay', +] + +internal_config.set_quoted('XWAYLAND_PATH', xwayland.get_variable('xwayland')) +foreach name : xwayland_feature_names + have = xwayland.get_variable('have_' + name, default_value: 'false') == 'true' + internal_config.set10('HAVE_XWAYLAND_' + name.to_upper(), have) +endforeach + +wlr_files += files( + 'selection/dnd.c', + 'selection/incoming.c', + 'selection/outgoing.c', + 'selection/selection.c', + 'server.c', + 'shell.c', + 'sockets.c', + 'xwayland.c', + 'xwm.c', +) +wlr_deps += xwayland_libs +features += { 'xwayland': true } + +have = cc.has_function('xcb_xfixes_set_client_disconnect_mode', dependencies: xwayland_libs) +internal_config.set10('HAVE_XCB_XFIXES_SET_CLIENT_DISCONNECT_MODE', have) diff --git a/xwayland/selection/dnd.c b/xwayland/selection/dnd.c new file mode 100644 index 0000000..1c053e5 --- /dev/null +++ b/xwayland/selection/dnd.c @@ -0,0 +1,338 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "xwayland/xwm.h" +#include "xwayland/selection.h" + +static xcb_atom_t data_device_manager_dnd_action_to_atom( + struct wlr_xwm *xwm, enum wl_data_device_manager_dnd_action action) { + if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) { + return xwm->atoms[DND_ACTION_COPY]; + } else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) { + return xwm->atoms[DND_ACTION_MOVE]; + } else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { + return xwm->atoms[DND_ACTION_ASK]; + } + return XCB_ATOM_NONE; +} + +static enum wl_data_device_manager_dnd_action + data_device_manager_dnd_action_from_atom(struct wlr_xwm *xwm, + enum atom_name atom) { + if (atom == xwm->atoms[DND_ACTION_COPY] || + atom == xwm->atoms[DND_ACTION_PRIVATE]) { + return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } else if (atom == xwm->atoms[DND_ACTION_MOVE]) { + return WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + } else if (atom == xwm->atoms[DND_ACTION_ASK]) { + return WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + } + return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; +} + +static void xwm_dnd_send_event(struct wlr_xwm *xwm, xcb_atom_t type, + xcb_client_message_data_t *data) { + struct wlr_xwayland_surface *dest = xwm->drag_focus; + assert(dest != NULL); + + xcb_client_message_event_t event = { + .response_type = XCB_CLIENT_MESSAGE, + .format = 32, + .sequence = 0, + .window = dest->window_id, + .type = type, + .data = *data, + }; + + xcb_send_event(xwm->xcb_conn, + 0, // propagate + dest->window_id, + XCB_EVENT_MASK_NO_EVENT, + (const char *)&event); + xcb_flush(xwm->xcb_conn); +} + +static void xwm_dnd_send_enter(struct wlr_xwm *xwm) { + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + struct wl_array *mime_types = &drag->source->mime_types; + + xcb_client_message_data_t data = { 0 }; + data.data32[0] = xwm->dnd_selection.window; + data.data32[1] = XDND_VERSION << 24; + + // If we have 3 MIME types or less, we can send them directly in the + // DND_ENTER message + size_t n = mime_types->size / sizeof(char *); + if (n <= 3) { + size_t i = 0; + char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, mime_types) { + char *mime_type = *mime_type_ptr; + data.data32[2+i] = xwm_mime_type_to_atom(xwm, mime_type); + ++i; + } + } else { + // Let the client know that targets are not contained in the message + // data and must be retrieved with the DND_TYPE_LIST property + data.data32[1] |= 1; + + xcb_atom_t targets[n]; + size_t i = 0; + char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, mime_types) { + char *mime_type = *mime_type_ptr; + targets[i] = xwm_mime_type_to_atom(xwm, mime_type); + ++i; + } + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->dnd_selection.window, + xwm->atoms[DND_TYPE_LIST], + XCB_ATOM_ATOM, + 32, // format + n, targets); + } + + xwm_dnd_send_event(xwm, xwm->atoms[DND_ENTER], &data); +} + +static void xwm_dnd_send_position(struct wlr_xwm *xwm, uint32_t time, int16_t x, + int16_t y) { + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + + xcb_client_message_data_t data = { 0 }; + data.data32[0] = xwm->dnd_selection.window; + data.data32[2] = (x << 16) | y; + data.data32[3] = time; + data.data32[4] = + data_device_manager_dnd_action_to_atom(xwm, drag->source->actions); + + xwm_dnd_send_event(xwm, xwm->atoms[DND_POSITION], &data); +} + +static void xwm_dnd_send_drop(struct wlr_xwm *xwm, uint32_t time) { + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + struct wlr_xwayland_surface *dest = xwm->drag_focus; + assert(dest != NULL); + + xcb_client_message_data_t data = { 0 }; + data.data32[0] = xwm->dnd_selection.window; + data.data32[2] = time; + + xwm_dnd_send_event(xwm, xwm->atoms[DND_DROP], &data); +} + +static void xwm_dnd_send_leave(struct wlr_xwm *xwm) { + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + struct wlr_xwayland_surface *dest = xwm->drag_focus; + assert(dest != NULL); + + xcb_client_message_data_t data = { 0 }; + data.data32[0] = xwm->dnd_selection.window; + + xwm_dnd_send_event(xwm, xwm->atoms[DND_LEAVE], &data); +} + +/*static void xwm_dnd_send_finished(struct wlr_xwm *xwm) { + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + struct wlr_xwayland_surface *dest = xwm->drag_focus; + assert(dest != NULL); + + xcb_client_message_data_t data = { 0 }; + data.data32[0] = xwm->dnd_selection.window; + data.data32[1] = drag->source->accepted; + + if (drag->source->accepted) { + data.data32[2] = data_device_manager_dnd_action_to_atom(xwm, + drag->source->current_dnd_action); + } + + xwm_dnd_send_event(xwm, xwm->atoms[DND_FINISHED], &data); +}*/ + +int xwm_handle_selection_client_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + if (ev->type == xwm->atoms[DND_STATUS]) { + if (xwm->drag == NULL) { + wlr_log(WLR_DEBUG, "ignoring XdndStatus client message because " + "there's no drag"); + return 1; + } + + xcb_client_message_data_t *data = &ev->data; + xcb_window_t target_window = data->data32[0]; + bool accepted = data->data32[1] & 1; + xcb_atom_t action_atom = data->data32[4]; + + if (xwm->drag_focus == NULL || + target_window != xwm->drag_focus->window_id) { + wlr_log(WLR_DEBUG, "ignoring XdndStatus client message because " + "it doesn't match the current drag focus window ID"); + return 1; + } + + enum wl_data_device_manager_dnd_action action = + data_device_manager_dnd_action_from_atom(xwm, action_atom); + + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + + drag->source->accepted = accepted; + wlr_data_source_dnd_action(drag->source, action); + + wlr_log(WLR_DEBUG, "DND_STATUS window=%" PRIu32 " accepted=%d action=%d", + target_window, accepted, action); + return 1; + } else if (ev->type == xwm->atoms[DND_FINISHED]) { + // This should only happen after the drag has ended, but before the drag + // source is destroyed + if (xwm->seat == NULL || xwm->seat->drag_source == NULL || + xwm->drag != NULL) { + wlr_log(WLR_DEBUG, "ignoring XdndFinished client message because " + "there's no finished drag"); + return 1; + } + + struct wlr_data_source *source = xwm->seat->drag_source; + + xcb_client_message_data_t *data = &ev->data; + xcb_window_t target_window = data->data32[0]; + bool performed = data->data32[1] & 1; + xcb_atom_t action_atom = data->data32[2]; + + if (xwm->drag_focus == NULL || + target_window != xwm->drag_focus->window_id) { + wlr_log(WLR_DEBUG, "ignoring XdndFinished client message because " + "it doesn't match the finished drag focus window ID"); + return 1; + } + + enum wl_data_device_manager_dnd_action action = + data_device_manager_dnd_action_from_atom(xwm, action_atom); + + if (performed) { + wlr_data_source_dnd_finish(source); + } + + wlr_log(WLR_DEBUG, "DND_FINISH window=%" PRIu32 " performed=%d action=%d", + target_window, performed, action); + return 1; + } else { + return 0; + } +} + +static void seat_handle_drag_focus(struct wl_listener *listener, void *data) { + struct wlr_drag *drag = data; + struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_focus); + + struct wlr_xwayland_surface *focus = NULL; + if (drag->focus != NULL) { + // TODO: check for subsurfaces? + struct wlr_xwayland_surface *surface; + wl_list_for_each(surface, &xwm->surfaces, link) { + if (surface->surface == drag->focus) { + focus = surface; + break; + } + } + } + + if (focus == xwm->drag_focus) { + return; + } + + if (xwm->drag_focus != NULL) { + wlr_data_source_dnd_action(drag->source, + WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE); + xwm_dnd_send_leave(xwm); + } + + xwm->drag_focus = focus; + + if (xwm->drag_focus != NULL) { + xwm_dnd_send_enter(xwm); + } +} + +static void seat_handle_drag_motion(struct wl_listener *listener, void *data) { + struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_motion); + struct wlr_drag_motion_event *event = data; + struct wlr_xwayland_surface *surface = xwm->drag_focus; + + if (surface == NULL) { + return; // No xwayland surface focused + } + + xwm_dnd_send_position(xwm, event->time, surface->x + (int16_t)event->sx, + surface->y + (int16_t)event->sy); +} + +static void seat_handle_drag_drop(struct wl_listener *listener, void *data) { + struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_drop); + struct wlr_drag_drop_event *event = data; + + if (xwm->drag_focus == NULL) { + return; // No xwayland surface focused + } + + wlr_log(WLR_DEBUG, "Wayland drag dropped over an Xwayland window"); + xwm_dnd_send_drop(xwm, event->time); +} + +static void seat_handle_drag_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_destroy); + + // Don't reset drag focus yet because the target will read the drag source + // right after + if (xwm->drag_focus != NULL && !xwm->drag->source->accepted) { + wlr_log(WLR_DEBUG, "Wayland drag cancelled over an Xwayland window"); + xwm_dnd_send_leave(xwm); + } + + wl_list_remove(&xwm->seat_drag_focus.link); + wl_list_remove(&xwm->seat_drag_motion.link); + wl_list_remove(&xwm->seat_drag_drop.link); + wl_list_remove(&xwm->seat_drag_destroy.link); + xwm->drag = NULL; +} + +static void seat_handle_drag_source_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xwm *xwm = + wl_container_of(listener, xwm, seat_drag_source_destroy); + + wl_list_remove(&xwm->seat_drag_source_destroy.link); + xwm->drag_focus = NULL; +} + +void xwm_seat_handle_start_drag(struct wlr_xwm *xwm, struct wlr_drag *drag) { + xwm->drag = drag; + xwm->drag_focus = NULL; + + if (drag != NULL) { + wl_signal_add(&drag->events.focus, &xwm->seat_drag_focus); + xwm->seat_drag_focus.notify = seat_handle_drag_focus; + wl_signal_add(&drag->events.motion, &xwm->seat_drag_motion); + xwm->seat_drag_motion.notify = seat_handle_drag_motion; + wl_signal_add(&drag->events.drop, &xwm->seat_drag_drop); + xwm->seat_drag_drop.notify = seat_handle_drag_drop; + wl_signal_add(&drag->events.destroy, &xwm->seat_drag_destroy); + xwm->seat_drag_destroy.notify = seat_handle_drag_destroy; + + wl_signal_add(&drag->source->events.destroy, + &xwm->seat_drag_source_destroy); + xwm->seat_drag_source_destroy.notify = seat_handle_drag_source_destroy; + } +} diff --git a/xwayland/selection/incoming.c b/xwayland/selection/incoming.c new file mode 100644 index 0000000..4a0b450 --- /dev/null +++ b/xwayland/selection/incoming.c @@ -0,0 +1,539 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xwayland/selection.h" +#include "xwayland/xwm.h" + +static struct wlr_xwm_selection_transfer * +xwm_selection_transfer_create_incoming(struct wlr_xwm_selection *selection) { + struct wlr_xwm_selection_transfer *transfer = calloc(1, sizeof(*transfer)); + if (!transfer) { + return NULL; + } + + xwm_selection_transfer_init(transfer, selection); + + wl_list_insert(&selection->incoming, &transfer->link); + + struct wlr_xwm *xwm = selection->xwm; + transfer->incoming_window = xcb_generate_id(xwm->xcb_conn); + xcb_create_window( + xwm->xcb_conn, + XCB_COPY_FROM_PARENT, + transfer->incoming_window, + xwm->screen->root, + 0, 0, + 10, 10, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + xwm->screen->root_visual, + XCB_CW_EVENT_MASK, (uint32_t[]){ + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE + } + ); + xcb_flush(xwm->xcb_conn); + + return transfer; +} + +struct wlr_xwm_selection_transfer * +xwm_selection_find_incoming_transfer_by_window( + struct wlr_xwm_selection *selection, xcb_window_t window) { + struct wlr_xwm_selection_transfer *transfer; + wl_list_for_each(transfer, &selection->incoming, link) { + if (transfer->incoming_window == window) { + return transfer; + } + } + + return NULL; +} + +static bool xwm_selection_transfer_get_incoming_selection_property( + struct wlr_xwm_selection_transfer *transfer, bool delete) { + struct wlr_xwm *xwm = transfer->selection->xwm; + + xcb_get_property_cookie_t cookie = xcb_get_property( + xwm->xcb_conn, + delete, + transfer->incoming_window, + xwm->atoms[WL_SELECTION], + XCB_GET_PROPERTY_TYPE_ANY, + 0, // offset + 0x1fffffff // length + ); + + transfer->property_start = 0; + transfer->property_reply = + xcb_get_property_reply(xwm->xcb_conn, cookie, NULL); + + if (!transfer->property_reply) { + wlr_log(WLR_ERROR, "cannot get selection property"); + return false; + } + + return true; +} + +static void xwm_notify_ready_for_next_incr_chunk( + struct wlr_xwm_selection_transfer *transfer) { + struct wlr_xwm *xwm = transfer->selection->xwm; + assert(transfer->incr); + + wlr_log(WLR_DEBUG, "deleting property"); + xcb_delete_property(xwm->xcb_conn, transfer->incoming_window, + xwm->atoms[WL_SELECTION]); + xcb_flush(xwm->xcb_conn); + + xwm_selection_transfer_remove_event_source(transfer); + xwm_selection_transfer_destroy_property_reply(transfer); +} + +/** + * Write the X11 selection to a Wayland client. Returns a nonzero value if the + * Wayland client might become writeable again in the future. + */ +static int write_selection_property_to_wl_client(int fd, uint32_t mask, + void *data) { + struct wlr_xwm_selection_transfer *transfer = data; + + char *property = xcb_get_property_value(transfer->property_reply); + int remainder = xcb_get_property_value_length(transfer->property_reply) - + transfer->property_start; + + ssize_t len = write(fd, property + transfer->property_start, remainder); + if (len == -1) { + wlr_log_errno(WLR_ERROR, "write error to target fd %d", fd); + xwm_selection_transfer_destroy(transfer); + return 0; + } + + wlr_log(WLR_DEBUG, + "wrote %zd (total %zd, remaining %d) of %d bytes to fd %d", + len, transfer->property_start + len, remainder, + xcb_get_property_value_length(transfer->property_reply), fd); + + if (len < remainder) { + transfer->property_start += len; + return 1; + } else if (transfer->incr) { + xwm_notify_ready_for_next_incr_chunk(transfer); + } else { + wlr_log(WLR_DEBUG, "transfer complete"); + xwm_selection_transfer_destroy(transfer); + } + + return 0; +} + +static void xwm_write_selection_property_to_wl_client( + struct wlr_xwm_selection_transfer *transfer) { + if (transfer->incr && transfer->wl_client_fd < 0) { + // Wayland client closed its pipe prematurely before the X11 client finished + // its incremental transfer. Continue draining the X11 client. + xwm_notify_ready_for_next_incr_chunk(transfer); + return; + } + + bool wl_client_finished_consuming = !write_selection_property_to_wl_client( + transfer->wl_client_fd, WL_EVENT_WRITABLE, transfer); + if (!wl_client_finished_consuming) { + // Wrote out part of the property to the Wayland client, but the client was + // unable to accept all of it. Schedule an event to asynchronously complete + // the transfer. + struct wl_event_loop *loop = + wl_display_get_event_loop(transfer->selection->xwm->xwayland->wl_display); + transfer->event_source = wl_event_loop_add_fd(loop, + transfer->wl_client_fd, WL_EVENT_WRITABLE, + write_selection_property_to_wl_client, transfer); + } +} + +void xwm_get_incr_chunk(struct wlr_xwm_selection_transfer *transfer) { + wlr_log(WLR_DEBUG, "xwm_get_incr_chunk"); + + if (transfer->property_reply) { + wlr_log(WLR_ERROR, "X11 client offered a new property before we deleted"); + return; + } + + if (!xwm_selection_transfer_get_incoming_selection_property(transfer, false)) { + return; + } + + if (xcb_get_property_value_length(transfer->property_reply) > 0) { + xwm_write_selection_property_to_wl_client(transfer); + } else { + wlr_log(WLR_DEBUG, "incremental transfer complete"); + xwm_selection_transfer_destroy(transfer); + } +} + +static void xwm_selection_transfer_get_data( + struct wlr_xwm_selection_transfer *transfer) { + struct wlr_xwm *xwm = transfer->selection->xwm; + + if (!xwm_selection_transfer_get_incoming_selection_property(transfer, true)) { + return; + } + + if (transfer->property_reply->type == xwm->atoms[INCR]) { + transfer->incr = true; + xwm_selection_transfer_destroy_property_reply(transfer); + } else { + // Reply's ownership is transferred to wm, which is responsible for freeing + // it. + xwm_write_selection_property_to_wl_client(transfer); + } +} + +static void source_send(struct wlr_xwm_selection *selection, + struct wl_array *mime_types, struct wl_array *mime_types_atoms, + const char *requested_mime_type, int fd) { + struct wlr_xwm *xwm = selection->xwm; + + xcb_atom_t *atoms = mime_types_atoms->data; + bool found = false; + xcb_atom_t mime_type_atom; + char **mime_type_ptr; + size_t i = 0; + wl_array_for_each(mime_type_ptr, mime_types) { + char *mime_type = *mime_type_ptr; + if (strcmp(mime_type, requested_mime_type) == 0) { + found = true; + mime_type_atom = atoms[i]; + break; + } + ++i; + } + if (!found) { + wlr_log(WLR_DEBUG, "Cannot send X11 selection to Wayland: " + "unsupported MIME type"); + close(fd); + return; + } + + struct wlr_xwm_selection_transfer *transfer = + xwm_selection_transfer_create_incoming(selection); + if (!transfer) { + wlr_log(WLR_ERROR, "Cannot create transfer"); + close(fd); + return; + } + + xcb_convert_selection(xwm->xcb_conn, + transfer->incoming_window, + selection->atom, + mime_type_atom, + xwm->atoms[WL_SELECTION], + XCB_TIME_CURRENT_TIME); + + xcb_flush(xwm->xcb_conn); + + fcntl(fd, F_SETFL, O_WRONLY | O_NONBLOCK); + transfer->wl_client_fd = fd; +} + +struct x11_data_source { + struct wlr_data_source base; + struct wlr_xwm_selection *selection; + struct wl_array mime_types_atoms; +}; + +static const struct wlr_data_source_impl data_source_impl; + +bool data_source_is_xwayland( + struct wlr_data_source *wlr_source) { + return wlr_source->impl == &data_source_impl; +} + +static struct x11_data_source *data_source_from_wlr_data_source( + struct wlr_data_source *wlr_source) { + assert(data_source_is_xwayland(wlr_source)); + struct x11_data_source *source = wl_container_of(wlr_source, source, base); + return source; +} + +static void data_source_send(struct wlr_data_source *wlr_source, + const char *mime_type, int32_t fd) { + struct x11_data_source *source = + data_source_from_wlr_data_source(wlr_source); + struct wlr_xwm_selection *selection = source->selection; + + source_send(selection, &wlr_source->mime_types, &source->mime_types_atoms, + mime_type, fd); +} + +static void data_source_destroy(struct wlr_data_source *wlr_source) { + struct x11_data_source *source = + data_source_from_wlr_data_source(wlr_source); + wl_array_release(&source->mime_types_atoms); + free(source); +} + +static const struct wlr_data_source_impl data_source_impl = { + .send = data_source_send, + .destroy = data_source_destroy, +}; + +struct x11_primary_selection_source { + struct wlr_primary_selection_source base; + struct wlr_xwm_selection *selection; + struct wl_array mime_types_atoms; +}; + +static const struct wlr_primary_selection_source_impl + primary_selection_source_impl; + +bool primary_selection_source_is_xwayland( + struct wlr_primary_selection_source *wlr_source) { + return wlr_source->impl == &primary_selection_source_impl; +} + +static void primary_selection_source_send( + struct wlr_primary_selection_source *wlr_source, + const char *mime_type, int fd) { + struct x11_primary_selection_source *source = wl_container_of(wlr_source, source, base); + struct wlr_xwm_selection *selection = source->selection; + + source_send(selection, &wlr_source->mime_types, &source->mime_types_atoms, + mime_type, fd); +} + +static void primary_selection_source_destroy( + struct wlr_primary_selection_source *wlr_source) { + struct x11_primary_selection_source *source = wl_container_of(wlr_source, source, base); + wl_array_release(&source->mime_types_atoms); + free(source); +} + +static const struct wlr_primary_selection_source_impl + primary_selection_source_impl = { + .send = primary_selection_source_send, + .destroy = primary_selection_source_destroy, +}; + +static bool source_get_targets(struct wlr_xwm_selection *selection, + struct wl_array *mime_types, struct wl_array *mime_types_atoms) { + struct wlr_xwm *xwm = selection->xwm; + + xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn, + 1, // delete + selection->window, + xwm->atoms[WL_SELECTION], + XCB_GET_PROPERTY_TYPE_ANY, + 0, // offset + 4096 // length + ); + + xcb_get_property_reply_t *reply = + xcb_get_property_reply(xwm->xcb_conn, cookie, NULL); + if (reply == NULL) { + return false; + } + + if (reply->type != XCB_ATOM_ATOM) { + free(reply); + return false; + } + + xcb_atom_t *value = xcb_get_property_value(reply); + for (uint32_t i = 0; i < reply->value_len; i++) { + char *mime_type = NULL; + + if (value[i] == xwm->atoms[UTF8_STRING]) { + mime_type = strdup("text/plain;charset=utf-8"); + } else if (value[i] == xwm->atoms[TEXT]) { + mime_type = strdup("text/plain"); + } else if (value[i] != xwm->atoms[TARGETS] && + value[i] != xwm->atoms[TIMESTAMP]) { + xcb_get_atom_name_cookie_t name_cookie = + xcb_get_atom_name(xwm->xcb_conn, value[i]); + xcb_get_atom_name_reply_t *name_reply = + xcb_get_atom_name_reply(xwm->xcb_conn, name_cookie, NULL); + if (name_reply == NULL) { + continue; + } + size_t len = xcb_get_atom_name_name_length(name_reply); + char *name = xcb_get_atom_name_name(name_reply); // not a C string + if (memchr(name, '/', len) != NULL) { + mime_type = malloc((len + 1) * sizeof(char)); + if (mime_type == NULL) { + free(name_reply); + continue; + } + memcpy(mime_type, name, len); + mime_type[len] = '\0'; + } + free(name_reply); + } + + if (mime_type != NULL) { + char **mime_type_ptr = + wl_array_add(mime_types, sizeof(*mime_type_ptr)); + if (mime_type_ptr == NULL) { + free(mime_type); + break; + } + *mime_type_ptr = mime_type; + + xcb_atom_t *atom_ptr = + wl_array_add(mime_types_atoms, sizeof(*atom_ptr)); + if (atom_ptr == NULL) { + break; + } + *atom_ptr = value[i]; + } + } + + free(reply); + return true; +} + +static void xwm_selection_get_targets(struct wlr_xwm_selection *selection) { + // set the wayland selection to the X11 selection + struct wlr_xwm *xwm = selection->xwm; + + if (selection == &xwm->clipboard_selection) { + struct x11_data_source *source = calloc(1, sizeof(*source)); + if (source == NULL) { + return; + } + wlr_data_source_init(&source->base, &data_source_impl); + + source->selection = selection; + wl_array_init(&source->mime_types_atoms); + + bool ok = source_get_targets(selection, &source->base.mime_types, + &source->mime_types_atoms); + if (ok) { + wlr_seat_request_set_selection(xwm->seat, NULL, &source->base, + wl_display_next_serial(xwm->xwayland->wl_display)); + } else { + wlr_data_source_destroy(&source->base); + } + } else if (selection == &xwm->primary_selection) { + struct x11_primary_selection_source *source = calloc(1, sizeof(*source)); + if (source == NULL) { + return; + } + wlr_primary_selection_source_init(&source->base, + &primary_selection_source_impl); + + source->selection = selection; + wl_array_init(&source->mime_types_atoms); + + bool ok = source_get_targets(selection, &source->base.mime_types, + &source->mime_types_atoms); + if (ok) { + wlr_seat_set_primary_selection(xwm->seat, &source->base, + wl_display_next_serial(xwm->xwayland->wl_display)); + } else { + wlr_primary_selection_source_destroy(&source->base); + } + } else if (selection == &xwm->dnd_selection) { + // TODO + } +} + +void xwm_handle_selection_notify(struct wlr_xwm *xwm, + xcb_selection_notify_event_t *event) { + wlr_log(WLR_DEBUG, "XCB_SELECTION_NOTIFY (selection=%u, property=%u, target=%u)", + event->selection, event->property, + event->target); + + struct wlr_xwm_selection *selection = + xwm_get_selection(xwm, event->selection); + if (selection == NULL) { + return; + } + + struct wlr_xwm_selection_transfer *transfer = + xwm_selection_find_incoming_transfer_by_window(selection, event->requestor); + + if (event->property == XCB_ATOM_NONE) { + if (transfer) { + wlr_log(WLR_ERROR, "convert selection failed"); + xwm_selection_transfer_destroy(transfer); + } + } else if (event->target == xwm->atoms[TARGETS]) { + // No xwayland surface focused, deny access to clipboard + if (xwm->focus_surface == NULL) { + wlr_log(WLR_DEBUG, "denying write access to clipboard: " + "no xwayland surface focused"); + return; + } + + // This sets the Wayland clipboard (by calling wlr_seat_set_selection) + xwm_selection_get_targets(selection); + } else if (transfer) { + xwm_selection_transfer_get_data(transfer); + } +} + +int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm, + xcb_xfixes_selection_notify_event_t *event) { + wlr_log(WLR_DEBUG, "XCB_XFIXES_SELECTION_NOTIFY (selection=%u, owner=%u)", + event->selection, event->owner); + + struct wlr_xwm_selection *selection = + xwm_get_selection(xwm, event->selection); + if (selection == NULL) { + return 0; + } + + if (event->owner == XCB_WINDOW_NONE) { + if (selection->owner != selection->window) { + // A real X client selection went away, not our proxy selection + if (selection == &xwm->clipboard_selection) { + wlr_seat_request_set_selection(xwm->seat, NULL, NULL, + wl_display_next_serial(xwm->xwayland->wl_display)); + } else if (selection == &xwm->primary_selection) { + wlr_seat_request_set_primary_selection(xwm->seat, NULL, NULL, + wl_display_next_serial(xwm->xwayland->wl_display)); + } else if (selection == &xwm->dnd_selection) { + // TODO: DND + } else { + wlr_log(WLR_DEBUG, "X11 selection has been cleared, but cannot " + "clear Wayland selection"); + } + } + + selection->owner = XCB_WINDOW_NONE; + return 1; + } + + if (selection->owner == selection->window && + event->owner != selection->owner) { + wlr_log(WLR_DEBUG, "proxy window lost selection ownership"); + } + + selection->owner = event->owner; + + if (selection->owner == selection->window) { + // We have to use XCB_TIME_CURRENT_TIME when we claim the selection, so + // grab the actual timestamp here so we can answer TIMESTAMP conversion + // requests correctly. + selection->timestamp = event->timestamp; + return 1; + } + + // doing this will give a selection notify where we actually handle the sync + xcb_convert_selection( + xwm->xcb_conn, + selection->window, + selection->atom, + xwm->atoms[TARGETS], + xwm->atoms[WL_SELECTION], + event->timestamp + ); + xcb_flush(xwm->xcb_conn); + + return 1; +} diff --git a/xwayland/selection/outgoing.c b/xwayland/selection/outgoing.c new file mode 100644 index 0000000..d2d0932 --- /dev/null +++ b/xwayland/selection/outgoing.c @@ -0,0 +1,471 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xwayland/selection.h" +#include "xwayland/xwm.h" + +static void xwm_selection_send_notify(struct wlr_xwm *xwm, + xcb_selection_request_event_t *req, bool success) { + xcb_selection_notify_event_t selection_notify = { + .response_type = XCB_SELECTION_NOTIFY, + .sequence = 0, + .time = req->time, + .requestor = req->requestor, + .selection = req->selection, + .target = req->target, + .property = success ? req->property : XCB_ATOM_NONE, + }; + + wlr_log(WLR_DEBUG, "SendEvent destination=%" PRIu32 " SelectionNotify(31) time=%" PRIu32 + " requestor=%" PRIu32 " selection=%" PRIu32 " target=%" PRIu32 " property=%" PRIu32, + req->requestor, req->time, req->requestor, req->selection, req->target, + selection_notify.property); + xcb_send_event(xwm->xcb_conn, + 0, // propagate + req->requestor, + XCB_EVENT_MASK_NO_EVENT, + (const char *)&selection_notify); + xcb_flush(xwm->xcb_conn); +} + +static int xwm_selection_flush_source_data( + struct wlr_xwm_selection_transfer *transfer) { + xcb_change_property(transfer->selection->xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + transfer->request.requestor, + transfer->request.property, + transfer->request.target, + 8, // format + transfer->source_data.size, + transfer->source_data.data); + xcb_flush(transfer->selection->xwm->xcb_conn); + transfer->property_set = true; + size_t length = transfer->source_data.size; + transfer->source_data.size = 0; + return length; +} + +static void xwm_selection_transfer_start_outgoing( + struct wlr_xwm_selection_transfer *transfer); + +void xwm_selection_transfer_destroy_outgoing( + struct wlr_xwm_selection_transfer *transfer) { + wl_list_remove(&transfer->link); + wlr_log(WLR_DEBUG, "Destroying transfer %p", transfer); + + xwm_selection_transfer_remove_event_source(transfer); + xwm_selection_transfer_close_wl_client_fd(transfer); + wl_array_release(&transfer->source_data); + free(transfer); +} + +static int xwm_data_source_read(int fd, uint32_t mask, void *data) { + struct wlr_xwm_selection_transfer *transfer = data; + struct wlr_xwm *xwm = transfer->selection->xwm; + + void *p; + size_t current = transfer->source_data.size; + if (transfer->source_data.size < INCR_CHUNK_SIZE) { + p = wl_array_add(&transfer->source_data, INCR_CHUNK_SIZE); + if (p == NULL) { + wlr_log(WLR_ERROR, "Could not allocate selection source_data"); + goto error_out; + } + } else { + p = (char *)transfer->source_data.data + transfer->source_data.size; + } + + size_t available = transfer->source_data.alloc - current; + ssize_t len = read(fd, p, available); + if (len == -1) { + wlr_log_errno(WLR_ERROR, "read error from data source"); + goto error_out; + } + + wlr_log(WLR_DEBUG, "read %zd bytes (available %zu, mask 0x%x)", len, + available, mask); + + transfer->source_data.size = current + len; + if (transfer->source_data.size >= INCR_CHUNK_SIZE) { + if (!transfer->incr) { + wlr_log(WLR_DEBUG, "got %zu bytes, starting incr", + transfer->source_data.size); + + size_t incr_chunk_size = INCR_CHUNK_SIZE; + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + transfer->request.requestor, + transfer->request.property, + xwm->atoms[INCR], + 32, /* format */ + 1, &incr_chunk_size); + transfer->incr = true; + transfer->property_set = true; + transfer->flush_property_on_delete = true; + xwm_selection_transfer_remove_event_source(transfer); + xwm_selection_send_notify(xwm, &transfer->request, true); + } else if (transfer->property_set) { + wlr_log(WLR_DEBUG, "got %zu bytes, waiting for property delete", + transfer->source_data.size); + + transfer->flush_property_on_delete = true; + xwm_selection_transfer_remove_event_source(transfer); + } else { + wlr_log(WLR_DEBUG, "got %zu bytes, property deleted, setting new " + "property", transfer->source_data.size); + xwm_selection_flush_source_data(transfer); + } + } else if (len == 0 && !transfer->incr) { + wlr_log(WLR_DEBUG, "non-incr transfer complete"); + xwm_selection_flush_source_data(transfer); + xwm_selection_send_notify(xwm, &transfer->request, true); + xwm_selection_transfer_destroy_outgoing(transfer); + } else if (len == 0 && transfer->incr) { + wlr_log(WLR_DEBUG, "incr transfer complete"); + + transfer->flush_property_on_delete = true; + if (transfer->property_set) { + wlr_log(WLR_DEBUG, "got %zu bytes, waiting for property delete", + transfer->source_data.size); + } else { + wlr_log(WLR_DEBUG, "got %zu bytes, property deleted, setting new " + "property", transfer->source_data.size); + xwm_selection_flush_source_data(transfer); + } + xwm_selection_transfer_remove_event_source(transfer); + xwm_selection_transfer_close_wl_client_fd(transfer); + } else { + wlr_log(WLR_DEBUG, "nothing happened, buffered the bytes"); + } + + return 1; + +error_out: + xwm_selection_send_notify(xwm, &transfer->request, false); + xwm_selection_transfer_destroy_outgoing(transfer); + return 0; +} + +void xwm_send_incr_chunk(struct wlr_xwm_selection_transfer *transfer) { + wlr_log(WLR_DEBUG, "property deleted"); + + transfer->property_set = false; + if (transfer->flush_property_on_delete) { + wlr_log(WLR_DEBUG, "setting new property, %zu bytes", + transfer->source_data.size); + transfer->flush_property_on_delete = false; + int length = xwm_selection_flush_source_data(transfer); + + if (transfer->wl_client_fd >= 0) { + xwm_selection_transfer_start_outgoing(transfer); + } else if (length > 0) { + /* Transfer is all done, but queue a flush for + * the delete of the last chunk so we can set + * the 0 sized property to signal the end of + * the transfer. */ + transfer->flush_property_on_delete = true; + wl_array_release(&transfer->source_data); + wl_array_init(&transfer->source_data); + } else { + xwm_selection_transfer_destroy_outgoing(transfer); + } + } +} + +static void xwm_selection_source_send(struct wlr_xwm_selection *selection, + const char *mime_type, int32_t fd) { + if (selection == &selection->xwm->clipboard_selection) { + struct wlr_data_source *source = + selection->xwm->seat->selection_source; + if (source != NULL) { + wlr_data_source_send(source, mime_type, fd); + return; + } + } else if (selection == &selection->xwm->primary_selection) { + struct wlr_primary_selection_source *source = + selection->xwm->seat->primary_selection_source; + if (source != NULL) { + wlr_primary_selection_source_send(source, mime_type, fd); + return; + } + } else if (selection == &selection->xwm->dnd_selection) { + struct wlr_data_source *source = + selection->xwm->seat->drag_source; + if (source != NULL) { + wlr_data_source_send(source, mime_type, fd); + return; + } + } + + wlr_log(WLR_DEBUG, "not sending selection: no selection source available"); +} + +static void xwm_selection_transfer_start_outgoing( + struct wlr_xwm_selection_transfer *transfer) { + struct wlr_xwm *xwm = transfer->selection->xwm; + struct wl_event_loop *loop = + wl_display_get_event_loop(xwm->xwayland->wl_display); + wlr_log(WLR_DEBUG, "Starting transfer %p", transfer); + transfer->event_source = wl_event_loop_add_fd(loop, transfer->wl_client_fd, + WL_EVENT_READABLE, xwm_data_source_read, transfer); +} + +static struct wl_array *xwm_selection_source_get_mime_types( + struct wlr_xwm_selection *selection) { + if (selection == &selection->xwm->clipboard_selection) { + struct wlr_data_source *source = + selection->xwm->seat->selection_source; + if (source != NULL) { + return &source->mime_types; + } + } else if (selection == &selection->xwm->primary_selection) { + struct wlr_primary_selection_source *source = + selection->xwm->seat->primary_selection_source; + if (source != NULL) { + return &source->mime_types; + } + } else if (selection == &selection->xwm->dnd_selection) { + struct wlr_data_source *source = + selection->xwm->seat->drag_source; + if (source != NULL) { + return &source->mime_types; + } + } + return NULL; +} + +/** + * Read the Wayland selection and send it to an Xwayland client. + */ +static bool xwm_selection_send_data(struct wlr_xwm_selection *selection, + xcb_selection_request_event_t *req, const char *mime_type) { + // Check MIME type + struct wl_array *mime_types = + xwm_selection_source_get_mime_types(selection); + if (mime_types == NULL) { + wlr_log(WLR_ERROR, "not sending selection: no MIME type list available"); + return false; + } + + bool found = false; + char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, mime_types) { + char *t = *mime_type_ptr; + if (strcmp(t, mime_type) == 0) { + found = true; + break; + } + } + if (!found) { + wlr_log(WLR_ERROR, "not sending selection: " + "requested an unsupported MIME type %s", mime_type); + return false; + } + + struct wlr_xwm_selection_transfer *transfer = calloc(1, sizeof(*transfer)); + if (transfer == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return false; + } + + xwm_selection_transfer_init(transfer, selection); + transfer->request = *req; + wl_array_init(&transfer->source_data); + + int p[2]; + if (pipe(p) == -1) { + wlr_log_errno(WLR_ERROR, "pipe() failed"); + return false; + } + + fcntl(p[0], F_SETFD, FD_CLOEXEC); + fcntl(p[0], F_SETFL, O_NONBLOCK); + fcntl(p[1], F_SETFD, FD_CLOEXEC); + fcntl(p[1], F_SETFL, O_NONBLOCK); + + transfer->wl_client_fd = p[0]; + + wlr_log(WLR_DEBUG, "Sending Wayland selection %u to Xwayland window with " + "MIME type %s, target %u, transfer %p", req->target, mime_type, + req->target, transfer); + xwm_selection_source_send(selection, mime_type, p[1]); + + // It seems that if we ever try to reply to a selection request after + // another has been sent by the same requestor, the requestor never reads + // from it. It appears to only ever read from the latest, so purge stale + // transfers to prevent clipboard hangs. + struct wlr_xwm_selection_transfer *outgoing, *tmp; + wl_list_for_each_safe(outgoing, tmp, &selection->outgoing, link) { + if (transfer->request.requestor == outgoing->request.requestor) { + wlr_log(WLR_DEBUG, "Destroying stale transfer %p", outgoing); + xwm_selection_send_notify(selection->xwm, &outgoing->request, false); + xwm_selection_transfer_destroy_outgoing(outgoing); + } else { + wlr_log(WLR_DEBUG, "Transfer %p still running", outgoing); + } + } + + wl_list_insert(&selection->outgoing, &transfer->link); + + xwm_selection_transfer_start_outgoing(transfer); + + return true; +} + +static void xwm_selection_send_targets(struct wlr_xwm_selection *selection, + xcb_selection_request_event_t *req) { + struct wlr_xwm *xwm = selection->xwm; + + struct wl_array *mime_types = + xwm_selection_source_get_mime_types(selection); + if (mime_types == NULL) { + wlr_log(WLR_ERROR, "not sending selection targets: " + "no selection source available"); + xwm_selection_send_notify(selection->xwm, req, false); + return; + } + + size_t n = 2 + mime_types->size / sizeof(char *); + xcb_atom_t targets[n]; + targets[0] = xwm->atoms[TIMESTAMP]; + targets[1] = xwm->atoms[TARGETS]; + + size_t i = 0; + char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, mime_types) { + char *mime_type = *mime_type_ptr; + targets[2+i] = xwm_mime_type_to_atom(xwm, mime_type); + ++i; + } + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + req->requestor, + req->property, + XCB_ATOM_ATOM, + 32, // format + n, targets); + + xwm_selection_send_notify(selection->xwm, req, true); +} + +static void xwm_selection_send_timestamp(struct wlr_xwm_selection *selection, + xcb_selection_request_event_t *req) { + xcb_change_property(selection->xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + req->requestor, + req->property, + XCB_ATOM_INTEGER, + 32, // format + 1, &selection->timestamp); + + xwm_selection_send_notify(selection->xwm, req, true); +} + +void xwm_handle_selection_request(struct wlr_xwm *xwm, + xcb_selection_request_event_t *req) { + wlr_log(WLR_DEBUG, "XCB_SELECTION_REQUEST (time=%u owner=%u, requestor=%u " + "selection=%u, target=%u, property=%u)", + req->time, req->owner, req->requestor, req->selection, req->target, + req->property); + + if (req->selection == xwm->atoms[CLIPBOARD_MANAGER]) { + // The wlroots clipboard should already have grabbed the first target, + // so just send selection notify now. This isn't synchronized with the + // clipboard finishing getting the data, so there's a race here. + xwm_selection_send_notify(xwm, req, true); + return; + } + + struct wlr_xwm_selection *selection = + xwm_get_selection(xwm, req->selection); + if (selection == NULL) { + wlr_log(WLR_DEBUG, "received selection request for unknown selection"); + goto fail_notify_requestor; + } + + if (req->requestor == selection->window) { + wlr_log(WLR_ERROR, "selection request should have been caught before"); + goto fail_notify_requestor; + } + + if (selection->window != req->owner) { + if (req->time != XCB_CURRENT_TIME && req->time < selection->timestamp) { + wlr_log(WLR_DEBUG, "ignored old request from timestamp %d; expected > %d", + req->time, selection->timestamp); + goto fail_notify_requestor; + } + + wlr_log(WLR_DEBUG, "received selection request with invalid owner"); + // Don't fail (`goto fail_notify_requestor`) the selection request if we're + // no longer the selection owner. + return; + } + + // No xwayland surface focused, deny access to clipboard + if (xwm->focus_surface == NULL && xwm->drag_focus == NULL) { + if (wlr_log_get_verbosity() >= WLR_DEBUG) { + char *selection_name = xwm_get_atom_name(xwm, selection->atom); + wlr_log(WLR_DEBUG, "denying read access to selection %u (%s): " + "no xwayland surface focused", selection->atom, selection_name); + free(selection_name); + } + goto fail_notify_requestor; + } + + if (req->target == xwm->atoms[TARGETS]) { + xwm_selection_send_targets(selection, req); + } else if (req->target == xwm->atoms[TIMESTAMP]) { + xwm_selection_send_timestamp(selection, req); + } else if (req->target == xwm->atoms[DELETE]) { + xwm_selection_send_notify(selection->xwm, req, true); + } else { + // Send data + char *mime_type = xwm_mime_type_from_atom(xwm, req->target); + if (mime_type == NULL) { + wlr_log(WLR_ERROR, "ignoring selection request: unknown atom %u", + req->target); + goto fail_notify_requestor; + } + + bool send_success = xwm_selection_send_data(selection, req, mime_type); + free(mime_type); + if (!send_success) { + goto fail_notify_requestor; + } + } + + return; + +fail_notify_requestor: + // Something went wrong, and there won't be any data being sent to the + // requestor, so let them know. + xwm_selection_send_notify(xwm, req, false); +} + +void xwm_handle_selection_destroy_notify(struct wlr_xwm *xwm, + xcb_destroy_notify_event_t *event) { + struct wlr_xwm_selection *selections[] = { + &xwm->clipboard_selection, + &xwm->primary_selection, + &xwm->dnd_selection, + }; + + for (size_t i = 0; i < sizeof(selections)/sizeof(selections[0]); ++i) { + struct wlr_xwm_selection *selection = selections[i]; + + struct wlr_xwm_selection_transfer *outgoing, *tmp; + wl_list_for_each_safe(outgoing, tmp, &selection->outgoing, link) { + if (event->window == outgoing->request.requestor) { + xwm_selection_transfer_destroy_outgoing(outgoing); + } + } + } +} diff --git a/xwayland/selection/selection.c b/xwayland/selection/selection.c new file mode 100644 index 0000000..4d1e366 --- /dev/null +++ b/xwayland/selection/selection.c @@ -0,0 +1,345 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "xwayland/selection.h" +#include "xwayland/xwm.h" + +void xwm_selection_transfer_remove_event_source( + struct wlr_xwm_selection_transfer *transfer) { + if (transfer->event_source != NULL) { + wl_event_source_remove(transfer->event_source); + transfer->event_source = NULL; + } +} + +void xwm_selection_transfer_close_wl_client_fd( + struct wlr_xwm_selection_transfer *transfer) { + if (transfer->wl_client_fd >= 0) { + close(transfer->wl_client_fd); + transfer->wl_client_fd = -1; + } +} + +void xwm_selection_transfer_destroy_property_reply( + struct wlr_xwm_selection_transfer *transfer) { + free(transfer->property_reply); + transfer->property_reply = NULL; +} + +void xwm_selection_transfer_init(struct wlr_xwm_selection_transfer *transfer, + struct wlr_xwm_selection *selection) { + *transfer = (struct wlr_xwm_selection_transfer){ + .selection = selection, + .wl_client_fd = -1, + }; +} + +void xwm_selection_transfer_destroy( + struct wlr_xwm_selection_transfer *transfer) { + if (!transfer) { + return; + } + + xwm_selection_transfer_destroy_property_reply(transfer); + xwm_selection_transfer_remove_event_source(transfer); + xwm_selection_transfer_close_wl_client_fd(transfer); + + if (transfer->incoming_window) { + struct wlr_xwm *xwm = transfer->selection->xwm; + xcb_destroy_window(xwm->xcb_conn, transfer->incoming_window); + xcb_flush(xwm->xcb_conn); + } + + wl_list_remove(&transfer->link); + free(transfer); +} + +xcb_atom_t xwm_mime_type_to_atom(struct wlr_xwm *xwm, char *mime_type) { + if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) { + return xwm->atoms[UTF8_STRING]; + } else if (strcmp(mime_type, "text/plain") == 0) { + return xwm->atoms[TEXT]; + } + + xcb_intern_atom_cookie_t cookie = + xcb_intern_atom(xwm->xcb_conn, 0, strlen(mime_type), mime_type); + xcb_intern_atom_reply_t *reply = + xcb_intern_atom_reply(xwm->xcb_conn, cookie, NULL); + if (reply == NULL) { + return XCB_ATOM_NONE; + } + xcb_atom_t atom = reply->atom; + free(reply); + return atom; +} + +char *xwm_mime_type_from_atom(struct wlr_xwm *xwm, xcb_atom_t atom) { + if (atom == xwm->atoms[UTF8_STRING]) { + return strdup("text/plain;charset=utf-8"); + } else if (atom == xwm->atoms[TEXT]) { + return strdup("text/plain"); + } else { + return xwm_get_atom_name(xwm, atom); + } +} + +struct wlr_xwm_selection *xwm_get_selection(struct wlr_xwm *xwm, + xcb_atom_t selection_atom) { + if (selection_atom == xwm->atoms[CLIPBOARD]) { + return &xwm->clipboard_selection; + } else if (selection_atom == xwm->atoms[PRIMARY]) { + return &xwm->primary_selection; + } else if (selection_atom == xwm->atoms[DND_SELECTION]) { + return &xwm->dnd_selection; + } else { + return NULL; + } +} + +static int xwm_handle_selection_property_notify(struct wlr_xwm *xwm, + xcb_property_notify_event_t *event) { + struct wlr_xwm_selection *selections[] = { + &xwm->clipboard_selection, + &xwm->primary_selection, + &xwm->dnd_selection, + }; + + for (size_t i = 0; i < sizeof(selections)/sizeof(selections[0]); ++i) { + struct wlr_xwm_selection *selection = selections[i]; + + if (event->state == XCB_PROPERTY_NEW_VALUE && + event->atom == xwm->atoms[WL_SELECTION]) { + struct wlr_xwm_selection_transfer *transfer = + xwm_selection_find_incoming_transfer_by_window(selection, + event->window); + if (transfer) { + if (transfer->incr) { + xwm_get_incr_chunk(transfer); + } + + return 1; + } + } + + struct wlr_xwm_selection_transfer *outgoing; + wl_list_for_each(outgoing, &selection->outgoing, link) { + if (event->window == outgoing->request.requestor) { + if (event->state == XCB_PROPERTY_DELETE && + event->atom == outgoing->request.property && + outgoing->incr) { + xwm_send_incr_chunk(outgoing); + } + return 1; + } + } + } + + return 0; +} + +int xwm_handle_selection_event(struct wlr_xwm *xwm, + xcb_generic_event_t *event) { + if (xwm->seat == NULL) { + wlr_log(WLR_DEBUG, "not handling selection events: " + "no seat assigned to xwayland"); + return 0; + } + + switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { + case XCB_SELECTION_NOTIFY: + xwm_handle_selection_notify(xwm, (xcb_selection_notify_event_t *)event); + return 1; + case XCB_PROPERTY_NOTIFY: + return xwm_handle_selection_property_notify(xwm, + (xcb_property_notify_event_t *)event); + case XCB_SELECTION_REQUEST: + xwm_handle_selection_request(xwm, + (xcb_selection_request_event_t *)event); + return 1; + } + + switch (event->response_type - xwm->xfixes->first_event) { + case XCB_XFIXES_SELECTION_NOTIFY: + // an X11 window has copied something to the clipboard + return xwm_handle_xfixes_selection_notify(xwm, + (xcb_xfixes_selection_notify_event_t *)event); + } + + return 0; +} + +void xwm_selection_init(struct wlr_xwm_selection *selection, + struct wlr_xwm *xwm, xcb_atom_t atom) { + *selection = (struct wlr_xwm_selection){ + .xwm = xwm, + .atom = atom, + .window = xcb_generate_id(xwm->xcb_conn), + }; + wl_list_init(&selection->incoming); + wl_list_init(&selection->outgoing); + + if (atom == xwm->atoms[DND_SELECTION]) { + xcb_create_window( + xwm->xcb_conn, + XCB_COPY_FROM_PARENT, + selection->window, + xwm->screen->root, + 0, 0, + 8192, 8192, + 0, + XCB_WINDOW_CLASS_INPUT_ONLY, + xwm->screen->root_visual, + XCB_CW_EVENT_MASK, + (uint32_t[]){ + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE + } + ); + + xcb_change_property( + xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + selection->window, + xwm->atoms[DND_AWARE], + XCB_ATOM_ATOM, + 32, // format + 1, + &(uint32_t){XDND_VERSION} + ); + } else { + xcb_create_window( + xwm->xcb_conn, + XCB_COPY_FROM_PARENT, + selection->window, + xwm->screen->root, + 0, 0, + 10, 10, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + xwm->screen->root_visual, + XCB_CW_EVENT_MASK, + (uint32_t[]){ + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE + } + ); + + if (atom == xwm->atoms[CLIPBOARD]) { + xcb_set_selection_owner(xwm->xcb_conn, selection->window, + xwm->atoms[CLIPBOARD_MANAGER], XCB_TIME_CURRENT_TIME); + } else { + assert(atom == xwm->atoms[PRIMARY]); + } + } + + uint32_t mask = + XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE; + xcb_xfixes_select_selection_input(xwm->xcb_conn, selection->window, + selection->atom, mask); +} + +void xwm_selection_finish(struct wlr_xwm_selection *selection) { + if (!selection) { + return; + } + + struct wlr_xwm_selection_transfer *outgoing, *tmp; + wl_list_for_each_safe(outgoing, tmp, &selection->outgoing, link) { + wlr_log(WLR_INFO, "destroyed pending transfer %p", outgoing); + xwm_selection_transfer_destroy_outgoing(outgoing); + } + + struct wlr_xwm_selection_transfer *incoming; + wl_list_for_each_safe(incoming, tmp, &selection->incoming, link) { + xwm_selection_transfer_destroy(incoming); + } + + xcb_destroy_window(selection->xwm->xcb_conn, selection->window); +} + +static void xwm_selection_set_owner(struct wlr_xwm_selection *selection, + bool set) { + if (set) { + xcb_set_selection_owner(selection->xwm->xcb_conn, + selection->window, + selection->atom, + XCB_TIME_CURRENT_TIME); + xcb_flush(selection->xwm->xcb_conn); + } else { + if (selection->owner == selection->window) { + xcb_set_selection_owner(selection->xwm->xcb_conn, + XCB_WINDOW_NONE, + selection->atom, + selection->timestamp); + xcb_flush(selection->xwm->xcb_conn); + } + } +} + +static void handle_seat_set_selection(struct wl_listener *listener, + void *data) { + struct wlr_seat *seat = data; + struct wlr_xwm *xwm = + wl_container_of(listener, xwm, seat_set_selection); + struct wlr_data_source *source = seat->selection_source; + + if (source != NULL && data_source_is_xwayland(source)) { + return; + } + + xwm_selection_set_owner(&xwm->clipboard_selection, source != NULL); +} + +static void handle_seat_set_primary_selection(struct wl_listener *listener, + void *data) { + struct wlr_seat *seat = data; + struct wlr_xwm *xwm = + wl_container_of(listener, xwm, seat_set_primary_selection); + struct wlr_primary_selection_source *source = + seat->primary_selection_source; + + if (source != NULL && primary_selection_source_is_xwayland(source)) { + return; + } + + xwm_selection_set_owner(&xwm->primary_selection, source != NULL); +} + +static void seat_handle_start_drag(struct wl_listener *listener, void *data) { + struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_start_drag); + struct wlr_drag *drag = data; + + xwm_selection_set_owner(&xwm->dnd_selection, drag != NULL); + xwm_seat_handle_start_drag(xwm, drag); +} + +void xwm_set_seat(struct wlr_xwm *xwm, struct wlr_seat *seat) { + if (xwm->seat != NULL) { + wl_list_remove(&xwm->seat_set_selection.link); + wl_list_remove(&xwm->seat_set_primary_selection.link); + wl_list_remove(&xwm->seat_start_drag.link); + xwm->seat = NULL; + } + + if (seat == NULL) { + return; + } + + xwm->seat = seat; + + wl_signal_add(&seat->events.set_selection, &xwm->seat_set_selection); + xwm->seat_set_selection.notify = handle_seat_set_selection; + wl_signal_add(&seat->events.set_primary_selection, + &xwm->seat_set_primary_selection); + xwm->seat_set_primary_selection.notify = handle_seat_set_primary_selection; + wl_signal_add(&seat->events.start_drag, &xwm->seat_start_drag); + xwm->seat_start_drag.notify = seat_handle_start_drag; + + handle_seat_set_selection(&xwm->seat_set_selection, seat); + handle_seat_set_primary_selection(&xwm->seat_set_primary_selection, seat); +} diff --git a/xwayland/server.c b/xwayland/server.c new file mode 100644 index 0000000..4f92879 --- /dev/null +++ b/xwayland/server.c @@ -0,0 +1,502 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "sockets.h" + +static void safe_close(int fd) { + if (fd >= 0) { + close(fd); + } +} + +noreturn static void exec_xwayland(struct wlr_xwayland_server *server, + int notify_fd) { + if (!set_cloexec(server->x_fd[0], false) || + !set_cloexec(server->x_fd[1], false) || + !set_cloexec(server->wl_fd[1], false)) { + wlr_log(WLR_ERROR, "Failed to unset CLOEXEC on FD"); + _exit(EXIT_FAILURE); + } + if (server->options.enable_wm && !set_cloexec(server->wm_fd[1], false)) { + wlr_log(WLR_ERROR, "Failed to unset CLOEXEC on FD"); + _exit(EXIT_FAILURE); + } + + char *argv[64] = {0}; + size_t i = 0; + + char listenfd0[16], listenfd1[16], displayfd[16]; + snprintf(listenfd0, sizeof(listenfd0), "%d", server->x_fd[0]); + snprintf(listenfd1, sizeof(listenfd1), "%d", server->x_fd[1]); + snprintf(displayfd, sizeof(displayfd), "%d", notify_fd); + + argv[i++] = "Xwayland"; + argv[i++] = server->display_name; + argv[i++] = "-rootless"; + argv[i++] = "-core"; + + argv[i++] = "-terminate"; +#if HAVE_XWAYLAND_TERMINATE_DELAY + char terminate_delay[16]; + if (server->options.terminate_delay > 0) { + snprintf(terminate_delay, sizeof(terminate_delay), "%d", + server->options.terminate_delay); + argv[i++] = terminate_delay; + } +#endif + +#if HAVE_XWAYLAND_LISTENFD + argv[i++] = "-listenfd"; + argv[i++] = listenfd0; + argv[i++] = "-listenfd"; + argv[i++] = listenfd1; +#else + argv[i++] = "-listen"; + argv[i++] = listenfd0; + argv[i++] = "-listen"; + argv[i++] = listenfd1; +#endif + argv[i++] = "-displayfd"; + argv[i++] = displayfd; + + char wmfd[16]; + if (server->options.enable_wm) { + snprintf(wmfd, sizeof(wmfd), "%d", server->wm_fd[1]); + argv[i++] = "-wm"; + argv[i++] = wmfd; + } + +#if HAVE_XWAYLAND_NO_TOUCH_POINTER_EMULATION + if (server->options.no_touch_pointer_emulation) { + argv[i++] = "-noTouchPointerEmulation"; + } +#else + server->options.no_touch_pointer_emulation = false; +#endif + +#if HAVE_XWAYLAND_FORCE_XRANDR_EMULATION + if (server->options.force_xrandr_emulation) { + argv[i++] = "-force-xrandr-emulation"; + } +#else + server->options.force_xrandr_emulation = false; +#endif + + argv[i++] = NULL; + + assert(i < sizeof(argv) / sizeof(argv[0])); + + char wayland_socket_str[16]; + snprintf(wayland_socket_str, sizeof(wayland_socket_str), "%d", server->wl_fd[1]); + setenv("WAYLAND_SOCKET", wayland_socket_str, true); + + wlr_log(WLR_INFO, "Starting Xwayland on :%d", server->display); + + // Closes stdout/stderr depending on log verbosity + enum wlr_log_importance verbosity = wlr_log_get_verbosity(); + int devnull = open("/dev/null", O_WRONLY | O_CREAT | O_CLOEXEC, 0666); + if (devnull < 0) { + wlr_log_errno(WLR_ERROR, "XWayland: failed to open /dev/null"); + _exit(EXIT_FAILURE); + } + if (verbosity < WLR_INFO) { + dup2(devnull, STDOUT_FILENO); + } + if (verbosity < WLR_ERROR) { + dup2(devnull, STDERR_FILENO); + } + + const char *xwayland_path = getenv("WLR_XWAYLAND"); + if (xwayland_path) { + wlr_log(WLR_INFO, "Using Xwayland binary '%s' due to WLR_XWAYLAND", + xwayland_path); + } else { + xwayland_path = XWAYLAND_PATH; + } + + // This returns if and only if the call fails + execvp(xwayland_path, argv); + + wlr_log_errno(WLR_ERROR, "failed to exec %s", xwayland_path); + close(devnull); + _exit(EXIT_FAILURE); +} + +static void server_finish_process(struct wlr_xwayland_server *server) { + if (!server || server->display == -1) { + return; + } + + if (server->x_fd_read_event[0]) { + wl_event_source_remove(server->x_fd_read_event[0]); + wl_event_source_remove(server->x_fd_read_event[1]); + + server->x_fd_read_event[0] = server->x_fd_read_event[1] = NULL; + } + + if (server->client) { + wl_list_remove(&server->client_destroy.link); + wl_client_destroy(server->client); + } + if (server->pipe_source) { + wl_event_source_remove(server->pipe_source); + } + + safe_close(server->wl_fd[0]); + safe_close(server->wl_fd[1]); + safe_close(server->wm_fd[0]); + safe_close(server->wm_fd[1]); + memset(server, 0, offsetof(struct wlr_xwayland_server, display)); + server->wl_fd[0] = server->wl_fd[1] = -1; + server->wm_fd[0] = server->wm_fd[1] = -1; + + /* We do not kill the Xwayland process, it dies to broken pipe + * after we close our side of the wm/wl fds. This is more reliable + * than trying to kill something that might no longer be Xwayland. + */ +} + +static void server_finish_display(struct wlr_xwayland_server *server) { + if (!server) { + return; + } + + wl_list_remove(&server->display_destroy.link); + wl_list_init(&server->display_destroy.link); + + if (server->display == -1) { + return; + } + + safe_close(server->x_fd[0]); + safe_close(server->x_fd[1]); + server->x_fd[0] = server->x_fd[1] = -1; + + unlink_display_sockets(server->display); + server->display = -1; + server->display_name[0] = '\0'; +} + +static bool server_start(struct wlr_xwayland_server *server); +static bool server_start_lazy(struct wlr_xwayland_server *server); + +static void handle_client_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwayland_server *server = + wl_container_of(listener, server, client_destroy); + + if (server->pipe_source) { + // Xwayland failed to start, let the readiness handler deal with it + return; + } + + // Don't call client destroy: it's being destroyed already + server->client = NULL; + wl_list_remove(&server->client_destroy.link); + + server_finish_process(server); + + if (time(NULL) - server->server_start > 5) { + if (server->options.lazy) { + wlr_log(WLR_INFO, "Restarting Xwayland (lazy)"); + server_start_lazy(server); + } else { + wlr_log(WLR_INFO, "Restarting Xwayland"); + server_start(server); + } + } +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwayland_server *server = + wl_container_of(listener, server, display_destroy); + + // Don't call client destroy: the display is being destroyed, it's too late + if (server->client) { + server->client = NULL; + wl_list_remove(&server->client_destroy.link); + } + + wlr_xwayland_server_destroy(server); +} + +static int xserver_handle_ready(int fd, uint32_t mask, void *data) { + struct wlr_xwayland_server *server = data; + + if (mask & WL_EVENT_READABLE) { + /* Xwayland writes to the pipe twice, so if we close it too early + * it's possible the second write will fail and Xwayland shuts down. + * Make sure we read until end of line marker to avoid this. + */ + char buf[64]; + ssize_t n = read(fd, buf, sizeof(buf)); + if (n < 0 && errno != EINTR) { + /* Clear mask to signal start failure after reaping child */ + wlr_log_errno(WLR_ERROR, "read from Xwayland display_fd failed"); + mask = 0; + } else if (n <= 0 || buf[n-1] != '\n') { + /* Returning 1 here means recheck and call us again if required. */ + return 1; + } + } + + while (waitpid(server->pid, NULL, 0) < 0) { + if (errno == EINTR) { + continue; + } + wlr_log_errno(WLR_ERROR, "waitpid for Xwayland fork failed"); + goto error; + } + /* Xwayland will only write on the fd once it has finished its + * initial setup. Getting an event here without READABLE means + * the server end failed. + */ + if (!(mask & WL_EVENT_READABLE)) { + assert(mask & WL_EVENT_HANGUP); + wlr_log(WLR_ERROR, "Xwayland startup failed, not setting up xwm"); + goto error; + } + wlr_log(WLR_DEBUG, "Xserver is ready"); + + close(fd); + wl_event_source_remove(server->pipe_source); + server->pipe_source = NULL; + server->ready = true; + + struct wlr_xwayland_server_ready_event event = { + .server = server, + .wm_fd = server->wm_fd[0], + }; + wl_signal_emit_mutable(&server->events.ready, &event); + + /* We removed the source, so don't need recheck */ + return 0; + +error: + /* clean up */ + close(fd); + server_finish_process(server); + server_finish_display(server); + return 0; +} + +static bool server_start_display(struct wlr_xwayland_server *server, + struct wl_display *wl_display) { + server->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(wl_display, &server->display_destroy); + + server->display = open_display_sockets(server->x_fd); + if (server->display < 0) { + server_finish_display(server); + return false; + } + + snprintf(server->display_name, sizeof(server->display_name), + ":%d", server->display); + return true; +} + +static bool server_start(struct wlr_xwayland_server *server) { + if (socketpair(AF_UNIX, SOCK_STREAM, 0, server->wl_fd) != 0) { + wlr_log_errno(WLR_ERROR, "socketpair failed"); + server_finish_process(server); + return false; + } + if (!set_cloexec(server->wl_fd[0], true) || + !set_cloexec(server->wl_fd[1], true)) { + wlr_log(WLR_ERROR, "Failed to set O_CLOEXEC on socket"); + server_finish_process(server); + return false; + } + if (server->options.enable_wm) { + if (socketpair(AF_UNIX, SOCK_STREAM, 0, server->wm_fd) != 0) { + wlr_log_errno(WLR_ERROR, "socketpair failed"); + server_finish_process(server); + return false; + } + if (!set_cloexec(server->wm_fd[0], true) || + !set_cloexec(server->wm_fd[1], true)) { + wlr_log(WLR_ERROR, "Failed to set O_CLOEXEC on socket"); + server_finish_process(server); + return false; + } + } + + server->server_start = time(NULL); + + server->client = wl_client_create(server->wl_display, server->wl_fd[0]); + if (!server->client) { + wlr_log_errno(WLR_ERROR, "wl_client_create failed"); + server_finish_process(server); + return false; + } + + server->wl_fd[0] = -1; /* not ours anymore */ + + server->client_destroy.notify = handle_client_destroy; + wl_client_add_destroy_listener(server->client, &server->client_destroy); + + int notify_fd[2]; + if (pipe(notify_fd) == -1) { + wlr_log_errno(WLR_ERROR, "pipe failed"); + server_finish_process(server); + return false; + } + if (!set_cloexec(notify_fd[0], true)) { + wlr_log(WLR_ERROR, "Failed to set CLOEXEC on FD"); + close(notify_fd[0]); + close(notify_fd[1]); + server_finish_process(server); + return false; + } + + struct wl_event_loop *loop = wl_display_get_event_loop(server->wl_display); + server->pipe_source = wl_event_loop_add_fd(loop, notify_fd[0], + WL_EVENT_READABLE, xserver_handle_ready, server); + + wl_signal_emit_mutable(&server->events.start, NULL); + + server->pid = fork(); + if (server->pid < 0) { + wlr_log_errno(WLR_ERROR, "fork failed"); + close(notify_fd[0]); + close(notify_fd[1]); + server_finish_process(server); + return false; + } else if (server->pid == 0) { + pid_t pid = fork(); + if (pid < 0) { + wlr_log_errno(WLR_ERROR, "second fork failed"); + _exit(EXIT_FAILURE); + } else if (pid == 0) { + exec_xwayland(server, notify_fd[1]); + } + + _exit(EXIT_SUCCESS); + } + + /* close child fds */ + /* remain managing x sockets for lazy start */ + close(notify_fd[1]); + close(server->wl_fd[1]); + safe_close(server->wm_fd[1]); + server->wl_fd[1] = server->wm_fd[1] = -1; + + return true; +} + +static int xwayland_socket_connected(int fd, uint32_t mask, void *data) { + struct wlr_xwayland_server *server = data; + + wl_event_source_remove(server->x_fd_read_event[0]); + wl_event_source_remove(server->x_fd_read_event[1]); + server->x_fd_read_event[0] = server->x_fd_read_event[1] = NULL; + + server_start(server); + + return 0; +} + +static bool server_start_lazy(struct wlr_xwayland_server *server) { + struct wl_event_loop *loop = wl_display_get_event_loop(server->wl_display); + + if (!(server->x_fd_read_event[0] = wl_event_loop_add_fd(loop, server->x_fd[0], + WL_EVENT_READABLE, xwayland_socket_connected, server))) { + return false; + } + + if (!(server->x_fd_read_event[1] = wl_event_loop_add_fd(loop, server->x_fd[1], + WL_EVENT_READABLE, xwayland_socket_connected, server))) { + wl_event_source_remove(server->x_fd_read_event[0]); + server->x_fd_read_event[0] = NULL; + return false; + } + + return true; +} + +static void handle_idle(void *data) { + struct wlr_xwayland_server *server = data; + server->idle_source = NULL; + server_start(server); +} + +void wlr_xwayland_server_destroy(struct wlr_xwayland_server *server) { + if (!server) { + return; + } + + if (server->idle_source != NULL) { + wl_event_source_remove(server->idle_source); + } + server_finish_process(server); + server_finish_display(server); + wl_signal_emit_mutable(&server->events.destroy, NULL); + free(server); +} + +struct wlr_xwayland_server *wlr_xwayland_server_create( + struct wl_display *wl_display, + struct wlr_xwayland_server_options *options) { + if (!getenv("WLR_XWAYLAND") && access(XWAYLAND_PATH, X_OK) != 0) { + wlr_log(WLR_ERROR, "Cannot find Xwayland binary \"%s\"", XWAYLAND_PATH); + return NULL; + } + + struct wlr_xwayland_server *server = calloc(1, sizeof(*server)); + if (!server) { + return NULL; + } + + server->wl_display = wl_display; + server->options = *options; + +#if !HAVE_XWAYLAND_TERMINATE_DELAY + server->options.terminate_delay = 0; +#endif + + server->x_fd[0] = server->x_fd[1] = -1; + server->wl_fd[0] = server->wl_fd[1] = -1; + server->wm_fd[0] = server->wm_fd[1] = -1; + + wl_signal_init(&server->events.start); + wl_signal_init(&server->events.ready); + wl_signal_init(&server->events.destroy); + + if (!server_start_display(server, wl_display)) { + goto error_alloc; + } + + if (server->options.lazy) { + if (!server_start_lazy(server)) { + goto error_display; + } + } else { + struct wl_event_loop *loop = wl_display_get_event_loop(wl_display); + server->idle_source = wl_event_loop_add_idle(loop, handle_idle, server); + if (server->idle_source == NULL) { + goto error_display; + } + } + + return server; + +error_display: + server_finish_display(server); +error_alloc: + free(server); + return NULL; +} diff --git a/xwayland/shell.c b/xwayland/shell.c new file mode 100644 index 0000000..1f7e11a --- /dev/null +++ b/xwayland/shell.c @@ -0,0 +1,237 @@ +#include +#include +#include +#include + +#include "xwayland-shell-v1-protocol.h" + +#define SHELL_VERSION 1 + +static void destroy_resource(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct xwayland_shell_v1_interface shell_impl; +static const struct xwayland_surface_v1_interface xwl_surface_impl; + +static void xwl_surface_destroy(struct wlr_xwayland_surface_v1 *xwl_surface) { + wl_list_remove(&xwl_surface->link); + wl_resource_set_user_data(xwl_surface->resource, NULL); // make inert + free(xwl_surface); +} + +/** + * Get a struct wlr_xwayland_shell_v1 from a resource. + */ +static struct wlr_xwayland_shell_v1 *shell_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &xwayland_shell_v1_interface, &shell_impl)); + return wl_resource_get_user_data(resource); +} + +/** + * Get a struct wlr_xwayland_surface_v1 from a resource. + * + * Returns NULL if the Xwayland surface is inert. + */ +static struct wlr_xwayland_surface_v1 *xwl_surface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &xwayland_surface_v1_interface, &xwl_surface_impl)); + return wl_resource_get_user_data(resource); +} + +static void xwl_surface_role_commit(struct wlr_surface *surface) { + struct wlr_xwayland_surface_v1 *xwl_surface = xwl_surface_from_resource(surface->role_resource); + if (xwl_surface == NULL) { + return; + } + + if (xwl_surface->serial != 0 && !xwl_surface->added) { + xwl_surface->added = true; + wl_signal_emit_mutable(&xwl_surface->shell->events.new_surface, + xwl_surface); + } +} + +static void xwl_surface_role_destroy(struct wlr_surface *surface) { + struct wlr_xwayland_surface_v1 *xwl_surface = xwl_surface_from_resource(surface->role_resource); + if (xwl_surface == NULL) { + return; + } + + xwl_surface_destroy(xwl_surface); +} + +static const struct wlr_surface_role xwl_surface_role = { + .name = "xwayland_surface_v1", + .commit = xwl_surface_role_commit, + .destroy = xwl_surface_role_destroy, +}; + +static void xwl_surface_handle_set_serial(struct wl_client *client, + struct wl_resource *resource, uint32_t serial_lo, uint32_t serial_hi) { + struct wlr_xwayland_surface_v1 *xwl_surface = + xwl_surface_from_resource(resource); + if (xwl_surface == NULL) { + return; + } + + if (xwl_surface->serial != 0) { + wl_resource_post_error(resource, + XWAYLAND_SURFACE_V1_ERROR_ALREADY_ASSOCIATED, + "xwayland_surface_v1 is already associated with another X11 serial"); + return; + } + + xwl_surface->serial = ((uint64_t)serial_hi << 32) | serial_lo; +} + +static const struct xwayland_surface_v1_interface xwl_surface_impl = { + .destroy = destroy_resource, + .set_serial = xwl_surface_handle_set_serial, +}; + +static void shell_handle_get_xwayland_surface(struct wl_client *client, + struct wl_resource *shell_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_xwayland_shell_v1 *shell = shell_from_resource(shell_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_xwayland_surface_v1 *xwl_surface = calloc(1, sizeof(*xwl_surface)); + if (xwl_surface == NULL) { + wl_client_post_no_memory(client); + return; + } + + if (!wlr_surface_set_role(surface, &xwl_surface_role, + shell_resource, XWAYLAND_SHELL_V1_ERROR_ROLE)) { + free(xwl_surface); + return; + } + + xwl_surface->surface = surface; + xwl_surface->shell = shell; + + uint32_t version = wl_resource_get_version(shell_resource); + xwl_surface->resource = wl_resource_create(client, + &xwayland_surface_v1_interface, version, id); + if (xwl_surface->resource == NULL) { + free(xwl_surface); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(xwl_surface->resource, &xwl_surface_impl, + xwl_surface, NULL); + + wl_list_insert(&shell->surfaces, &xwl_surface->link); + + wlr_surface_set_role_object(surface, xwl_surface->resource); +} + +static const struct xwayland_shell_v1_interface shell_impl = { + .destroy = destroy_resource, + .get_xwayland_surface = shell_handle_get_xwayland_surface, +}; + +static void shell_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_xwayland_shell_v1 *shell = data; + + if (client != shell->client) { + wl_client_post_implementation_error(client, + "Permission denied to bind to %s", xwayland_shell_v1_interface.name); + return; + } + + struct wl_resource *resource = wl_resource_create(client, + &xwayland_shell_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &shell_impl, shell, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwayland_shell_v1 *shell = + wl_container_of(listener, shell, display_destroy); + wlr_xwayland_shell_v1_destroy(shell); +} + +struct wlr_xwayland_shell_v1 *wlr_xwayland_shell_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= SHELL_VERSION); + + struct wlr_xwayland_shell_v1 *shell = calloc(1, sizeof(*shell)); + if (shell == NULL) { + return NULL; + } + + shell->global = wl_global_create(display, &xwayland_shell_v1_interface, + version, shell, shell_bind); + if (shell->global == NULL) { + free(shell); + return NULL; + } + + wl_list_init(&shell->surfaces); + wl_signal_init(&shell->events.new_surface); + wl_signal_init(&shell->events.destroy); + + shell->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &shell->display_destroy); + + wl_list_init(&shell->client_destroy.link); + + return shell; +} + +void wlr_xwayland_shell_v1_destroy(struct wlr_xwayland_shell_v1 *shell) { + if (shell == NULL) { + return; + } + + wl_signal_emit_mutable(&shell->events.destroy, NULL); + + struct wlr_xwayland_surface_v1 *xwl_surface, *tmp; + wl_list_for_each_safe(xwl_surface, tmp, &shell->surfaces, link) { + xwl_surface_destroy(xwl_surface); + } + + wl_list_remove(&shell->display_destroy.link); + wl_list_remove(&shell->client_destroy.link); + wl_global_destroy(shell->global); + free(shell); +} + +static void shell_handle_client_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwayland_shell_v1 *shell = + wl_container_of(listener, shell, client_destroy); + wlr_xwayland_shell_v1_set_client(shell, NULL); +} + +void wlr_xwayland_shell_v1_set_client(struct wlr_xwayland_shell_v1 *shell, + struct wl_client *client) { + wl_list_remove(&shell->client_destroy.link); + shell->client = client; + if (client != NULL) { + shell->client_destroy.notify = shell_handle_client_destroy; + wl_client_add_destroy_listener(client, &shell->client_destroy); + } else { + wl_list_init(&shell->client_destroy.link); + } +} + +struct wlr_surface *wlr_xwayland_shell_v1_surface_from_serial( + struct wlr_xwayland_shell_v1 *shell, uint64_t serial) { + struct wlr_xwayland_surface_v1 *xwl_surface; + wl_list_for_each(xwl_surface, &shell->surfaces, link) { + if (xwl_surface->serial == serial) { + return xwl_surface->surface; + } + } + return NULL; +} diff --git a/xwayland/sockets.c b/xwayland/sockets.c new file mode 100644 index 0000000..9e287f3 --- /dev/null +++ b/xwayland/sockets.c @@ -0,0 +1,222 @@ +#undef _POSIX_C_SOURCE +#define _XOPEN_SOURCE 700 // for S_ISVTX +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sockets.h" + +static const char lock_fmt[] = "/tmp/.X%d-lock"; +static const char socket_dir[] = "/tmp/.X11-unix"; +static const char socket_fmt[] = "/tmp/.X11-unix/X%d"; +#ifndef __linux__ +static const char socket_fmt2[] = "/tmp/.X11-unix/X%d_"; +#endif + +bool set_cloexec(int fd, bool cloexec) { + int flags = fcntl(fd, F_GETFD); + if (flags == -1) { + wlr_log_errno(WLR_ERROR, "fcntl failed"); + return false; + } + if (cloexec) { + flags = flags | FD_CLOEXEC; + } else { + flags = flags & ~FD_CLOEXEC; + } + if (fcntl(fd, F_SETFD, flags) == -1) { + wlr_log_errno(WLR_ERROR, "fcntl failed"); + return false; + } + return true; +} + +static int open_socket(struct sockaddr_un *addr, size_t path_size) { + int fd, rc; + socklen_t size = offsetof(struct sockaddr_un, sun_path) + path_size + 1; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to create socket %c%s", + addr->sun_path[0] ? addr->sun_path[0] : '@', + addr->sun_path + 1); + return -1; + } + if (!set_cloexec(fd, true)) { + close(fd); + return -1; + } + + if (addr->sun_path[0]) { + unlink(addr->sun_path); + } + if (bind(fd, (struct sockaddr*)addr, size) < 0) { + rc = errno; + wlr_log_errno(WLR_ERROR, "Failed to bind socket %c%s", + addr->sun_path[0] ? addr->sun_path[0] : '@', + addr->sun_path + 1); + goto cleanup; + } + if (listen(fd, 1) < 0) { + rc = errno; + wlr_log_errno(WLR_ERROR, "Failed to listen to socket %c%s", + addr->sun_path[0] ? addr->sun_path[0] : '@', + addr->sun_path + 1); + goto cleanup; + } + + return fd; + +cleanup: + close(fd); + if (addr->sun_path[0]) { + unlink(addr->sun_path); + } + errno = rc; + return -1; +} + +static bool check_socket_dir(void) { + struct stat buf; + + if (lstat(socket_dir, &buf)) { + wlr_log_errno(WLR_ERROR, "Failed to stat %s", socket_dir); + return false; + } + if (!(buf.st_mode & S_IFDIR)) { + wlr_log(WLR_ERROR, "%s is not a directory", socket_dir); + return false; + } + if (!((buf.st_uid == 0) || (buf.st_uid == getuid()))) { + wlr_log(WLR_ERROR, "%s not owned by root or us", socket_dir); + return false; + } + if (!(buf.st_mode & S_ISVTX)) { + /* we can deal with no sticky bit... */ + if ((buf.st_mode & (S_IWGRP | S_IWOTH))) { + /* but not if other users can mess with our sockets */ + wlr_log(WLR_ERROR, "sticky bit not set on %s", socket_dir); + return false; + } + } + return true; +} + +static bool open_sockets(int socks[2], int display) { + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + size_t path_size; + + if (mkdir(socket_dir, 0755) == 0) { + wlr_log(WLR_INFO, "Created %s ourselves -- other users will " + "be unable to create X11 UNIX sockets of their own", + socket_dir); + } else if (errno != EEXIST) { + wlr_log_errno(WLR_ERROR, "Unable to mkdir %s", socket_dir); + return false; + } else if (!check_socket_dir()) { + return false; + } + +#ifdef __linux__ + addr.sun_path[0] = 0; + path_size = snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, socket_fmt, display); +#else + path_size = snprintf(addr.sun_path, sizeof(addr.sun_path), socket_fmt2, display); +#endif + socks[0] = open_socket(&addr, path_size); + if (socks[0] < 0) { + return false; + } + + path_size = snprintf(addr.sun_path, sizeof(addr.sun_path), socket_fmt, display); + socks[1] = open_socket(&addr, path_size); + if (socks[1] < 0) { + close(socks[0]); + socks[0] = -1; + return false; + } + + return true; +} + +void unlink_display_sockets(int display) { + char sun_path[64]; + + snprintf(sun_path, sizeof(sun_path), socket_fmt, display); + unlink(sun_path); + +#ifndef __linux__ + snprintf(sun_path, sizeof(sun_path), socket_fmt2, display); + unlink(sun_path); +#endif + + snprintf(sun_path, sizeof(sun_path), lock_fmt, display); + unlink(sun_path); +} + +int open_display_sockets(int socks[2]) { + int lock_fd, display; + char lock_name[64]; + + for (display = 0; display <= 32; display++) { + snprintf(lock_name, sizeof(lock_name), lock_fmt, display); + if ((lock_fd = open(lock_name, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0444)) >= 0) { + if (!open_sockets(socks, display)) { + unlink(lock_name); + close(lock_fd); + continue; + } + char pid[12]; + snprintf(pid, sizeof(pid), "%10d", getpid()); + if (write(lock_fd, pid, sizeof(pid) - 1) != sizeof(pid) - 1) { + unlink(lock_name); + close(lock_fd); + continue; + } + close(lock_fd); + break; + } + + if ((lock_fd = open(lock_name, O_RDONLY | O_CLOEXEC)) < 0) { + continue; + } + + char pid[12] = { 0 }, *end_pid; + ssize_t bytes = read(lock_fd, pid, sizeof(pid) - 1); + close(lock_fd); + + if (bytes != sizeof(pid) - 1) { + continue; + } + long int read_pid; + read_pid = strtol(pid, &end_pid, 10); + if (read_pid < 0 || read_pid > INT32_MAX || end_pid != pid + sizeof(pid) - 2) { + continue; + } + errno = 0; + if (kill((pid_t)read_pid, 0) != 0 && errno == ESRCH) { + if (unlink(lock_name) != 0) { + continue; + } + // retry + display--; + continue; + } + } + + if (display > 32) { + wlr_log(WLR_ERROR, "No display available in the first 33"); + return -1; + } + + return display; +} diff --git a/xwayland/sockets.h b/xwayland/sockets.h new file mode 100644 index 0000000..4c55a08 --- /dev/null +++ b/xwayland/sockets.h @@ -0,0 +1,10 @@ +#ifndef XWAYLAND_SOCKETS_H +#define XWAYLAND_SOCKETS_H + +#include + +bool set_cloexec(int fd, bool cloexec); +void unlink_display_sockets(int display); +int open_display_sockets(int socks[2]); + +#endif diff --git a/xwayland/xwayland.c b/xwayland/xwayland.c new file mode 100644 index 0000000..c4ca9ae --- /dev/null +++ b/xwayland/xwayland.c @@ -0,0 +1,220 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sockets.h" +#include "xwayland/xwm.h" + +struct wlr_xwayland_cursor { + uint8_t *pixels; + uint32_t stride; + uint32_t width; + uint32_t height; + int32_t hotspot_x; + int32_t hotspot_y; +}; + +static void handle_server_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwayland *xwayland = + wl_container_of(listener, xwayland, server_destroy); + // Server is being destroyed so avoid destroying it once again. + xwayland->server = NULL; + wlr_xwayland_destroy(xwayland); +} + +static void handle_server_start(struct wl_listener *listener, void *data) { + struct wlr_xwayland *xwayland = + wl_container_of(listener, xwayland, server_start); + if (xwayland->shell_v1 != NULL) { + wlr_xwayland_shell_v1_set_client(xwayland->shell_v1, xwayland->server->client); + } +} + +static void xwayland_mark_ready(struct wlr_xwayland *xwayland) { + assert(xwayland->server->wm_fd[0] >= 0); + xwayland->xwm = xwm_create(xwayland, xwayland->server->wm_fd[0]); + if (!xwayland->xwm) { + return; + } + + if (xwayland->seat) { + xwm_set_seat(xwayland->xwm, xwayland->seat); + } + + if (xwayland->cursor != NULL) { + struct wlr_xwayland_cursor *cur = xwayland->cursor; + xwm_set_cursor(xwayland->xwm, cur->pixels, cur->stride, cur->width, + cur->height, cur->hotspot_x, cur->hotspot_y); + } + + wl_signal_emit_mutable(&xwayland->events.ready, NULL); +} + +static void handle_server_ready(struct wl_listener *listener, void *data) { + struct wlr_xwayland *xwayland = + wl_container_of(listener, xwayland, server_ready); + xwayland_mark_ready(xwayland); +} + +static void handle_shell_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwayland *xwayland = + wl_container_of(listener, xwayland, shell_destroy); + xwayland->shell_v1 = NULL; +} + +void wlr_xwayland_destroy(struct wlr_xwayland *xwayland) { + if (!xwayland) { + return; + } + + wl_list_remove(&xwayland->server_destroy.link); + wl_list_remove(&xwayland->server_start.link); + wl_list_remove(&xwayland->server_ready.link); + wl_list_remove(&xwayland->shell_destroy.link); + free(xwayland->cursor); + + wlr_xwayland_set_seat(xwayland, NULL); + if (xwayland->own_server) { + wlr_xwayland_server_destroy(xwayland->server); + } + xwayland->server = NULL; + wlr_xwayland_shell_v1_destroy(xwayland->shell_v1); + free(xwayland); +} + +struct wlr_xwayland *wlr_xwayland_create_with_server(struct wl_display *wl_display, + struct wlr_compositor *compositor, struct wlr_xwayland_server *server) { + struct wlr_xwayland *xwayland = calloc(1, sizeof(*xwayland)); + if (!xwayland) { + return NULL; + } + + xwayland->wl_display = wl_display; + xwayland->compositor = compositor; + + wl_signal_init(&xwayland->events.new_surface); + wl_signal_init(&xwayland->events.ready); + wl_signal_init(&xwayland->events.remove_startup_info); + + xwayland->server = server; + xwayland->display_name = xwayland->server->display_name; + + xwayland->server_destroy.notify = handle_server_destroy; + wl_signal_add(&xwayland->server->events.destroy, &xwayland->server_destroy); + + xwayland->server_start.notify = handle_server_start; + wl_signal_add(&xwayland->server->events.start, &xwayland->server_start); + + xwayland->server_ready.notify = handle_server_ready; + wl_signal_add(&xwayland->server->events.ready, &xwayland->server_ready); + + wl_list_init(&xwayland->shell_destroy.link); + + if (server->ready) { + xwayland_mark_ready(xwayland); + } + + return xwayland; +} + +struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display, + struct wlr_compositor *compositor, bool lazy) { + struct wlr_xwayland_shell_v1 *shell_v1 = wlr_xwayland_shell_v1_create(wl_display, 1); + if (shell_v1 == NULL) { + return NULL; + } + + struct wlr_xwayland_server_options options = { + .lazy = lazy, + .enable_wm = true, +#if HAVE_XCB_XFIXES_SET_CLIENT_DISCONNECT_MODE + .terminate_delay = lazy ? 10 : 0, +#endif + }; + struct wlr_xwayland_server *server = wlr_xwayland_server_create(wl_display, &options); + if (server == NULL) { + goto error_shell_v1; + } + + struct wlr_xwayland *xwayland = wlr_xwayland_create_with_server(wl_display, compositor, server); + if (xwayland == NULL) { + goto error_server; + } + + xwayland->shell_v1 = shell_v1; + xwayland->own_server = true; + + xwayland->shell_destroy.notify = handle_shell_destroy; + wl_signal_add(&xwayland->shell_v1->events.destroy, &xwayland->shell_destroy); + + return xwayland; + +error_server: + wlr_xwayland_server_destroy(server); +error_shell_v1: + wlr_xwayland_shell_v1_destroy(shell_v1); + return NULL; +} + +void wlr_xwayland_set_cursor(struct wlr_xwayland *xwayland, + uint8_t *pixels, uint32_t stride, uint32_t width, uint32_t height, + int32_t hotspot_x, int32_t hotspot_y) { + if (xwayland->xwm != NULL) { + xwm_set_cursor(xwayland->xwm, pixels, stride, width, height, + hotspot_x, hotspot_y); + return; + } + + free(xwayland->cursor); + + xwayland->cursor = calloc(1, sizeof(*xwayland->cursor)); + if (xwayland->cursor == NULL) { + return; + } + xwayland->cursor->pixels = pixels; + xwayland->cursor->stride = stride; + xwayland->cursor->width = width; + xwayland->cursor->height = height; + xwayland->cursor->hotspot_x = hotspot_x; + xwayland->cursor->hotspot_y = hotspot_y; +} + +static void xwayland_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xwayland *xwayland = + wl_container_of(listener, xwayland, seat_destroy); + + wlr_xwayland_set_seat(xwayland, NULL); +} + +void wlr_xwayland_set_seat(struct wlr_xwayland *xwayland, + struct wlr_seat *seat) { + if (xwayland->seat) { + wl_list_remove(&xwayland->seat_destroy.link); + } + + xwayland->seat = seat; + + if (xwayland->xwm) { + xwm_set_seat(xwayland->xwm, seat); + } + + if (seat == NULL) { + return; + } + + xwayland->seat_destroy.notify = xwayland_handle_seat_destroy; + wl_signal_add(&seat->events.destroy, &xwayland->seat_destroy); +} diff --git a/xwayland/xwm.c b/xwayland/xwm.c new file mode 100644 index 0000000..0aa3333 --- /dev/null +++ b/xwayland/xwm.c @@ -0,0 +1,2387 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xwayland/xwm.h" + +static const char *const atom_map[ATOM_LAST] = { + [WL_SURFACE_ID] = "WL_SURFACE_ID", + [WL_SURFACE_SERIAL] = "WL_SURFACE_SERIAL", + [WM_DELETE_WINDOW] = "WM_DELETE_WINDOW", + [WM_PROTOCOLS] = "WM_PROTOCOLS", + [WM_HINTS] = "WM_HINTS", + [WM_NORMAL_HINTS] = "WM_NORMAL_HINTS", + [WM_SIZE_HINTS] = "WM_SIZE_HINTS", + [WM_WINDOW_ROLE] = "WM_WINDOW_ROLE", + [MOTIF_WM_HINTS] = "_MOTIF_WM_HINTS", + [UTF8_STRING] = "UTF8_STRING", + [WM_S0] = "WM_S0", + [NET_SUPPORTED] = "_NET_SUPPORTED", + [NET_WM_CM_S0] = "_NET_WM_CM_S0", + [NET_WM_PID] = "_NET_WM_PID", + [NET_WM_NAME] = "_NET_WM_NAME", + [NET_WM_STATE] = "_NET_WM_STATE", + [NET_WM_STRUT_PARTIAL] = "_NET_WM_STRUT_PARTIAL", + [NET_WM_WINDOW_TYPE] = "_NET_WM_WINDOW_TYPE", + [WM_TAKE_FOCUS] = "WM_TAKE_FOCUS", + [WINDOW] = "WINDOW", + [NET_ACTIVE_WINDOW] = "_NET_ACTIVE_WINDOW", + [NET_WM_MOVERESIZE] = "_NET_WM_MOVERESIZE", + [NET_SUPPORTING_WM_CHECK] = "_NET_SUPPORTING_WM_CHECK", + [NET_WM_STATE_FOCUSED] = "_NET_WM_STATE_FOCUSED", + [NET_WM_STATE_MODAL] = "_NET_WM_STATE_MODAL", + [NET_WM_STATE_FULLSCREEN] = "_NET_WM_STATE_FULLSCREEN", + [NET_WM_STATE_MAXIMIZED_VERT] = "_NET_WM_STATE_MAXIMIZED_VERT", + [NET_WM_STATE_MAXIMIZED_HORZ] = "_NET_WM_STATE_MAXIMIZED_HORZ", + [NET_WM_STATE_HIDDEN] = "_NET_WM_STATE_HIDDEN", + [NET_WM_PING] = "_NET_WM_PING", + [WM_CHANGE_STATE] = "WM_CHANGE_STATE", + [WM_STATE] = "WM_STATE", + [CLIPBOARD] = "CLIPBOARD", + [PRIMARY] = "PRIMARY", + [WL_SELECTION] = "_WL_SELECTION", + [TARGETS] = "TARGETS", + [CLIPBOARD_MANAGER] = "CLIPBOARD_MANAGER", + [INCR] = "INCR", + [TEXT] = "TEXT", + [TIMESTAMP] = "TIMESTAMP", + [DELETE] = "DELETE", + [NET_STARTUP_ID] = "_NET_STARTUP_ID", + [NET_STARTUP_INFO] = "_NET_STARTUP_INFO", + [NET_STARTUP_INFO_BEGIN] = "_NET_STARTUP_INFO_BEGIN", + [NET_WM_WINDOW_TYPE_NORMAL] = "_NET_WM_WINDOW_TYPE_NORMAL", + [NET_WM_WINDOW_TYPE_UTILITY] = "_NET_WM_WINDOW_TYPE_UTILITY", + [NET_WM_WINDOW_TYPE_TOOLTIP] = "_NET_WM_WINDOW_TYPE_TOOLTIP", + [NET_WM_WINDOW_TYPE_DND] = "_NET_WM_WINDOW_TYPE_DND", + [NET_WM_WINDOW_TYPE_DROPDOWN_MENU] = "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", + [NET_WM_WINDOW_TYPE_POPUP_MENU] = "_NET_WM_WINDOW_TYPE_POPUP_MENU", + [NET_WM_WINDOW_TYPE_COMBO] = "_NET_WM_WINDOW_TYPE_COMBO", + [NET_WM_WINDOW_TYPE_MENU] = "_NET_WM_WINDOW_TYPE_MENU", + [NET_WM_WINDOW_TYPE_NOTIFICATION] = "_NET_WM_WINDOW_TYPE_NOTIFICATION", + [NET_WM_WINDOW_TYPE_SPLASH] = "_NET_WM_WINDOW_TYPE_SPLASH", + [NET_WM_WINDOW_TYPE_DESKTOP] = "_NET_WM_WINDOW_TYPE_DESKTOP", + [DND_SELECTION] = "XdndSelection", + [DND_AWARE] = "XdndAware", + [DND_STATUS] = "XdndStatus", + [DND_POSITION] = "XdndPosition", + [DND_ENTER] = "XdndEnter", + [DND_LEAVE] = "XdndLeave", + [DND_DROP] = "XdndDrop", + [DND_FINISHED] = "XdndFinished", + [DND_PROXY] = "XdndProxy", + [DND_TYPE_LIST] = "XdndTypeList", + [DND_ACTION_MOVE] = "XdndActionMove", + [DND_ACTION_COPY] = "XdndActionCopy", + [DND_ACTION_ASK] = "XdndActionAsk", + [DND_ACTION_PRIVATE] = "XdndActionPrivate", + [NET_CLIENT_LIST] = "_NET_CLIENT_LIST", + [NET_CLIENT_LIST_STACKING] = "_NET_CLIENT_LIST_STACKING", + [NET_WORKAREA] = "_NET_WORKAREA", +}; + +#define STARTUP_INFO_REMOVE_PREFIX "remove: ID=" +struct pending_startup_id { + char *msg; + size_t len; + xcb_window_t window; + struct wl_list link; +}; + +static const struct wlr_addon_interface surface_addon_impl; + +struct wlr_xwayland_surface *wlr_xwayland_surface_try_from_wlr_surface( + struct wlr_surface *surface) { + struct wlr_addon *addon = wlr_addon_find(&surface->addons, NULL, &surface_addon_impl); + if (addon == NULL) { + return NULL; + } + struct wlr_xwayland_surface *xsurface = wl_container_of(addon, xsurface, surface_addon); + return xsurface; +} + +// TODO: replace this with hash table? +static struct wlr_xwayland_surface *lookup_surface(struct wlr_xwm *xwm, + xcb_window_t window_id) { + struct wlr_xwayland_surface *surface; + wl_list_for_each(surface, &xwm->surfaces, link) { + if (surface->window_id == window_id) { + return surface; + } + } + return NULL; +} + +static int xwayland_surface_handle_ping_timeout(void *data) { + struct wlr_xwayland_surface *surface = data; + + wl_signal_emit_mutable(&surface->events.ping_timeout, NULL); + surface->pinging = false; + return 1; +} + +static void read_surface_client_id(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_res_query_client_ids_cookie_t cookie) { + xcb_res_query_client_ids_reply_t *reply = xcb_res_query_client_ids_reply( + xwm->xcb_conn, cookie, NULL); + if (reply == NULL) { + return; + } + + uint32_t *pid = NULL; + xcb_res_client_id_value_iterator_t iter = + xcb_res_query_client_ids_ids_iterator(reply); + while (iter.rem > 0) { + if (iter.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID && + xcb_res_client_id_value_value_length(iter.data) > 0) { + pid = xcb_res_client_id_value_value(iter.data); + break; + } + xcb_res_client_id_value_next(&iter); + } + if (pid == NULL) { + free(reply); + return; + } + xsurface->pid = *pid; + free(reply); +} + +static struct wlr_xwayland_surface *xwayland_surface_create( + struct wlr_xwm *xwm, xcb_window_t window_id, int16_t x, int16_t y, + uint16_t width, uint16_t height, bool override_redirect) { + struct wlr_xwayland_surface *surface = calloc(1, sizeof(*surface)); + if (!surface) { + wlr_log(WLR_ERROR, "Could not allocate wlr xwayland surface"); + return NULL; + } + + xcb_get_geometry_cookie_t geometry_cookie = + xcb_get_geometry(xwm->xcb_conn, window_id); + + xcb_res_query_client_ids_cookie_t client_id_cookie = { 0 }; + if (xwm->xres) { + xcb_res_client_id_spec_t spec = { + .client = window_id, + .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID + }; + client_id_cookie = xcb_res_query_client_ids(xwm->xcb_conn, 1, &spec); + } + + uint32_t values[1]; + values[0] = + XCB_EVENT_MASK_FOCUS_CHANGE | + XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_change_window_attributes(xwm->xcb_conn, window_id, + XCB_CW_EVENT_MASK, values); + + surface->xwm = xwm; + surface->window_id = window_id; + surface->x = x; + surface->y = y; + surface->width = width; + surface->height = height; + surface->override_redirect = override_redirect; + wl_list_init(&surface->children); + wl_list_init(&surface->stack_link); + wl_list_init(&surface->parent_link); + wl_list_init(&surface->unpaired_link); + wl_signal_init(&surface->events.destroy); + wl_signal_init(&surface->events.request_configure); + wl_signal_init(&surface->events.request_move); + wl_signal_init(&surface->events.request_resize); + wl_signal_init(&surface->events.request_minimize); + wl_signal_init(&surface->events.request_maximize); + wl_signal_init(&surface->events.request_fullscreen); + wl_signal_init(&surface->events.request_activate); + wl_signal_init(&surface->events.associate); + wl_signal_init(&surface->events.dissociate); + wl_signal_init(&surface->events.set_class); + wl_signal_init(&surface->events.set_role); + wl_signal_init(&surface->events.set_title); + wl_signal_init(&surface->events.set_parent); + wl_signal_init(&surface->events.set_startup_id); + wl_signal_init(&surface->events.set_window_type); + wl_signal_init(&surface->events.set_hints); + wl_signal_init(&surface->events.set_decorations); + wl_signal_init(&surface->events.set_strut_partial); + wl_signal_init(&surface->events.set_override_redirect); + wl_signal_init(&surface->events.set_geometry); + wl_signal_init(&surface->events.map_request); + wl_signal_init(&surface->events.ping_timeout); + + xcb_get_geometry_reply_t *geometry_reply = + xcb_get_geometry_reply(xwm->xcb_conn, geometry_cookie, NULL); + if (geometry_reply != NULL) { + surface->has_alpha = geometry_reply->depth == 32; + } + free(geometry_reply); + + struct wl_display *display = xwm->xwayland->wl_display; + struct wl_event_loop *loop = wl_display_get_event_loop(display); + surface->ping_timer = wl_event_loop_add_timer(loop, + xwayland_surface_handle_ping_timeout, surface); + if (surface->ping_timer == NULL) { + free(surface); + wlr_log(WLR_ERROR, "Could not add timer to event loop"); + return NULL; + } + + wl_list_insert(&xwm->surfaces, &surface->link); + + if (xwm->xres) { + read_surface_client_id(xwm, surface, client_id_cookie); + } + + wl_signal_emit_mutable(&xwm->xwayland->events.new_surface, surface); + + return surface; +} + +static void xwm_set_net_active_window(struct wlr_xwm *xwm, + xcb_window_t window) { + xcb_change_property(xwm->xcb_conn, XCB_PROP_MODE_REPLACE, + xwm->screen->root, xwm->atoms[NET_ACTIVE_WINDOW], + xwm->atoms[WINDOW], 32, 1, &window); +} + +static void xwm_send_wm_message(struct wlr_xwayland_surface *surface, + xcb_client_message_data_t *data, uint32_t event_mask) { + struct wlr_xwm *xwm = surface->xwm; + + xcb_client_message_event_t event = { + .response_type = XCB_CLIENT_MESSAGE, + .format = 32, + .sequence = 0, + .window = surface->window_id, + .type = xwm->atoms[WM_PROTOCOLS], + .data = *data, + }; + + xcb_send_event(xwm->xcb_conn, + 0, // propagate + surface->window_id, + event_mask, + (const char *)&event); + xcb_flush(xwm->xcb_conn); +} + +static void xwm_set_net_client_list(struct wlr_xwm *xwm) { + // FIXME: _NET_CLIENT_LIST is expected to be ordered by map time, but the + // order of surfaces in `xwm->surfaces` is by creation time. The order of + // windows _NET_CLIENT_LIST exposed by wlroots is wrong. + + size_t mapped_surfaces = 0; + struct wlr_xwayland_surface *surface; + wl_list_for_each(surface, &xwm->surfaces, link) { + if (surface->surface != NULL && surface->surface->mapped) { + mapped_surfaces++; + } + } + + xcb_window_t *windows = NULL; + if (mapped_surfaces > 0) { + windows = malloc(sizeof(*windows) * mapped_surfaces); + if (!windows) { + return; + } + + size_t index = 0; + wl_list_for_each(surface, &xwm->surfaces, link) { + if (surface->surface != NULL && surface->surface->mapped) { + windows[index++] = surface->window_id; + } + } + } + + xcb_change_property(xwm->xcb_conn, XCB_PROP_MODE_REPLACE, + xwm->screen->root, xwm->atoms[NET_CLIENT_LIST], + XCB_ATOM_WINDOW, 32, mapped_surfaces, windows); + free(windows); +} + +static void xwm_set_net_client_list_stacking(struct wlr_xwm *xwm) { + size_t num_surfaces = wl_list_length(&xwm->surfaces_in_stack_order); + xcb_window_t *windows = malloc(sizeof(xcb_window_t) * num_surfaces); + if (!windows) { + return; + } + + size_t i = 0; + struct wlr_xwayland_surface *xsurface; + wl_list_for_each(xsurface, &xwm->surfaces_in_stack_order, stack_link) { + windows[i++] = xsurface->window_id; + } + + xcb_change_property(xwm->xcb_conn, XCB_PROP_MODE_REPLACE, xwm->screen->root, + xwm->atoms[NET_CLIENT_LIST_STACKING], XCB_ATOM_WINDOW, 32, num_surfaces, + windows); + free(windows); +} + +static void xsurface_set_net_wm_state(struct wlr_xwayland_surface *xsurface); + +static void xwm_set_focus_window(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface) { + struct wlr_xwayland_surface *unfocus_surface = xwm->focus_surface; + + // We handle cases where focus_surface == xsurface because we + // want to be able to deny FocusIn events. + xwm->focus_surface = xsurface; + + if (unfocus_surface) { + xsurface_set_net_wm_state(unfocus_surface); + } + + if (!xsurface) { + xcb_set_input_focus_checked(xwm->xcb_conn, + XCB_INPUT_FOCUS_POINTER_ROOT, + XCB_NONE, XCB_CURRENT_TIME); + return; + } + + if (xsurface->override_redirect) { + return; + } + + xcb_client_message_data_t message_data = { 0 }; + message_data.data32[0] = xwm->atoms[WM_TAKE_FOCUS]; + message_data.data32[1] = XCB_TIME_CURRENT_TIME; + + if (xsurface->hints && !xsurface->hints->input) { + // if the surface doesn't allow the focus request, we will send him + // only the take focus event. It will get the focus by itself. + xwm_send_wm_message(xsurface, &message_data, XCB_EVENT_MASK_NO_EVENT); + } else { + xwm_send_wm_message(xsurface, &message_data, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT); + + xcb_void_cookie_t cookie = xcb_set_input_focus(xwm->xcb_conn, + XCB_INPUT_FOCUS_POINTER_ROOT, xsurface->window_id, XCB_CURRENT_TIME); + xwm->last_focus_seq = cookie.sequence; + } + + xsurface_set_net_wm_state(xsurface); +} + +static void xwm_surface_activate(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface) { + if (xwm->focus_surface == xsurface || + (xsurface && xsurface->override_redirect)) { + return; + } + + if (xsurface) { + xwm_set_net_active_window(xwm, xsurface->window_id); + } else { + xwm_set_net_active_window(xwm, XCB_WINDOW_NONE); + } + + xwm_set_focus_window(xwm, xsurface); + + xcb_flush(xwm->xcb_conn); +} + +static void xsurface_set_net_wm_state(struct wlr_xwayland_surface *xsurface) { + struct wlr_xwm *xwm = xsurface->xwm; + + // EWMH says _NET_WM_STATE should be unset if the window is withdrawn + if (xsurface->withdrawn) { + xcb_delete_property(xwm->xcb_conn, + xsurface->window_id, + xwm->atoms[NET_WM_STATE]); + return; + } + + uint32_t property[6]; + size_t i = 0; + if (xsurface->modal) { + property[i++] = xwm->atoms[NET_WM_STATE_MODAL]; + } + if (xsurface->fullscreen) { + property[i++] = xwm->atoms[NET_WM_STATE_FULLSCREEN]; + } + if (xsurface->maximized_vert) { + property[i++] = xwm->atoms[NET_WM_STATE_MAXIMIZED_VERT]; + } + if (xsurface->maximized_horz) { + property[i++] = xwm->atoms[NET_WM_STATE_MAXIMIZED_HORZ]; + } + if (xsurface->minimized) { + property[i++] = xwm->atoms[NET_WM_STATE_HIDDEN]; + } + if (xsurface == xwm->focus_surface) { + property[i++] = xwm->atoms[NET_WM_STATE_FOCUSED]; + } + assert(i <= sizeof(property) / sizeof(property[0])); + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xsurface->window_id, + xwm->atoms[NET_WM_STATE], + XCB_ATOM_ATOM, + 32, // format + i, property); +} + +static void xwayland_surface_dissociate(struct wlr_xwayland_surface *xsurface) { + if (xsurface->surface != NULL) { + wlr_surface_unmap(xsurface->surface); + wl_signal_emit_mutable(&xsurface->events.dissociate, NULL); + + wl_list_remove(&xsurface->surface_commit.link); + wl_list_remove(&xsurface->surface_map.link); + wl_list_remove(&xsurface->surface_unmap.link); + wlr_addon_finish(&xsurface->surface_addon); + xsurface->surface = NULL; + } + + // Make sure we're not on the unpaired surface list or we + // could be assigned a surface during surface creation that + // was mapped before this unmap request. + wl_list_remove(&xsurface->unpaired_link); + wl_list_init(&xsurface->unpaired_link); + xsurface->surface_id = 0; + xsurface->serial = 0; + + wl_list_remove(&xsurface->stack_link); + wl_list_init(&xsurface->stack_link); + xwm_set_net_client_list_stacking(xsurface->xwm); +} + +static void xwayland_surface_destroy(struct wlr_xwayland_surface *xsurface) { + xwayland_surface_dissociate(xsurface); + + wl_signal_emit_mutable(&xsurface->events.destroy, NULL); + + if (xsurface == xsurface->xwm->focus_surface) { + xwm_surface_activate(xsurface->xwm, NULL); + } + + wl_list_remove(&xsurface->link); + wl_list_remove(&xsurface->parent_link); + + struct wlr_xwayland_surface *child, *next; + wl_list_for_each_safe(child, next, &xsurface->children, parent_link) { + wl_list_remove(&child->parent_link); + wl_list_init(&child->parent_link); + child->parent = NULL; + } + + wl_list_remove(&xsurface->unpaired_link); + + wl_event_source_remove(xsurface->ping_timer); + + free(xsurface->title); + free(xsurface->class); + free(xsurface->instance); + free(xsurface->role); + free(xsurface->window_type); + free(xsurface->protocols); + free(xsurface->startup_id); + free(xsurface->hints); + free(xsurface->size_hints); + free(xsurface->strut_partial); + free(xsurface); +} + +static void read_surface_class(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *surface, xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_STRING && + reply->type != xwm->atoms[UTF8_STRING]) { + return; + } + + size_t len = xcb_get_property_value_length(reply); + char *class = xcb_get_property_value(reply); + + // Unpack two sequentially stored strings: instance, class + size_t instance_len = strnlen(class, len); + free(surface->instance); + if (len > 0 && instance_len < len) { + surface->instance = strndup(class, instance_len); + class += instance_len + 1; + } else { + surface->instance = NULL; + } + free(surface->class); + if (len > 0) { + surface->class = strndup(class, len); + } else { + surface->class = NULL; + } + + wl_signal_emit_mutable(&surface->events.set_class, NULL); +} + +static void read_surface_startup_id(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_STRING && + reply->type != xwm->atoms[UTF8_STRING]) { + return; + } + + size_t len = xcb_get_property_value_length(reply); + char *startup_id = xcb_get_property_value(reply); + + free(xsurface->startup_id); + if (len > 0) { + xsurface->startup_id = strndup(startup_id, len); + } else { + xsurface->startup_id = NULL; + } + + wlr_log(WLR_DEBUG, "XCB_ATOM_NET_STARTUP_ID: %s", + xsurface->startup_id ? xsurface->startup_id: "(null)"); + wl_signal_emit_mutable(&xsurface->events.set_startup_id, NULL); +} + +static void read_surface_role(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_STRING && + reply->type != xwm->atoms[UTF8_STRING]) { + return; + } + + size_t len = xcb_get_property_value_length(reply); + char *role = xcb_get_property_value(reply); + + free(xsurface->role); + if (len > 0) { + xsurface->role = strndup(role, len); + } else { + xsurface->role = NULL; + } + + wl_signal_emit_mutable(&xsurface->events.set_role, NULL); +} + +static void read_surface_title(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_STRING && + reply->type != xwm->atoms[UTF8_STRING]) { + return; + } + + bool is_utf8 = reply->type == xwm->atoms[UTF8_STRING]; + if (!is_utf8 && xsurface->has_utf8_title) { + return; + } + + size_t len = xcb_get_property_value_length(reply); + char *title = xcb_get_property_value(reply); + + free(xsurface->title); + if (len > 0) { + xsurface->title = strndup(title, len); + } else { + xsurface->title = NULL; + } + xsurface->has_utf8_title = is_utf8; + + wl_signal_emit_mutable(&xsurface->events.set_title, NULL); +} + +static bool has_parent(struct wlr_xwayland_surface *parent, + struct wlr_xwayland_surface *child) { + while (parent) { + if (child == parent) { + return true; + } + + parent = parent->parent; + } + + return false; +} + +static void read_surface_parent(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + struct wlr_xwayland_surface *found_parent = NULL; + if (reply->type != XCB_ATOM_WINDOW) { + return; + } + + xcb_window_t *xid = xcb_get_property_value(reply); + if (xid != NULL) { + found_parent = lookup_surface(xwm, *xid); + if (!has_parent(found_parent, xsurface)) { + xsurface->parent = found_parent; + } else { + wlr_log(WLR_INFO, "%p with %p would create a loop", xsurface, + found_parent); + } + } else { + xsurface->parent = NULL; + } + + + wl_list_remove(&xsurface->parent_link); + if (xsurface->parent != NULL) { + wl_list_insert(&xsurface->parent->children, &xsurface->parent_link); + } else { + wl_list_init(&xsurface->parent_link); + } + + wl_signal_emit_mutable(&xsurface->events.set_parent, NULL); +} + +static void read_surface_window_type(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_ATOM) { + return; + } + + xcb_atom_t *atoms = xcb_get_property_value(reply); + size_t atoms_len = reply->value_len; + size_t atoms_size = sizeof(xcb_atom_t) * atoms_len; + + free(xsurface->window_type); + xsurface->window_type = malloc(atoms_size); + if (xsurface->window_type == NULL) { + return; + } + memcpy(xsurface->window_type, atoms, atoms_size); + xsurface->window_type_len = atoms_len; + + wl_signal_emit_mutable(&xsurface->events.set_window_type, NULL); +} + +static void read_surface_protocols(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_ATOM) { + return; + } + + xcb_atom_t *atoms = xcb_get_property_value(reply); + size_t atoms_len = reply->value_len; + size_t atoms_size = sizeof(xcb_atom_t) * atoms_len; + + free(xsurface->protocols); + xsurface->protocols = malloc(atoms_size); + if (xsurface->protocols == NULL) { + return; + } + memcpy(xsurface->protocols, atoms, atoms_size); + xsurface->protocols_len = atoms_len; +} + +static void read_surface_hints(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + // According to the docs, reply->type == xwm->atoms[WM_HINTS] + // In practice, reply->type == XCB_ATOM_ATOM + if (reply->value_len == 0) { + return; + } + + free(xsurface->hints); + xsurface->hints = calloc(1, sizeof(*xsurface->hints)); + if (xsurface->hints == NULL) { + return; + } + xcb_icccm_get_wm_hints_from_reply(xsurface->hints, reply); + + if (!(xsurface->hints->flags & XCB_ICCCM_WM_HINT_INPUT)) { + // The client didn't specify whether it wants input. + // Assume it does. + xsurface->hints->input = true; + } + + wl_signal_emit_mutable(&xsurface->events.set_hints, NULL); +} + +static void read_surface_normal_hints(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != xwm->atoms[WM_SIZE_HINTS] || reply->value_len == 0) { + return; + } + + free(xsurface->size_hints); + xsurface->size_hints = calloc(1, sizeof(*xsurface->size_hints)); + if (xsurface->size_hints == NULL) { + return; + } + xcb_icccm_get_wm_size_hints_from_reply(xsurface->size_hints, reply); + + int32_t flags = xsurface->size_hints->flags; + bool has_min_size_hints = (flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) != 0; + bool has_base_size_hints = (flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) != 0; + /* ICCCM says that if absent, min size is equal to base size and vice versa */ + if (!has_min_size_hints && !has_base_size_hints) { + xsurface->size_hints->min_width = -1; + xsurface->size_hints->min_height = -1; + xsurface->size_hints->base_width = -1; + xsurface->size_hints->base_height = -1; + } else if (!has_base_size_hints) { + xsurface->size_hints->base_width = xsurface->size_hints->min_width; + xsurface->size_hints->base_height = xsurface->size_hints->min_height; + } else if (!has_min_size_hints) { + xsurface->size_hints->min_width = xsurface->size_hints->base_width; + xsurface->size_hints->min_height = xsurface->size_hints->base_height; + } + + if ((flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) == 0) { + xsurface->size_hints->max_width = -1; + xsurface->size_hints->max_height = -1; + } +} + +#define MWM_HINTS_FLAGS_FIELD 0 +#define MWM_HINTS_DECORATIONS_FIELD 2 + +#define MWM_HINTS_DECORATIONS (1 << 1) + +#define MWM_DECOR_ALL (1 << 0) +#define MWM_DECOR_BORDER (1 << 1) +#define MWM_DECOR_TITLE (1 << 3) + +static void read_surface_motif_hints(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->value_len < 5) { + return; + } + + uint32_t *motif_hints = xcb_get_property_value(reply); + if (motif_hints[MWM_HINTS_FLAGS_FIELD] & MWM_HINTS_DECORATIONS) { + xsurface->decorations = WLR_XWAYLAND_SURFACE_DECORATIONS_ALL; + uint32_t decorations = motif_hints[MWM_HINTS_DECORATIONS_FIELD]; + if ((decorations & MWM_DECOR_ALL) == 0) { + if ((decorations & MWM_DECOR_BORDER) == 0) { + xsurface->decorations |= + WLR_XWAYLAND_SURFACE_DECORATIONS_NO_BORDER; + } + if ((decorations & MWM_DECOR_TITLE) == 0) { + xsurface->decorations |= + WLR_XWAYLAND_SURFACE_DECORATIONS_NO_TITLE; + } + } + wl_signal_emit_mutable(&xsurface->events.set_decorations, NULL); + } +} + +static void read_surface_strut_partial(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_CARDINAL || reply->format != 32 || + xcb_get_property_value_length(reply) != + sizeof(xcb_ewmh_wm_strut_partial_t)) { + return; + } + + free(xsurface->strut_partial); + xsurface->strut_partial = calloc(1, sizeof(*xsurface->strut_partial)); + if (xsurface->strut_partial == NULL) { + return; + } + xcb_ewmh_get_wm_strut_partial_from_reply(xsurface->strut_partial, reply); + wl_signal_emit_mutable(&xsurface->events.set_strut_partial, NULL); +} + +static void read_surface_net_wm_state(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + xsurface->fullscreen = 0; + xcb_atom_t *atom = xcb_get_property_value(reply); + for (uint32_t i = 0; i < reply->value_len; i++) { + if (atom[i] == xwm->atoms[NET_WM_STATE_MODAL]) { + xsurface->modal = true; + } else if (atom[i] == xwm->atoms[NET_WM_STATE_FULLSCREEN]) { + xsurface->fullscreen = true; + } else if (atom[i] == xwm->atoms[NET_WM_STATE_MAXIMIZED_VERT]) { + xsurface->maximized_vert = true; + } else if (atom[i] == xwm->atoms[NET_WM_STATE_MAXIMIZED_HORZ]) { + xsurface->maximized_horz = true; + } else if (atom[i] == xwm->atoms[NET_WM_STATE_HIDDEN]) { + xsurface->minimized = true; + } + } +} + +char *xwm_get_atom_name(struct wlr_xwm *xwm, xcb_atom_t atom) { + xcb_get_atom_name_cookie_t name_cookie = + xcb_get_atom_name(xwm->xcb_conn, atom); + xcb_get_atom_name_reply_t *name_reply = + xcb_get_atom_name_reply(xwm->xcb_conn, name_cookie, NULL); + if (name_reply == NULL) { + return NULL; + } + size_t len = xcb_get_atom_name_name_length(name_reply); + char *buf = xcb_get_atom_name_name(name_reply); // not a C string + char *name = strndup(buf, len); + free(name_reply); + return name; +} + +static void read_surface_property(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, xcb_atom_t property, + xcb_get_property_reply_t *reply) { + if (property == XCB_ATOM_WM_CLASS) { + read_surface_class(xwm, xsurface, reply); + } else if (property == XCB_ATOM_WM_NAME || + property == xwm->atoms[NET_WM_NAME]) { + read_surface_title(xwm, xsurface, reply); + } else if (property == XCB_ATOM_WM_TRANSIENT_FOR) { + read_surface_parent(xwm, xsurface, reply); + } else if (property == xwm->atoms[NET_WM_PID]) { + // intentionally ignored + } else if (property == xwm->atoms[NET_WM_WINDOW_TYPE]) { + read_surface_window_type(xwm, xsurface, reply); + } else if (property == xwm->atoms[WM_PROTOCOLS]) { + read_surface_protocols(xwm, xsurface, reply); + } else if (property == xwm->atoms[NET_WM_STATE]) { + read_surface_net_wm_state(xwm, xsurface, reply); + } else if (property == xwm->atoms[WM_HINTS]) { + read_surface_hints(xwm, xsurface, reply); + } else if (property == xwm->atoms[WM_NORMAL_HINTS]) { + read_surface_normal_hints(xwm, xsurface, reply); + } else if (property == xwm->atoms[MOTIF_WM_HINTS]) { + read_surface_motif_hints(xwm, xsurface, reply); + } else if (property == xwm->atoms[NET_WM_STRUT_PARTIAL]) { + read_surface_strut_partial(xwm, xsurface, reply); + } else if (property == xwm->atoms[WM_WINDOW_ROLE]) { + read_surface_role(xwm, xsurface, reply); + } else if (property == xwm->atoms[NET_STARTUP_ID]) { + read_surface_startup_id(xwm, xsurface, reply); + } else if (wlr_log_get_verbosity() >= WLR_DEBUG) { + char *prop_name = xwm_get_atom_name(xwm, property); + wlr_log(WLR_DEBUG, "unhandled X11 property %" PRIu32 " (%s) for window %" PRIu32, + property, prop_name ? prop_name : "(null)", xsurface->window_id); + free(prop_name); + } +} + +static void xwayland_surface_handle_commit(struct wl_listener *listener, void *data) { + struct wlr_xwayland_surface *xsurface = wl_container_of(listener, xsurface, surface_commit); + if (wlr_surface_has_buffer(xsurface->surface)) { + wlr_surface_map(xsurface->surface); + } +} + +static void xwayland_surface_handle_map(struct wl_listener *listener, void *data) { + struct wlr_xwayland_surface *xsurface = wl_container_of(listener, xsurface, surface_map); + xwm_set_net_client_list(xsurface->xwm); +} + +static void xwayland_surface_handle_unmap(struct wl_listener *listener, void *data) { + struct wlr_xwayland_surface *xsurface = wl_container_of(listener, xsurface, surface_unmap); + xwm_set_net_client_list(xsurface->xwm); +} + +static void xwayland_surface_handle_addon_destroy(struct wlr_addon *addon) { + struct wlr_xwayland_surface *xsurface = wl_container_of(addon, xsurface, surface_addon); + xwayland_surface_dissociate(xsurface); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "wlr_xwayland_surface", + .destroy = xwayland_surface_handle_addon_destroy, +}; + +static void xwayland_surface_associate(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, struct wlr_surface *surface) { + assert(xsurface->surface == NULL); + + wl_list_remove(&xsurface->unpaired_link); + wl_list_init(&xsurface->unpaired_link); + xsurface->surface_id = 0; + + xsurface->surface = surface; + wlr_addon_init(&xsurface->surface_addon, &surface->addons, NULL, &surface_addon_impl); + + xsurface->surface_commit.notify = xwayland_surface_handle_commit; + wl_signal_add(&surface->events.commit, &xsurface->surface_commit); + + xsurface->surface_map.notify = xwayland_surface_handle_map; + wl_signal_add(&surface->events.map, &xsurface->surface_map); + + xsurface->surface_unmap.notify = xwayland_surface_handle_unmap; + wl_signal_add(&surface->events.unmap, &xsurface->surface_unmap); + + // read all surface properties + const xcb_atom_t props[] = { + XCB_ATOM_WM_CLASS, + XCB_ATOM_WM_NAME, + XCB_ATOM_WM_TRANSIENT_FOR, + xwm->atoms[WM_PROTOCOLS], + xwm->atoms[WM_HINTS], + xwm->atoms[WM_NORMAL_HINTS], + xwm->atoms[MOTIF_WM_HINTS], + xwm->atoms[NET_STARTUP_ID], + xwm->atoms[NET_WM_STATE], + xwm->atoms[NET_WM_STRUT_PARTIAL], + xwm->atoms[NET_WM_WINDOW_TYPE], + xwm->atoms[NET_WM_NAME], + }; + + xcb_get_property_cookie_t cookies[sizeof(props) / sizeof(props[0])] = {0}; + for (size_t i = 0; i < sizeof(props) / sizeof(props[0]); i++) { + cookies[i] = xcb_get_property(xwm->xcb_conn, 0, xsurface->window_id, + props[i], XCB_ATOM_ANY, 0, 2048); + } + + for (size_t i = 0; i < sizeof(props) / sizeof(props[0]); i++) { + xcb_get_property_reply_t *reply = + xcb_get_property_reply(xwm->xcb_conn, cookies[i], NULL); + if (reply == NULL) { + wlr_log(WLR_ERROR, "Failed to get window property"); + continue; + } + read_surface_property(xwm, xsurface, props[i], reply); + free(reply); + } + + wl_signal_emit_mutable(&xsurface->events.associate, NULL); +} + +static void xwm_handle_create_notify(struct wlr_xwm *xwm, + xcb_create_notify_event_t *ev) { + if (ev->window == xwm->window || + ev->window == xwm->primary_selection.window || + ev->window == xwm->clipboard_selection.window || + ev->window == xwm->dnd_selection.window) { + return; + } + + xwayland_surface_create(xwm, ev->window, ev->x, ev->y, + ev->width, ev->height, ev->override_redirect); +} + +static void xwm_handle_destroy_notify(struct wlr_xwm *xwm, + xcb_destroy_notify_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (xsurface == NULL) { + return; + } + xwayland_surface_destroy(xsurface); + xwm_handle_selection_destroy_notify(xwm, ev); +} + +static void xwm_handle_configure_request(struct wlr_xwm *xwm, + xcb_configure_request_event_t *ev) { + struct wlr_xwayland_surface *surface = lookup_surface(xwm, ev->window); + if (surface == NULL) { + return; + } + + // TODO: handle ev->{parent,sibling}? + + uint16_t mask = ev->value_mask; + uint16_t geo_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + if ((mask & geo_mask) == 0) { + return; + } + + struct wlr_xwayland_surface_configure_event wlr_event = { + .surface = surface, + .x = mask & XCB_CONFIG_WINDOW_X ? ev->x : surface->x, + .y = mask & XCB_CONFIG_WINDOW_Y ? ev->y : surface->y, + .width = mask & XCB_CONFIG_WINDOW_WIDTH ? ev->width : surface->width, + .height = mask & XCB_CONFIG_WINDOW_HEIGHT ? ev->height : surface->height, + .mask = mask, + }; + + wl_signal_emit_mutable(&surface->events.request_configure, &wlr_event); +} + +static void xwm_update_override_redirect(struct wlr_xwayland_surface *xsurface, + bool override_redirect) { + if (xsurface->override_redirect == override_redirect) { + return; + } + xsurface->override_redirect = override_redirect; + + if (override_redirect) { + wl_list_remove(&xsurface->stack_link); + wl_list_init(&xsurface->stack_link); + xwm_set_net_client_list_stacking(xsurface->xwm); + } else if (xsurface->surface != NULL && xsurface->surface->mapped) { + wlr_xwayland_surface_restack(xsurface, NULL, XCB_STACK_MODE_BELOW); + } + + wl_signal_emit_mutable(&xsurface->events.set_override_redirect, NULL); +} + +static void xwm_handle_configure_notify(struct wlr_xwm *xwm, + xcb_configure_notify_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (!xsurface) { + return; + } + + bool geometry_changed = + (xsurface->x != ev->x || xsurface->y != ev->y || + xsurface->width != ev->width || xsurface->height != ev->height); + + if (geometry_changed) { + xsurface->x = ev->x; + xsurface->y = ev->y; + xsurface->width = ev->width; + xsurface->height = ev->height; + } + + xwm_update_override_redirect(xsurface, ev->override_redirect); + + if (geometry_changed) { + wl_signal_emit_mutable(&xsurface->events.set_geometry, NULL); + } +} + +static void xsurface_set_wm_state(struct wlr_xwayland_surface *xsurface) { + struct wlr_xwm *xwm = xsurface->xwm; + uint32_t property[] = { XCB_ICCCM_WM_STATE_NORMAL, XCB_WINDOW_NONE }; + + if (xsurface->withdrawn) { + property[0] = XCB_ICCCM_WM_STATE_WITHDRAWN; + } else if (xsurface->minimized) { + property[0] = XCB_ICCCM_WM_STATE_ICONIC; + } else { + property[0] = XCB_ICCCM_WM_STATE_NORMAL; + } + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xsurface->window_id, + xwm->atoms[WM_STATE], + xwm->atoms[WM_STATE], + 32, // format + sizeof(property) / sizeof(property[0]), property); +} + +void wlr_xwayland_surface_restack(struct wlr_xwayland_surface *xsurface, + struct wlr_xwayland_surface *sibling, enum xcb_stack_mode_t mode) { + struct wlr_xwm *xwm = xsurface->xwm; + uint32_t values[2]; + size_t idx = 0; + uint32_t flags = XCB_CONFIG_WINDOW_STACK_MODE; + + assert(!xsurface->override_redirect); + + // X11 clients expect their override_redirect windows to stay on top. + // Avoid interfering by restacking above the topmost managed surface. + if (mode == XCB_STACK_MODE_ABOVE && !sibling) { + sibling = wl_container_of(xwm->surfaces_in_stack_order.prev, sibling, stack_link); + } + + if (sibling == xsurface) { + return; + } + + if (sibling != NULL) { + values[idx++] = sibling->window_id; + flags |= XCB_CONFIG_WINDOW_SIBLING; + } + values[idx++] = mode; + + xcb_configure_window(xwm->xcb_conn, xsurface->window_id, flags, values); + + wl_list_remove(&xsurface->stack_link); + + struct wl_list *node; + if (mode == XCB_STACK_MODE_ABOVE) { + node = &sibling->stack_link; + } else if (mode == XCB_STACK_MODE_BELOW) { + if (sibling) { + node = sibling->stack_link.prev; + } else { + node = &xwm->surfaces_in_stack_order; + } + } else { + // Not implementing XCB_STACK_MODE_TOP_IF | XCB_STACK_MODE_BOTTOM_IF | + // XCB_STACK_MODE_OPPOSITE. + abort(); + } + + wl_list_insert(node, &xsurface->stack_link); + xwm_set_net_client_list_stacking(xwm); + xcb_flush(xwm->xcb_conn); +} + +static void xwm_handle_map_request(struct wlr_xwm *xwm, + xcb_map_request_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (!xsurface) { + return; + } + + wl_signal_emit_mutable(&xsurface->events.map_request, NULL); + xcb_map_window(xwm->xcb_conn, ev->window); +} + +static void xwm_handle_map_notify(struct wlr_xwm *xwm, + xcb_map_notify_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (!xsurface) { + return; + } + + xwm_update_override_redirect(xsurface, ev->override_redirect); + + if (!xsurface->override_redirect) { + wlr_xwayland_surface_set_withdrawn(xsurface, false); + wlr_xwayland_surface_restack(xsurface, NULL, XCB_STACK_MODE_BELOW); + } +} + +static void xwm_handle_unmap_notify(struct wlr_xwm *xwm, + xcb_unmap_notify_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (xsurface == NULL) { + return; + } + + xwayland_surface_dissociate(xsurface); + + if (!xsurface->override_redirect) { + wlr_xwayland_surface_set_withdrawn(xsurface, true); + } +} + +static void xwm_handle_property_notify(struct wlr_xwm *xwm, + xcb_property_notify_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (xsurface == NULL) { + return; + } + + xcb_get_property_cookie_t cookie = + xcb_get_property(xwm->xcb_conn, 0, xsurface->window_id, ev->atom, XCB_ATOM_ANY, 0, 2048); + xcb_get_property_reply_t *reply = + xcb_get_property_reply(xwm->xcb_conn, cookie, NULL); + if (reply == NULL) { + wlr_log(WLR_ERROR, "Failed to get window property"); + return; + } + + read_surface_property(xwm, xsurface, ev->atom, reply); + free(reply); +} + +static void xwm_handle_surface_id_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (xsurface == NULL) { + wlr_log(WLR_DEBUG, + "client message WL_SURFACE_ID but no new window %u ?", + ev->window); + return; + } + /* Check if we got notified after wayland surface create event */ + uint32_t id = ev->data.data32[0]; + struct wl_resource *resource = + wl_client_get_object(xwm->xwayland->server->client, id); + if (resource) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + xwayland_surface_associate(xwm, xsurface, surface); + } else { + xsurface->surface_id = id; + wl_list_remove(&xsurface->unpaired_link); + wl_list_insert(&xwm->unpaired_surfaces, &xsurface->unpaired_link); + } +} + +static void xwm_handle_surface_serial_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (xsurface == NULL) { + wlr_log(WLR_DEBUG, + "Received client message WL_SURFACE_SERIAL but no X11 window %u", + ev->window); + return; + } + if (xsurface->serial != 0) { + wlr_log(WLR_DEBUG, "Received multiple client messages WL_SURFACE_SERIAL " + "for the same X11 window %u", ev->window); + return; + } + + uint32_t serial_lo = ev->data.data32[0]; + uint32_t serial_hi = ev->data.data32[1]; + xsurface->serial = ((uint64_t)serial_hi << 32) | serial_lo; + + struct wlr_surface *surface = wlr_xwayland_shell_v1_surface_from_serial( + xwm->xwayland->shell_v1, xsurface->serial); + if (surface != NULL) { + xwayland_surface_associate(xwm, xsurface, surface); + } else { + wl_list_remove(&xsurface->unpaired_link); + wl_list_insert(&xwm->unpaired_surfaces, &xsurface->unpaired_link); + } +} + +#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#define _NET_WM_MOVERESIZE_SIZE_TOP 1 +#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 +#define _NET_WM_MOVERESIZE_MOVE 8 // movement only +#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 // size via keyboard +#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 // move via keyboard +#define _NET_WM_MOVERESIZE_CANCEL 11 // cancel operation + +static enum wlr_edges net_wm_edges_to_wlr(uint32_t net_wm_edges) { + switch(net_wm_edges) { + case _NET_WM_MOVERESIZE_SIZE_TOPLEFT: + return WLR_EDGE_TOP | WLR_EDGE_LEFT; + case _NET_WM_MOVERESIZE_SIZE_TOP: + return WLR_EDGE_TOP; + case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT: + return WLR_EDGE_TOP | WLR_EDGE_RIGHT; + case _NET_WM_MOVERESIZE_SIZE_RIGHT: + return WLR_EDGE_RIGHT; + case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: + return WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT; + case _NET_WM_MOVERESIZE_SIZE_BOTTOM: + return WLR_EDGE_BOTTOM; + case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT: + return WLR_EDGE_BOTTOM | WLR_EDGE_LEFT; + case _NET_WM_MOVERESIZE_SIZE_LEFT: + return WLR_EDGE_LEFT; + default: + return WLR_EDGE_NONE; + } +} + +static void xwm_handle_net_wm_moveresize_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (!xsurface) { + return; + } + + int detail = ev->data.data32[2]; + switch (detail) { + case _NET_WM_MOVERESIZE_MOVE: + wl_signal_emit_mutable(&xsurface->events.request_move, NULL); + break; + case _NET_WM_MOVERESIZE_SIZE_TOPLEFT: + case _NET_WM_MOVERESIZE_SIZE_TOP: + case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT: + case _NET_WM_MOVERESIZE_SIZE_RIGHT: + case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: + case _NET_WM_MOVERESIZE_SIZE_BOTTOM: + case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT: + case _NET_WM_MOVERESIZE_SIZE_LEFT:; + struct wlr_xwayland_resize_event resize_event = { + .surface = xsurface, + .edges = net_wm_edges_to_wlr(detail), + }; + wl_signal_emit_mutable(&xsurface->events.request_resize, &resize_event); + break; + case _NET_WM_MOVERESIZE_CANCEL: + // handled by the compositor + break; + } +} + +#define _NET_WM_STATE_REMOVE 0 +#define _NET_WM_STATE_ADD 1 +#define _NET_WM_STATE_TOGGLE 2 + +static bool update_state(int action, bool *state) { + int new_state, changed; + + switch (action) { + case _NET_WM_STATE_REMOVE: + new_state = false; + break; + case _NET_WM_STATE_ADD: + new_state = true; + break; + case _NET_WM_STATE_TOGGLE: + new_state = !*state; + break; + default: + return false; + } + + changed = (*state != new_state); + *state = new_state; + + return changed; +} + +static bool xsurface_is_maximized( + struct wlr_xwayland_surface *xsurface) { + return xsurface->maximized_horz && xsurface->maximized_vert; +} + +static void xwm_handle_net_wm_state_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *client_message) { + struct wlr_xwayland_surface *xsurface = + lookup_surface(xwm, client_message->window); + if (!xsurface) { + return; + } + if (client_message->format != 32) { + return; + } + + bool fullscreen = xsurface->fullscreen; + bool maximized = xsurface_is_maximized(xsurface); + bool minimized = xsurface->minimized; + + uint32_t action = client_message->data.data32[0]; + for (size_t i = 0; i < 2; ++i) { + xcb_atom_t property = client_message->data.data32[1 + i]; + + bool changed = false; + if (property == xwm->atoms[NET_WM_STATE_MODAL]) { + changed = update_state(action, &xsurface->modal); + } else if (property == xwm->atoms[NET_WM_STATE_FULLSCREEN]) { + changed = update_state(action, &xsurface->fullscreen); + } else if (property == xwm->atoms[NET_WM_STATE_MAXIMIZED_VERT]) { + changed = update_state(action, &xsurface->maximized_vert); + } else if (property == xwm->atoms[NET_WM_STATE_MAXIMIZED_HORZ]) { + changed = update_state(action, &xsurface->maximized_horz); + } else if (property == xwm->atoms[NET_WM_STATE_HIDDEN]) { + changed = update_state(action, &xsurface->minimized); + } else if (property != XCB_ATOM_NONE && wlr_log_get_verbosity() >= WLR_DEBUG) { + char *prop_name = xwm_get_atom_name(xwm, property); + wlr_log(WLR_DEBUG, "Unhandled NET_WM_STATE property change " + "%"PRIu32" (%s)", property, prop_name ? prop_name : "(null)"); + free(prop_name); + } + + if (changed) { + xsurface_set_net_wm_state(xsurface); + } + } + // client_message->data.data32[3] is the source indication + // all other values are set to 0 + + if (fullscreen != xsurface->fullscreen) { + if (xsurface->fullscreen) { + xsurface->saved_width = xsurface->width; + xsurface->saved_height = xsurface->height; + } + + wl_signal_emit_mutable(&xsurface->events.request_fullscreen, NULL); + } + + if (maximized != xsurface_is_maximized(xsurface)) { + if (xsurface_is_maximized(xsurface)) { + xsurface->saved_width = xsurface->width; + xsurface->saved_height = xsurface->height; + } + + wl_signal_emit_mutable(&xsurface->events.request_maximize, NULL); + } + + if (minimized != xsurface->minimized) { + if (xsurface->minimized) { + xsurface->saved_width = xsurface->width; + xsurface->saved_height = xsurface->height; + } + + struct wlr_xwayland_minimize_event minimize_event = { + .surface = xsurface, + .minimize = xsurface->minimized, + }; + wl_signal_emit_mutable(&xsurface->events.request_minimize, &minimize_event); + } +} + +static void xwm_handle_wm_protocols_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + xcb_atom_t type = ev->data.data32[0]; + + if (type == xwm->atoms[NET_WM_PING]) { + xcb_window_t window_id = ev->data.data32[2]; + + struct wlr_xwayland_surface *surface = lookup_surface(xwm, window_id); + if (surface == NULL) { + return; + } + + if (!surface->pinging) { + return; + } + + wl_event_source_timer_update(surface->ping_timer, 0); + surface->pinging = false; + } else if (wlr_log_get_verbosity() >= WLR_DEBUG) { + char *type_name = xwm_get_atom_name(xwm, type); + wlr_log(WLR_DEBUG, "unhandled WM_PROTOCOLS client message %" PRIu32 " (%s)", + type, type_name ? type_name : "(null)"); + free(type_name); + } +} + +static void xwm_handle_net_active_window_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + struct wlr_xwayland_surface *surface = lookup_surface(xwm, ev->window); + if (surface == NULL) { + return; + } + wl_signal_emit_mutable(&surface->events.request_activate, NULL); +} + +static void pending_startup_id_destroy(struct pending_startup_id *pending) { + wl_list_remove(&pending->link); + free(pending->msg); + free(pending); +} + +static void xwm_handle_net_startup_info_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + struct pending_startup_id *pending, *curr = NULL; + wl_list_for_each(pending, &xwm->pending_startup_ids, link) { + if (pending->window == ev->window) { + curr = pending; + break; + } + } + + char *start; + size_t buf_len = sizeof(ev->data); + if (curr) { + curr->msg = realloc(curr->msg, curr->len + buf_len); + if (!curr->msg) { + pending_startup_id_destroy(curr); + return; + } + start = curr->msg + curr->len; + curr->len += buf_len; + } else { + curr = calloc(1, sizeof(*curr)); + if (!curr) + return; + curr->window = ev->window; + curr->msg = malloc(buf_len); + if (!curr->msg) { + free(curr); + return; + } + start = curr->msg; + curr->len = buf_len; + wl_list_insert(&xwm->pending_startup_ids, &curr->link); + } + + char *id = NULL; + const char *data = (const char *)ev->data.data8; + for (size_t i = 0; i < buf_len; i++) { + start[i] = data[i]; + if (start[i] == '\0') { + if (strncmp(curr->msg, STARTUP_INFO_REMOVE_PREFIX, + strlen(STARTUP_INFO_REMOVE_PREFIX)) == 0 && + strlen(curr->msg) > strlen(STARTUP_INFO_REMOVE_PREFIX)) { + id = curr->msg + strlen(STARTUP_INFO_REMOVE_PREFIX); + break; + } else { + wlr_log(WLR_ERROR, "Unhandled message '%s'\n", curr->msg); + pending_startup_id_destroy(curr); + return; + } + } + } + + if (id) { + struct wlr_xwayland_remove_startup_info_event data = { id, ev->window }; + wlr_log(WLR_DEBUG, "Got startup id: %s", id); + wl_signal_emit_mutable(&xwm->xwayland->events.remove_startup_info, &data); + pending_startup_id_destroy(curr); + } +} + +static void xwm_handle_wm_change_state_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + uint32_t detail = ev->data.data32[0]; + + if (xsurface == NULL) { + return; + } + + bool minimize; + if (detail == XCB_ICCCM_WM_STATE_ICONIC) { + minimize = true; + } else if (detail == XCB_ICCCM_WM_STATE_NORMAL) { + minimize = false; + } else { + wlr_log(WLR_DEBUG, "unhandled wm_change_state event %u", detail); + return; + } + + struct wlr_xwayland_minimize_event minimize_event = { + .surface = xsurface, + .minimize = minimize, + }; + wl_signal_emit_mutable(&xsurface->events.request_minimize, &minimize_event); +} + +static void xwm_handle_client_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + if (ev->type == xwm->atoms[WL_SURFACE_ID]) { + xwm_handle_surface_id_message(xwm, ev); + } else if (ev->type == xwm->atoms[WL_SURFACE_SERIAL]) { + xwm_handle_surface_serial_message(xwm, ev); + } else if (ev->type == xwm->atoms[NET_WM_STATE]) { + xwm_handle_net_wm_state_message(xwm, ev); + } else if (ev->type == xwm->atoms[NET_WM_MOVERESIZE]) { + xwm_handle_net_wm_moveresize_message(xwm, ev); + } else if (ev->type == xwm->atoms[WM_PROTOCOLS]) { + xwm_handle_wm_protocols_message(xwm, ev); + } else if (ev->type == xwm->atoms[NET_ACTIVE_WINDOW]) { + xwm_handle_net_active_window_message(xwm, ev); + } else if (ev->type == xwm->atoms[NET_STARTUP_INFO] || + ev->type == xwm->atoms[NET_STARTUP_INFO_BEGIN]) { + xwm_handle_net_startup_info_message(xwm, ev); + } else if (ev->type == xwm->atoms[WM_CHANGE_STATE]) { + xwm_handle_wm_change_state_message(xwm, ev); + } else if (!xwm_handle_selection_client_message(xwm, ev) && + wlr_log_get_verbosity() >= WLR_DEBUG) { + char *type_name = xwm_get_atom_name(xwm, ev->type); + wlr_log(WLR_DEBUG, "unhandled x11 client message %" PRIu32 " (%s)", ev->type, + type_name ? type_name : "(null)"); + free(type_name); + } +} + +static bool validate_focus_serial(uint16_t last_focus_seq, uint16_t event_seq) { + uint16_t rev_dist = event_seq - last_focus_seq; + if (rev_dist >= UINT16_MAX / 2) { + // Probably overflow or too old + return false; + } + + return true; +} + +static void xwm_handle_focus_in(struct wlr_xwm *xwm, + xcb_focus_in_event_t *ev) { + // Do not interfere with grabs + if (ev->mode == XCB_NOTIFY_MODE_GRAB || + ev->mode == XCB_NOTIFY_MODE_UNGRAB) { + return; + } + // Ignore pointer focus change events + if (ev->detail == XCB_NOTIFY_DETAIL_POINTER) { + return; + } + + // Do not let X clients change the focus behind the compositor's + // back. Reset the focus to the old one if it changed. + // + // Note: Some applications rely on being able to change focus, for ex. Steam: + // https://github.com/swaywm/sway/issues/1865 + // Because of that, we allow changing focus between surfaces belonging to the + // same application. We must be careful to ignore requests that are too old + // though, because otherwise it may lead to race conditions: + // https://github.com/swaywm/wlroots/issues/2324 + struct wlr_xwayland_surface *requested_focus = lookup_surface(xwm, ev->event); + if (xwm->focus_surface && requested_focus && + requested_focus->pid == xwm->focus_surface->pid && + validate_focus_serial(xwm->last_focus_seq, ev->sequence)) { + xwm_set_focus_window(xwm, requested_focus); + } else { + xwm_set_focus_window(xwm, xwm->focus_surface); + } +} + +static void xwm_handle_xcb_error(struct wlr_xwm *xwm, xcb_value_error_t *ev) { +#if HAVE_XCB_ERRORS + const char *major_name = + xcb_errors_get_name_for_major_code(xwm->errors_context, + ev->major_opcode); + if (!major_name) { + wlr_log(WLR_DEBUG, "xcb error happened, but could not get major name"); + goto log_raw; + } + + const char *minor_name = + xcb_errors_get_name_for_minor_code(xwm->errors_context, + ev->major_opcode, ev->minor_opcode); + + const char *extension; + const char *error_name = + xcb_errors_get_name_for_error(xwm->errors_context, + ev->error_code, &extension); + if (!error_name) { + wlr_log(WLR_DEBUG, "xcb error happened, but could not get error name"); + goto log_raw; + } + + wlr_log(WLR_ERROR, "xcb error: op %s (%s), code %s (%s), sequence %"PRIu16", value %"PRIu32, + major_name, minor_name ? minor_name : "no minor", + error_name, extension ? extension : "no extension", + ev->sequence, ev->bad_value); + + return; +log_raw: +#endif + wlr_log(WLR_ERROR, + "xcb error: op %"PRIu8":%"PRIu16", code %"PRIu8", sequence %"PRIu16", value %"PRIu32, + ev->major_opcode, ev->minor_opcode, ev->error_code, + ev->sequence, ev->bad_value); + +} + +static void xwm_handle_unhandled_event(struct wlr_xwm *xwm, xcb_generic_event_t *ev) { +#if HAVE_XCB_ERRORS + const char *extension; + const char *event_name = + xcb_errors_get_name_for_xcb_event(xwm->errors_context, + ev, &extension); + if (!event_name) { + wlr_log(WLR_DEBUG, "no name for unhandled event: %u", + ev->response_type); + return; + } + + wlr_log(WLR_DEBUG, "unhandled X11 event: %s (%u)", event_name, ev->response_type); +#else + wlr_log(WLR_DEBUG, "unhandled X11 event: %u", ev->response_type); +#endif +} + +static int x11_event_handler(int fd, uint32_t mask, void *data) { + int count = 0; + xcb_generic_event_t *event; + struct wlr_xwm *xwm = data; + + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { + xwm_destroy(xwm); + return 0; + } + + while ((event = xcb_poll_for_event(xwm->xcb_conn))) { + count++; + + if (xwm->xwayland->user_event_handler && + xwm->xwayland->user_event_handler(xwm, event)) { + free(event); + continue; + } + + if (xwm_handle_selection_event(xwm, event)) { + free(event); + continue; + } + + switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { + case XCB_CREATE_NOTIFY: + xwm_handle_create_notify(xwm, (xcb_create_notify_event_t *)event); + break; + case XCB_DESTROY_NOTIFY: + xwm_handle_destroy_notify(xwm, (xcb_destroy_notify_event_t *)event); + break; + case XCB_CONFIGURE_REQUEST: + xwm_handle_configure_request(xwm, + (xcb_configure_request_event_t *)event); + break; + case XCB_CONFIGURE_NOTIFY: + xwm_handle_configure_notify(xwm, + (xcb_configure_notify_event_t *)event); + break; + case XCB_MAP_REQUEST: + xwm_handle_map_request(xwm, (xcb_map_request_event_t *)event); + break; + case XCB_MAP_NOTIFY: + xwm_handle_map_notify(xwm, (xcb_map_notify_event_t *)event); + break; + case XCB_UNMAP_NOTIFY: + xwm_handle_unmap_notify(xwm, (xcb_unmap_notify_event_t *)event); + break; + case XCB_PROPERTY_NOTIFY: + xwm_handle_property_notify(xwm, + (xcb_property_notify_event_t *)event); + break; + case XCB_CLIENT_MESSAGE: + xwm_handle_client_message(xwm, (xcb_client_message_event_t *)event); + break; + case XCB_FOCUS_IN: + xwm_handle_focus_in(xwm, (xcb_focus_in_event_t *)event); + break; + case 0: + xwm_handle_xcb_error(xwm, (xcb_value_error_t *)event); + break; + default: + xwm_handle_unhandled_event(xwm, event); + break; + } + free(event); + } + + if (count) { + xcb_flush(xwm->xcb_conn); + } + + return count; +} + +static void handle_compositor_new_surface(struct wl_listener *listener, + void *data) { + struct wlr_xwm *xwm = + wl_container_of(listener, xwm, compositor_new_surface); + struct wlr_surface *surface = data; + + struct wl_client *client = wl_resource_get_client(surface->resource); + if (client != xwm->xwayland->server->client) { + return; + } + + wlr_log(WLR_DEBUG, "New xwayland surface: %p", surface); + + uint32_t surface_id = wl_resource_get_id(surface->resource); + struct wlr_xwayland_surface *xsurface; + wl_list_for_each(xsurface, &xwm->unpaired_surfaces, unpaired_link) { + if (xsurface->surface_id == surface_id) { + xwayland_surface_associate(xwm, xsurface, surface); + xcb_flush(xwm->xcb_conn); + return; + } + } +} + +static void handle_compositor_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xwm *xwm = + wl_container_of(listener, xwm, compositor_destroy); + wl_list_remove(&xwm->compositor_new_surface.link); + wl_list_remove(&xwm->compositor_destroy.link); + wl_list_init(&xwm->compositor_new_surface.link); + wl_list_init(&xwm->compositor_destroy.link); +} + +static void handle_shell_v1_new_surface(struct wl_listener *listener, + void *data) { + struct wlr_xwm *xwm = wl_container_of(listener, xwm, shell_v1_new_surface); + struct wlr_xwayland_surface_v1 *shell_surface = data; + + struct wlr_xwayland_surface *xsurface; + wl_list_for_each(xsurface, &xwm->unpaired_surfaces, unpaired_link) { + if (xsurface->serial == shell_surface->serial) { + xwayland_surface_associate(xwm, xsurface, shell_surface->surface); + return; + } + } +} + +void wlr_xwayland_surface_activate(struct wlr_xwayland_surface *xsurface, + bool activated) { + struct wlr_xwayland_surface *focused = xsurface->xwm->focus_surface; + if (activated) { + xwm_surface_activate(xsurface->xwm, xsurface); + } else if (focused == xsurface) { + xwm_surface_activate(xsurface->xwm, NULL); + } +} + +void wlr_xwayland_surface_configure(struct wlr_xwayland_surface *xsurface, + int16_t x, int16_t y, uint16_t width, uint16_t height) { + int old_w = xsurface->width; + int old_h = xsurface->height; + + xsurface->x = x; + xsurface->y = y; + xsurface->width = width; + xsurface->height = height; + + struct wlr_xwm *xwm = xsurface->xwm; + uint32_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | + XCB_CONFIG_WINDOW_BORDER_WIDTH; + uint32_t values[] = {x, y, width, height, 0}; + xcb_configure_window(xwm->xcb_conn, xsurface->window_id, mask, values); + + // If the window size did not change, then we cannot rely on + // the X server to generate a ConfigureNotify event. Instead, + // we are supposed to send a synthetic event. See ICCCM part + // 4.1.5. But we ignore override-redirect windows as ICCCM does + // not apply to them. + if (width == old_w && height == old_h && !xsurface->override_redirect) { + xcb_configure_notify_event_t configure_notify = { + .response_type = XCB_CONFIGURE_NOTIFY, + .event = xsurface->window_id, + .window = xsurface->window_id, + .x = x, + .y = y, + .width = width, + .height = height, + }; + + xcb_send_event(xwm->xcb_conn, 0, xsurface->window_id, + XCB_EVENT_MASK_STRUCTURE_NOTIFY, + (const char *)&configure_notify); + } + + xcb_flush(xwm->xcb_conn); +} + +void wlr_xwayland_surface_close(struct wlr_xwayland_surface *xsurface) { + struct wlr_xwm *xwm = xsurface->xwm; + + bool supports_delete = false; + for (size_t i = 0; i < xsurface->protocols_len; i++) { + if (xsurface->protocols[i] == xwm->atoms[WM_DELETE_WINDOW]) { + supports_delete = true; + break; + } + } + + if (supports_delete) { + xcb_client_message_data_t message_data = {0}; + message_data.data32[0] = xwm->atoms[WM_DELETE_WINDOW]; + message_data.data32[1] = XCB_CURRENT_TIME; + xwm_send_wm_message(xsurface, &message_data, XCB_EVENT_MASK_NO_EVENT); + } else { + xcb_kill_client(xwm->xcb_conn, xsurface->window_id); + xcb_flush(xwm->xcb_conn); + } +} + +void xwm_destroy(struct wlr_xwm *xwm) { + if (!xwm) { + return; + } + + xwm_selection_finish(&xwm->clipboard_selection); + xwm_selection_finish(&xwm->primary_selection); + xwm_selection_finish(&xwm->dnd_selection); + + if (xwm->seat) { + if (xwm->seat->selection_source && + data_source_is_xwayland(xwm->seat->selection_source)) { + wlr_seat_set_selection(xwm->seat, NULL, + wl_display_next_serial(xwm->xwayland->wl_display)); + } + + if (xwm->seat->primary_selection_source && + primary_selection_source_is_xwayland( + xwm->seat->primary_selection_source)) { + wlr_seat_set_primary_selection(xwm->seat, NULL, + wl_display_next_serial(xwm->xwayland->wl_display)); + } + + wlr_xwayland_set_seat(xwm->xwayland, NULL); + } + + if (xwm->cursor) { + xcb_free_cursor(xwm->xcb_conn, xwm->cursor); + } + if (xwm->colormap) { + xcb_free_colormap(xwm->xcb_conn, xwm->colormap); + } + if (xwm->window) { + xcb_destroy_window(xwm->xcb_conn, xwm->window); + } + if (xwm->event_source) { + wl_event_source_remove(xwm->event_source); + } +#if HAVE_XCB_ERRORS + if (xwm->errors_context) { + xcb_errors_context_free(xwm->errors_context); + } +#endif + struct wlr_xwayland_surface *xsurface, *tmp; + wl_list_for_each_safe(xsurface, tmp, &xwm->surfaces, link) { + xwayland_surface_destroy(xsurface); + } + wl_list_for_each_safe(xsurface, tmp, &xwm->unpaired_surfaces, unpaired_link) { + xwayland_surface_destroy(xsurface); + } + wl_list_remove(&xwm->compositor_new_surface.link); + wl_list_remove(&xwm->compositor_destroy.link); + wl_list_remove(&xwm->shell_v1_new_surface.link); + xcb_disconnect(xwm->xcb_conn); + + struct pending_startup_id *pending, *next; + wl_list_for_each_safe(pending, next, &xwm->pending_startup_ids, link) { + pending_startup_id_destroy(pending); + } + + xwm->xwayland->xwm = NULL; + free(xwm); +} + +static void xwm_get_resources(struct wlr_xwm *xwm) { + xcb_prefetch_extension_data(xwm->xcb_conn, &xcb_xfixes_id); + xcb_prefetch_extension_data(xwm->xcb_conn, &xcb_composite_id); + xcb_prefetch_extension_data(xwm->xcb_conn, &xcb_res_id); + + size_t i; + xcb_intern_atom_cookie_t cookies[ATOM_LAST]; + + for (i = 0; i < ATOM_LAST; i++) { + cookies[i] = + xcb_intern_atom(xwm->xcb_conn, 0, strlen(atom_map[i]), atom_map[i]); + } + for (i = 0; i < ATOM_LAST; i++) { + xcb_generic_error_t *error; + xcb_intern_atom_reply_t *reply = + xcb_intern_atom_reply(xwm->xcb_conn, cookies[i], &error); + if (reply && !error) { + xwm->atoms[i] = reply->atom; + } + free(reply); + + if (error) { + wlr_log(WLR_ERROR, "could not resolve atom %s, x11 error code %d", + atom_map[i], error->error_code); + free(error); + return; + } + } + + xwm->xfixes = xcb_get_extension_data(xwm->xcb_conn, &xcb_xfixes_id); + + if (!xwm->xfixes || !xwm->xfixes->present) { + wlr_log(WLR_DEBUG, "xfixes not available"); + } + + xcb_xfixes_query_version_cookie_t xfixes_cookie; + xcb_xfixes_query_version_reply_t *xfixes_reply; + xfixes_cookie = + xcb_xfixes_query_version(xwm->xcb_conn, XCB_XFIXES_MAJOR_VERSION, + XCB_XFIXES_MINOR_VERSION); + xfixes_reply = + xcb_xfixes_query_version_reply(xwm->xcb_conn, xfixes_cookie, NULL); + + wlr_log(WLR_DEBUG, "xfixes version: %" PRIu32 ".%" PRIu32, + xfixes_reply->major_version, xfixes_reply->minor_version); + xwm->xfixes_major_version = xfixes_reply->major_version; + + free(xfixes_reply); + + const xcb_query_extension_reply_t *xres = + xcb_get_extension_data(xwm->xcb_conn, &xcb_res_id); + if (!xres || !xres->present) { + return; + } + + xcb_res_query_version_cookie_t xres_cookie = + xcb_res_query_version(xwm->xcb_conn, XCB_RES_MAJOR_VERSION, + XCB_RES_MINOR_VERSION); + xcb_res_query_version_reply_t *xres_reply = + xcb_res_query_version_reply(xwm->xcb_conn, xres_cookie, NULL); + if (xres_reply == NULL) { + return; + } + + wlr_log(WLR_DEBUG, "xres version: %" PRIu32 ".%" PRIu32, + xres_reply->server_major, xres_reply->server_minor); + if (xres_reply->server_major > 1 || + (xres_reply->server_major == 1 && xres_reply->server_minor >= 2)) { + xwm->xres = xres; + } + free(xres_reply); +} + +static void xwm_create_wm_window(struct wlr_xwm *xwm) { + static const char name[] = "wlroots wm"; + + xwm->window = xcb_generate_id(xwm->xcb_conn); + + xcb_create_window(xwm->xcb_conn, + XCB_COPY_FROM_PARENT, + xwm->window, + xwm->screen->root, + 0, 0, + 10, 10, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + xwm->screen->root_visual, + 0, NULL); + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->window, + xwm->atoms[NET_WM_NAME], + xwm->atoms[UTF8_STRING], + 8, // format + strlen(name), name); + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->screen->root, + xwm->atoms[NET_SUPPORTING_WM_CHECK], + XCB_ATOM_WINDOW, + 32, // format + 1, &xwm->window); + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->window, + xwm->atoms[NET_SUPPORTING_WM_CHECK], + XCB_ATOM_WINDOW, + 32, // format + 1, &xwm->window); + + xcb_set_selection_owner(xwm->xcb_conn, + xwm->window, + xwm->atoms[WM_S0], + XCB_CURRENT_TIME); + + xcb_set_selection_owner(xwm->xcb_conn, + xwm->window, + xwm->atoms[NET_WM_CM_S0], + XCB_CURRENT_TIME); +} + +// TODO use me to support 32 bit color somehow +static void xwm_get_visual_and_colormap(struct wlr_xwm *xwm) { + xcb_depth_iterator_t d_iter; + xcb_visualtype_iterator_t vt_iter; + xcb_visualtype_t *visualtype; + + d_iter = xcb_screen_allowed_depths_iterator(xwm->screen); + visualtype = NULL; + while (d_iter.rem > 0) { + if (d_iter.data->depth == 32) { + vt_iter = xcb_depth_visuals_iterator(d_iter.data); + visualtype = vt_iter.data; + break; + } + + xcb_depth_next(&d_iter); + } + + if (visualtype == NULL) { + wlr_log(WLR_DEBUG, "No 32 bit visualtype\n"); + return; + } + + xwm->visual_id = visualtype->visual_id; + xwm->colormap = xcb_generate_id(xwm->xcb_conn); + xcb_create_colormap(xwm->xcb_conn, + XCB_COLORMAP_ALLOC_NONE, + xwm->colormap, + xwm->screen->root, + xwm->visual_id); +} + +static void xwm_get_render_format(struct wlr_xwm *xwm) { + xcb_render_query_pict_formats_cookie_t cookie = + xcb_render_query_pict_formats(xwm->xcb_conn); + xcb_render_query_pict_formats_reply_t *reply = + xcb_render_query_pict_formats_reply(xwm->xcb_conn, cookie, NULL); + if (!reply) { + wlr_log(WLR_ERROR, "Did not get any reply from xcb_render_query_pict_formats"); + return; + } + xcb_render_pictforminfo_iterator_t iter = + xcb_render_query_pict_formats_formats_iterator(reply); + xcb_render_pictforminfo_t *format = NULL; + while (iter.rem > 0) { + if (iter.data->depth == 32) { + format = iter.data; + break; + } + + xcb_render_pictforminfo_next(&iter); + } + + if (format == NULL) { + wlr_log(WLR_DEBUG, "No 32 bit render format"); + free(reply); + return; + } + + xwm->render_format_id = format->id; + free(reply); +} + +void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride, + uint32_t width, uint32_t height, int32_t hotspot_x, int32_t hotspot_y) { + if (!xwm->render_format_id) { + wlr_log(WLR_ERROR, "Cannot set xwm cursor: no render format available"); + return; + } + if (xwm->cursor) { + xcb_free_cursor(xwm->xcb_conn, xwm->cursor); + } + + int depth = 32; + + xcb_pixmap_t pix = xcb_generate_id(xwm->xcb_conn); + xcb_create_pixmap(xwm->xcb_conn, depth, pix, xwm->screen->root, width, + height); + + xcb_render_picture_t pic = xcb_generate_id(xwm->xcb_conn); + xcb_render_create_picture(xwm->xcb_conn, pic, pix, xwm->render_format_id, + 0, 0); + + xcb_gcontext_t gc = xcb_generate_id(xwm->xcb_conn); + xcb_create_gc(xwm->xcb_conn, gc, pix, 0, NULL); + + xcb_put_image(xwm->xcb_conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, + width, height, 0, 0, 0, depth, stride * height * sizeof(uint8_t), + pixels); + xcb_free_gc(xwm->xcb_conn, gc); + + xwm->cursor = xcb_generate_id(xwm->xcb_conn); + xcb_render_create_cursor(xwm->xcb_conn, xwm->cursor, pic, hotspot_x, + hotspot_y); + xcb_free_pixmap(xwm->xcb_conn, pix); + xcb_render_free_picture(xwm->xcb_conn, pic); + + uint32_t values[] = {xwm->cursor}; + xcb_change_window_attributes(xwm->xcb_conn, xwm->screen->root, + XCB_CW_CURSOR, values); + xcb_flush(xwm->xcb_conn); +} + +struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { + struct wlr_xwm *xwm = calloc(1, sizeof(*xwm)); + if (xwm == NULL) { + return NULL; + } + + xwm->xwayland = xwayland; + wl_list_init(&xwm->surfaces); + wl_list_init(&xwm->surfaces_in_stack_order); + wl_list_init(&xwm->unpaired_surfaces); + wl_list_init(&xwm->pending_startup_ids); + xwm->ping_timeout = 10000; + + xwm->xcb_conn = xcb_connect_to_fd(wm_fd, NULL); + + int rc = xcb_connection_has_error(xwm->xcb_conn); + if (rc) { + wlr_log(WLR_ERROR, "xcb connect failed: %d", rc); + free(xwm); + return NULL; + } + +#if HAVE_XCB_ERRORS + if (xcb_errors_context_new(xwm->xcb_conn, &xwm->errors_context)) { + wlr_log(WLR_ERROR, "Could not allocate error context"); + xwm_destroy(xwm); + return NULL; + } +#endif + + xcb_screen_iterator_t screen_iterator = + xcb_setup_roots_iterator(xcb_get_setup(xwm->xcb_conn)); + xwm->screen = screen_iterator.data; + + struct wl_event_loop *event_loop = + wl_display_get_event_loop(xwayland->wl_display); + xwm->event_source = wl_event_loop_add_fd(event_loop, wm_fd, + WL_EVENT_READABLE, x11_event_handler, xwm); + wl_event_source_check(xwm->event_source); + + xwm_get_resources(xwm); + xwm_get_visual_and_colormap(xwm); + xwm_get_render_format(xwm); + + uint32_t values[] = { + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | + XCB_EVENT_MASK_PROPERTY_CHANGE, + }; + xcb_change_window_attributes(xwm->xcb_conn, + xwm->screen->root, + XCB_CW_EVENT_MASK, + values); + + xcb_composite_redirect_subwindows(xwm->xcb_conn, + xwm->screen->root, + XCB_COMPOSITE_REDIRECT_MANUAL); + + xcb_atom_t supported[] = { + xwm->atoms[NET_WM_STATE], + xwm->atoms[NET_ACTIVE_WINDOW], + xwm->atoms[NET_WM_MOVERESIZE], + xwm->atoms[NET_WM_STATE_FOCUSED], + xwm->atoms[NET_WM_STATE_MODAL], + xwm->atoms[NET_WM_STATE_FULLSCREEN], + xwm->atoms[NET_WM_STATE_MAXIMIZED_VERT], + xwm->atoms[NET_WM_STATE_MAXIMIZED_HORZ], + xwm->atoms[NET_WM_STATE_HIDDEN], + xwm->atoms[NET_CLIENT_LIST], + xwm->atoms[NET_CLIENT_LIST_STACKING], + }; + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->screen->root, + xwm->atoms[NET_SUPPORTED], + XCB_ATOM_ATOM, + 32, + sizeof(supported)/sizeof(*supported), + supported); + +#if HAVE_XCB_XFIXES_SET_CLIENT_DISCONNECT_MODE + if (xwm->xwayland->server->options.terminate_delay > 0 && + xwm->xfixes_major_version >= 6) { + xcb_xfixes_set_client_disconnect_mode(xwm->xcb_conn, + XCB_XFIXES_CLIENT_DISCONNECT_FLAGS_TERMINATE); + } +#endif + + xcb_flush(xwm->xcb_conn); + + xwm_set_net_active_window(xwm, XCB_WINDOW_NONE); + + xwm_selection_init(&xwm->clipboard_selection, xwm, xwm->atoms[CLIPBOARD]); + xwm_selection_init(&xwm->primary_selection, xwm, xwm->atoms[PRIMARY]); + xwm_selection_init(&xwm->dnd_selection, xwm, xwm->atoms[DND_SELECTION]); + + xwm->compositor_new_surface.notify = handle_compositor_new_surface; + wl_signal_add(&xwayland->compositor->events.new_surface, + &xwm->compositor_new_surface); + xwm->compositor_destroy.notify = handle_compositor_destroy; + wl_signal_add(&xwayland->compositor->events.destroy, + &xwm->compositor_destroy); + + xwm->shell_v1_new_surface.notify = handle_shell_v1_new_surface; + wl_signal_add(&xwayland->shell_v1->events.new_surface, + &xwm->shell_v1_new_surface); + + xwm_create_wm_window(xwm); + + xcb_flush(xwm->xcb_conn); + + return xwm; +} + +void wlr_xwayland_surface_set_withdrawn(struct wlr_xwayland_surface *surface, + bool withdrawn) { + surface->withdrawn = withdrawn; + xsurface_set_wm_state(surface); + xsurface_set_net_wm_state(surface); + xcb_flush(surface->xwm->xcb_conn); +} + +void wlr_xwayland_surface_set_minimized(struct wlr_xwayland_surface *surface, + bool minimized) { + surface->minimized = minimized; + xsurface_set_wm_state(surface); + xsurface_set_net_wm_state(surface); + xcb_flush(surface->xwm->xcb_conn); +} + +void wlr_xwayland_surface_set_maximized(struct wlr_xwayland_surface *surface, + bool maximized) { + surface->maximized_horz = maximized; + surface->maximized_vert = maximized; + xsurface_set_net_wm_state(surface); + xcb_flush(surface->xwm->xcb_conn); +} + +void wlr_xwayland_surface_set_fullscreen(struct wlr_xwayland_surface *surface, + bool fullscreen) { + surface->fullscreen = fullscreen; + xsurface_set_net_wm_state(surface); + xcb_flush(surface->xwm->xcb_conn); +} + +bool xwm_atoms_contains(struct wlr_xwm *xwm, xcb_atom_t *atoms, + size_t num_atoms, enum atom_name needle) { + xcb_atom_t atom = xwm->atoms[needle]; + + for (size_t i = 0; i < num_atoms; ++i) { + if (atom == atoms[i]) { + return true; + } + } + + return false; +} + +void wlr_xwayland_surface_ping(struct wlr_xwayland_surface *surface) { + xcb_client_message_data_t data = { 0 }; + data.data32[0] = surface->xwm->atoms[NET_WM_PING]; + data.data32[1] = XCB_CURRENT_TIME; + data.data32[2] = surface->window_id; + + xwm_send_wm_message(surface, &data, XCB_EVENT_MASK_NO_EVENT); + + wl_event_source_timer_update(surface->ping_timer, + surface->xwm->ping_timeout); + surface->pinging = true; +} + +bool wlr_xwayland_or_surface_wants_focus( + const struct wlr_xwayland_surface *xsurface) { + static const enum atom_name needles[] = { + NET_WM_WINDOW_TYPE_COMBO, + NET_WM_WINDOW_TYPE_DND, + NET_WM_WINDOW_TYPE_DROPDOWN_MENU, + NET_WM_WINDOW_TYPE_MENU, + NET_WM_WINDOW_TYPE_NOTIFICATION, + NET_WM_WINDOW_TYPE_POPUP_MENU, + NET_WM_WINDOW_TYPE_SPLASH, + NET_WM_WINDOW_TYPE_DESKTOP, + NET_WM_WINDOW_TYPE_TOOLTIP, + NET_WM_WINDOW_TYPE_UTILITY, + }; + + for (size_t i = 0; i < sizeof(needles) / sizeof(needles[0]); ++i) { + if (xwm_atoms_contains(xsurface->xwm, xsurface->window_type, + xsurface->window_type_len, needles[i])) { + return false; + } + } + + return true; +} + +enum wlr_xwayland_icccm_input_model wlr_xwayland_icccm_input_model( + const struct wlr_xwayland_surface *xsurface) { + bool take_focus = xwm_atoms_contains(xsurface->xwm, + xsurface->protocols, xsurface->protocols_len, + WM_TAKE_FOCUS); + + if (!xsurface->hints || xsurface->hints->input) { + if (take_focus) { + return WLR_ICCCM_INPUT_MODEL_LOCAL; + } + return WLR_ICCCM_INPUT_MODEL_PASSIVE; + } else { + if (take_focus) { + return WLR_ICCCM_INPUT_MODEL_GLOBAL; + } + } + return WLR_ICCCM_INPUT_MODEL_NONE; +} + +void wlr_xwayland_set_workareas(struct wlr_xwayland *wlr_xwayland, + const struct wlr_box *workareas, size_t num_workareas) { + uint32_t *data = malloc(4 * sizeof(uint32_t) * num_workareas); + if (!data) { + return; + } + + for (size_t i = 0; i < num_workareas; i++) { + data[4 * i] = workareas[i].x; + data[4 * i + 1] = workareas[i].y; + data[4 * i + 2] = workareas[i].width; + data[4 * i + 3] = workareas[i].height; + } + + struct wlr_xwm *xwm = wlr_xwayland->xwm; + xcb_change_property(xwm->xcb_conn, XCB_PROP_MODE_REPLACE, + xwm->screen->root, xwm->atoms[NET_WORKAREA], + XCB_ATOM_CARDINAL, 32, 4 * num_workareas, data); + free(data); +} + +xcb_connection_t *wlr_xwayland_get_xwm_connection( + struct wlr_xwayland *wlr_xwayland) { + return wlr_xwayland->xwm ? wlr_xwayland->xwm->xcb_conn : NULL; +}